Break proto_tree_add_item_format() into multiple functions:
[obnox/wireshark/wip.git] / packet-giop.c
1 /* packet-giop.c
2  * Routines for CORBA GIOP/IIOP packet disassembly
3  *
4  * Laurent Deniel <deniel@worldnet.fr>
5  *
6  * $Id: packet-giop.c,v 1.11 2000/03/12 04:47:38 gram Exp $
7  *
8  * Ethereal - Network traffic analyzer
9  * By Gerald Combs <gerald@zing.org>
10  * Copyright 1998 Gerald Combs
11  *
12  * 
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  * 
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  * 
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  *
27  *
28  */
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #ifdef HAVE_SYS_TYPES_H
35 # include <sys/types.h>
36 #endif
37
38 #include <string.h>
39 #include <ctype.h>
40 #include <glib.h>
41 #include "packet.h"
42
43 static int proto_giop = -1;
44 static int hf_giop_message_type = -1;
45 static int hf_giop_message_size = -1;
46
47 static gint ett_giop = -1;
48
49 /*
50  * GIOP / IIOP types definition - OMG CORBA 2.x / GIOP 1.[01]
51  * See OMG WEB site <http://www.omg.org> - CORBA+IIOP 2.2 (98-02-01.ps)
52  *
53  * Notes on mapping:
54  *
55  * <sequence> : unsigned int (# elts) + elements
56  * <string>   : unsigned int (string length) + length characters (with '\0')
57  * <enum>     : unsigned int (from 0 to n)
58  */
59
60 #define GIOP_MAGIC       "GIOP"
61 #define GIOP_MAJOR       1
62 #define GIOP_MINOR       1
63
64 #define GIOP_HEADER_SIZE 12
65
66 typedef struct OctetSequence{
67   u_int         sequence_length;
68   u_char        sequence_data[1];               /* of length bytes */
69 } OctetSequence;
70
71 typedef OctetSequence Principal;
72 typedef OctetSequence String;
73
74 /* 
75  * Some structures that contain sequences can not be directly used 
76  * (alignment problem on 64 bit architectures)
77  */
78
79 typedef struct ServiceContext {
80   u_int         context_id;
81   OctetSequence context_data;
82 } ServiceContext;
83
84 typedef struct ServiceContextList{
85   u_int           nr_context;
86   ServiceContext  service_context[1];           /* nr_context elements */
87 } ServiceContextList;
88
89 typedef enum MsgType {
90   Request,
91   Reply,
92   CancelRequest, 
93   LocateRequest,
94   LocateReply, 
95   CloseConnection,
96   MessageError,
97   Fragment                                      /* GIOP 1.1 only */
98 } MsgType;
99
100 typedef struct Version {
101   u_char        major;
102   u_char        minor;
103 } Version;
104
105 typedef struct MessageHeader {
106   char          magic[4];
107   Version       GIOP_version;
108   u_char        flags;                          /* byte_order in 1.0 */
109   u_char        message_type;
110   u_int         message_size;
111 } MessageHeader;
112
113 typedef struct RequestHeader_1_0 {
114   /* ServiceContextList service_context;*/
115   u_int         request_id;
116   u_char        response_expected;
117   OctetSequence object_key;
118   /* String     operation;              */
119   /* Principal  requesting_principal;   */
120 } RequestHeader_1_0;
121
122 typedef struct RequestHeader_1_1 {
123   /* ServiceContextList service_context;*/
124   u_int         request_id;
125   u_char        response_expected;
126   u_char        reserved[3];
127   OctetSequence object_key;
128   /* String     operation;              */
129   /* Principal  requesting_principal;   */
130 } RequestHeader_1_1;
131
132 typedef enum ReplyStatusType {
133   NO_EXCEPTION, 
134   USER_EXCEPTION, 
135   SYSTEM_EXCEPTION, 
136   LOCATION_FORWARD
137 } ReplyStatusType;
138
139 typedef struct ReplyHeader {
140   /* ServiceContext service_context;    */
141   u_int         request_id;
142   u_int         reply_status;
143 } ReplyHeader;
144
145 typedef struct SystemExceptionReplyBody {
146   String        exception_id; 
147   u_int         minor_code_value;
148   u_int         completion_status;
149 } SystemExceptionReplyBody;
150
151 typedef struct CancelRequestHeader {
152   u_int         request_id;
153 } CancelRequestHeader;
154
155 typedef struct LocateRequestHeader {
156   u_int         request_id;
157   OctetSequence object_key;
158 } LocateRequestHeader;
159
160 typedef enum LocateStatusType {
161   UNKNOWN_OBJECT, 
162   OBJECT_HERE, 
163   OBJECT_FORWARD
164 } LocateStatusType;
165
166 typedef struct LocateReplyHeader {
167   u_int         request_id;
168   u_int         locate_status;
169 } LocateReplyHeader;
170
171
172 static u_char *print_object_key(int length, u_char *from) 
173 {
174 #define MAX_OBJECT_KEY_LENGTH 64
175   static u_char buffer[MAX_OBJECT_KEY_LENGTH];
176   u_char *to = buffer;
177   int i = 0;
178   length = MIN(MAX_OBJECT_KEY_LENGTH - 3, length);
179   *to++ = '"';
180   while(i++ < length) {
181     *to = (isprint(*from)) ? *from : '.'; 
182     to++;
183     from++;
184   }  
185   *to++ = '"';
186   *to = '\0';
187   return buffer;
188 }
189
190 /* main entry point */
191
192 void dissect_giop(const u_char *pd, int offset, frame_data *fd, proto_tree *tree) 
193 {
194
195   MessageHeader header;
196   proto_tree *clnp_tree = NULL;
197   proto_item *ti;
198   u_char response_expected = 0;
199   u_int first_offset = offset;
200   u_int big_endian = FALSE;
201   u_int request_id = 0;
202   u_int message_size;
203   u_int minor_version;
204   u_int context_id;
205   u_int reply_status;
206   u_int locate_status;
207   u_int sequence_length;
208   u_int nr_seq;
209   RequestHeader_1_1 request_1_1;
210   RequestHeader_1_0 request_1_0;
211   ReplyHeader reply;
212   LocateReplyHeader locate_rep;
213   LocateRequestHeader locate_req;
214   int i;
215
216 #define END_OF_GIOP_MESSAGE (offset - first_offset - GIOP_HEADER_SIZE)
217
218   if (!BYTES_ARE_IN_FRAME(offset, GIOP_HEADER_SIZE)) {
219     dissect_data(pd, offset, fd, tree);
220     return;
221   }
222
223   /* avoid alignment problem */
224
225   memcpy(&header, &pd[offset], sizeof(header));
226
227   /* check magic number and version */
228
229   if (memcmp(header.magic, GIOP_MAGIC, sizeof(header.magic)) != 0) {
230     dissect_data(pd, offset, fd, tree);
231     return;
232   }
233
234   if (header.GIOP_version.major != GIOP_MAJOR ||
235       ((minor_version = header.GIOP_version.minor) >  GIOP_MINOR)) {
236     dissect_data(pd, offset, fd, tree);
237     return;
238   }
239
240   switch(minor_version) {
241     case 1  :
242       if (header.flags & 0x01)
243         big_endian = FALSE;
244       else
245         big_endian = TRUE;
246       break;
247     case 0  :
248       if (header.flags)
249         big_endian = FALSE;
250       else
251         big_endian = TRUE;
252       break;
253     default :
254       break;
255   }
256   
257   if (big_endian)
258     message_size = pntohl(&header.message_size);
259   else
260     message_size = pletohl(&header.message_size);
261
262   if (check_col(fd, COL_PROTOCOL)) {
263     col_add_str(fd, COL_PROTOCOL, "GIOP");
264   }
265
266   if (tree) {
267     ti = proto_tree_add_item(tree, proto_giop, offset, 
268                           GIOP_HEADER_SIZE + message_size, NULL);
269     clnp_tree = proto_item_add_subtree(ti, ett_giop);
270     proto_tree_add_text(clnp_tree, offset,      4,
271                      "Magic number: %s", GIOP_MAGIC);
272     proto_tree_add_text(clnp_tree, offset +  4, 2, 
273                      "Version: %d.%d", 
274                      header.GIOP_version.major,
275                      header.GIOP_version.minor);
276     switch(minor_version) {
277       case 1  :
278         proto_tree_add_text(clnp_tree, offset +  6, 1, 
279                          "Flags: 0x%02x (%s%s)", 
280                          header.flags,
281                          (big_endian) ? "little" : "big",
282                          (header.flags & 0x02) ? " fragment" : "");
283         break;
284       case 0  :
285         proto_tree_add_text(clnp_tree, offset +  6, 1, 
286                          "Byte ordering: %s endian",
287                          (big_endian) ? "little" : "big");
288         break;
289       default :
290         break;
291     } /* minor_version */
292
293     proto_tree_add_uint_format(clnp_tree, 
294                                hf_giop_message_type,
295                                offset +  7, 1, 
296                                header.message_type,
297                                "Message type: %s",
298                                (header.message_type == Request) ? "Request" :
299                                (header.message_type == Reply) ? "Reply" :
300                                (header.message_type == CancelRequest) ? "CancelRequest" :
301                                (header.message_type == LocateRequest) ? "LocateRequest" :
302                                (header.message_type == LocateReply) ? "LocateReply" :
303                                (header.message_type == CloseConnection) ? "CloseConnection" :
304                                (header.message_type == MessageError) ? "MessageError" :
305                                (header.message_type == Fragment) ? "Fragment" : "?");
306
307     proto_tree_add_item(clnp_tree, 
308                         hf_giop_message_size,
309                         offset +  8, 4, 
310                         message_size);
311
312   } /* tree */
313
314   offset += GIOP_HEADER_SIZE;
315
316   if (!BYTES_ARE_IN_FRAME(offset, message_size)) {
317     dissect_data(pd, offset, fd, tree);
318     return;
319   }
320
321   /* skip service_context in Request/Reply messages */
322
323   switch(header.message_type) {
324
325     case Request:
326     case Reply :
327
328       nr_seq = (big_endian) ? pntohl(&pd[offset]) : pletohl(&pd[offset]);
329
330       offset += sizeof(nr_seq);
331
332       for (i = 0 ; i < nr_seq ; i++) {
333
334         if (big_endian) {       
335           context_id = pntohl(&pd[offset]);
336           sequence_length = pntohl(&pd[offset + sizeof(context_id)]);
337         }
338         else {
339           context_id = pletohl(&pd[offset]);
340           sequence_length = pletohl(&pd[offset + sizeof(context_id)]);
341         }
342
343         if (tree) {
344           proto_tree_add_text(clnp_tree, offset, sizeof(context_id),
345                            "Context id: %d", context_id);
346           proto_tree_add_text(clnp_tree, offset + sizeof(context_id),
347                            sizeof(sequence_length),
348                            "Sequence length: %d", sequence_length);
349           proto_tree_add_text(clnp_tree,
350                            offset + 
351                            sizeof(context_id) + sizeof(sequence_length),
352                            sequence_length,
353                            "Sequence data: <not shown>");
354         }
355
356         offset += sizeof(context_id) + sizeof(sequence_length) + sequence_length;
357         offset += (sequence_length %4) ? 4 - (sequence_length%4) : 0 ;
358
359       } /* for */
360
361     default :
362       break;
363
364   } /* switch message_type */
365
366   /* decode next parts according to message type */
367
368   switch(header.message_type) {
369
370     case Request:
371
372       switch(minor_version) {
373         case 1  :
374           memcpy(&request_1_1, &pd[offset], sizeof(request_1_1));
375           response_expected = request_1_1.response_expected;
376           request_id = (big_endian)? pntohl(&request_1_1.request_id) :
377             pletohl(&request_1_1.request_id);
378           if (tree) {
379             proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
380                              "Request id: %d", request_id);
381             proto_tree_add_text(clnp_tree, offset + sizeof(request_id),
382                              sizeof(request_1_1.response_expected),
383                              "Response expected: %d", 
384                              response_expected);
385             proto_tree_add_text(clnp_tree, offset + sizeof(request_id) +
386                              sizeof(request_1_1.response_expected),
387                              3,
388                              "Reserved");
389           }
390           offset += sizeof(request_id) + 
391             sizeof(request_1_1.response_expected) + 3;
392           break;
393         case 0  :
394           memcpy(&request_1_0, &pd[offset], sizeof(request_1_0));
395           response_expected = request_1_0.response_expected;
396           request_id = (big_endian)? pntohl(&request_1_0.request_id) :
397             pletohl(&request_1_0.request_id);
398           if (tree) {
399             proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
400                              "Request id: %d", request_id);
401             proto_tree_add_text(clnp_tree, offset + sizeof(request_id),
402                              sizeof(request_1_0.response_expected),
403                              "Response expected: %d", 
404                              response_expected);
405           }
406
407           offset += sizeof(request_id) + 
408             sizeof(request_1_0.response_expected);
409           break;
410         default :
411           break;
412       }
413
414       /* strange thing here with some ORBs/IIOP1.0 ? */
415       if ((offset - first_offset) % 4)
416         offset += 4 - (offset - first_offset)%4;
417
418       /* object_key */
419
420       sequence_length = (big_endian) ? 
421         pntohl(&pd[offset]) : pletohl(&pd[offset]);
422
423       if (tree) {
424         proto_tree_add_text(clnp_tree, offset, sizeof(sequence_length),
425                          "Object key length: %d", sequence_length);
426         proto_tree_add_text(clnp_tree, offset + sizeof(sequence_length),
427                          sequence_length,
428                          "Object key: %s",
429                          print_object_key(sequence_length, 
430                            (u_char *)&pd[offset + sizeof(sequence_length)]));
431       }
432
433       /* operation & requesting_principal */
434
435       offset += sizeof(sequence_length) + sequence_length;
436       offset += (sequence_length %4) ? 4 - (sequence_length%4) : 0 ;
437
438       sequence_length = (big_endian) ? 
439         pntohl(&pd[offset]) : pletohl(&pd[offset]);
440
441       if (sequence_length > message_size) {
442         dissect_data(pd, offset, fd, tree);
443         return;
444       }
445        
446       if (tree) {
447         proto_tree_add_text(clnp_tree, offset, sizeof(sequence_length),
448                          "Operation length: %d", sequence_length);
449         proto_tree_add_text(clnp_tree, offset + sizeof(sequence_length), 
450                          sequence_length,
451                          "Operation: %s",
452                          &pd[offset+sizeof(sequence_length)]);
453         proto_tree_add_text(clnp_tree, offset +
454                          sizeof(sequence_length)+ sequence_length,
455                          message_size - END_OF_GIOP_MESSAGE - 
456                          sizeof(sequence_length) - sequence_length,
457                          "Requesting principal: <not shown>");
458       }
459
460       if (check_col(fd, COL_INFO)) {
461         col_add_fstr(fd, COL_INFO, "Request %s %d: %s",
462                 response_expected ? "two-way" : "one-way" ,
463                 request_id,
464                 &pd[offset+sizeof(sequence_length)]);
465       }
466
467       break;
468
469     case Reply :
470
471       memcpy(&reply, &pd[offset], sizeof(reply));
472       request_id =  (big_endian) ? 
473         pntohl(&reply.request_id) : pletohl(&reply.request_id);
474       reply_status = (big_endian) ? 
475         pntohl(&reply.reply_status) : pletohl(&reply.reply_status);
476
477       if (tree) {
478         proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
479                          "Request id: %d", request_id);
480         proto_tree_add_text(clnp_tree, offset + sizeof(request_id), 
481                          sizeof(reply_status),
482                          "Reply status: %s",
483                          reply_status == NO_EXCEPTION ? "no exception" :
484                          reply_status == USER_EXCEPTION ? "user exception" :
485                          reply_status == SYSTEM_EXCEPTION ? "system exception" :
486                          reply_status == LOCATION_FORWARD ? "location forward" :
487                          "?");
488       }
489
490       if (check_col(fd, COL_INFO)) {
491         col_add_fstr(fd, COL_INFO, "Reply %d: %s",
492                 request_id,
493                 reply_status == NO_EXCEPTION ? "no exception" :
494                 reply_status == USER_EXCEPTION ? "user exception" :
495                 reply_status == SYSTEM_EXCEPTION ? "system exception" :
496                 reply_status == LOCATION_FORWARD ? "location forward" :
497                 "?");
498       }
499
500       offset += sizeof(request_id) + sizeof(reply_status);
501
502       if (reply_status == SYSTEM_EXCEPTION) {
503
504         u_int minor_code_value;
505         u_int completion_status;
506
507         sequence_length = (big_endian) ? 
508           pntohl(&pd[offset]) : pletohl(&pd[offset]);
509
510         if (sequence_length > message_size) {
511           dissect_data(pd, offset, fd, tree);
512           return;
513         }
514
515         if (tree) {
516           proto_tree_add_text(clnp_tree, offset, sizeof(sequence_length),
517                            "Exception length: %d", sequence_length);
518           proto_tree_add_text(clnp_tree, offset + sizeof(sequence_length), 
519                            sequence_length,
520                            "Exception id: %s",
521                            &pd[offset+sizeof(sequence_length)]);
522
523         }
524
525         offset += sizeof(sequence_length) + sequence_length;
526
527         minor_code_value = (big_endian) ? 
528           pntohl(&pd[offset]) : pletohl(&pd[offset]);
529         completion_status = (big_endian) ? 
530           pntohl(&pd[offset+sizeof(minor_code_value)]) :
531           pletohl(&pd[offset+sizeof(minor_code_value)]);
532         
533         if (tree) {
534           proto_tree_add_text(clnp_tree, offset, sizeof(minor_code_value),
535                            "Minor code value: %d", minor_code_value);
536           proto_tree_add_text(clnp_tree, offset + sizeof(minor_code_value),
537                            sizeof(completion_status),
538                            "Completion Status: %d",
539                            completion_status);
540           
541         }
542
543       }
544       else if (reply_status == USER_EXCEPTION) {
545
546         sequence_length = (big_endian) ? 
547           pntohl(&pd[offset]) : pletohl(&pd[offset]);
548
549         if (sequence_length > message_size) {
550           dissect_data(pd, offset, fd, tree);
551           return;
552         }
553
554         if (tree) {
555           proto_tree_add_text(clnp_tree, offset, sizeof(sequence_length),
556                            "Exception length: %d", sequence_length);
557           proto_tree_add_text(clnp_tree, offset + sizeof(sequence_length), 
558                            sequence_length,
559                            "Exception id: %s",
560                            &pd[offset+sizeof(sequence_length)]);
561
562         }
563
564         offset += sizeof(sequence_length) + sequence_length;
565
566         sequence_length = (big_endian) ? 
567           pntohl(&pd[offset]) : pletohl(&pd[offset]);
568
569         if (sequence_length > message_size) {
570           dissect_data(pd, offset, fd, tree);
571           return;
572         }
573
574         if (tree && sequence_length) {
575           proto_tree_add_text(clnp_tree, offset, sizeof(sequence_length),
576                            "Exception member length: %d", sequence_length);
577           proto_tree_add_text(clnp_tree, offset + sizeof(sequence_length), 
578                            sequence_length,
579                            "Exception member: %s",
580                            &pd[offset+sizeof(sequence_length)]);
581         }
582
583         offset += sizeof(sequence_length) + sequence_length;
584
585       }
586       else {
587         
588         if (tree) {
589           proto_tree_add_text(clnp_tree, offset,
590                            message_size - END_OF_GIOP_MESSAGE,
591                            "Reply body: <not shown>");
592         }
593
594       } /* reply_status */
595
596       break;
597
598     case LocateRequest :
599
600       memcpy(&locate_req, &pd[offset], sizeof(locate_req));
601       request_id =  (big_endian) ? 
602         pntohl(&locate_req.request_id) : pletohl(&locate_req.request_id);
603
604       sequence_length = (big_endian) ? 
605         pntohl(&pd[offset+sizeof(request_id)]) : 
606         pletohl(&pd[offset+sizeof(request_id)]);
607
608       if (tree) {
609         proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
610                          "Request id: %d", request_id);
611         proto_tree_add_text(clnp_tree, offset + sizeof(request_id), 
612                          sizeof(sequence_length),
613                          "Object key length: %d", sequence_length);
614         offset += sizeof(request_id) + sizeof(sequence_length);
615         proto_tree_add_text(clnp_tree,
616                          offset,
617                          sequence_length,
618                          "Object key: %s", 
619                          print_object_key(sequence_length, 
620                                           (u_char *)&pd[offset]));
621       }
622
623       if (check_col(fd, COL_INFO)) {
624         col_add_fstr(fd, COL_INFO, "LocateRequest %d", request_id);
625       }
626
627       break;
628
629     case LocateReply :
630
631       memcpy(&locate_rep, &pd[offset], sizeof(locate_rep));
632       request_id =  (big_endian) ? 
633         pntohl(&locate_rep.request_id) : pletohl(&locate_rep.request_id);
634       locate_status = (big_endian) ? 
635         pntohl(&locate_rep.locate_status) : pletohl(&locate_rep.locate_status);
636
637       if (tree) {
638         proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
639                          "Request id: %d", request_id);
640         proto_tree_add_text(clnp_tree, offset + sizeof(request_id), 
641                          sizeof(locate_status),
642                          "Locate status: %d", locate_status);
643         offset += sizeof(request_id) + sizeof(locate_status);
644         if (locate_status == OBJECT_FORWARD) {
645           proto_tree_add_text(clnp_tree, offset,
646                            message_size - END_OF_GIOP_MESSAGE,
647                            "Locate reply body: <not shown>");
648         }
649       }
650
651       if (check_col(fd, COL_INFO)) {
652         col_add_fstr(fd, COL_INFO, "LocateReply %d: %s",
653                 request_id, 
654                 (locate_status == UNKNOWN_OBJECT) ? "Unknown object" :
655                 (locate_status == OBJECT_HERE) ? "Object here" :
656                 (locate_status == OBJECT_FORWARD) ? "Object forward" : "?");
657       }
658
659       break;
660
661     case CancelRequest :
662
663       request_id =  (big_endian) ? 
664         pntohl(&pd[offset]) : pletohl(&pd[offset]);
665
666       if (tree) {
667         proto_tree_add_text(clnp_tree, offset, sizeof(request_id),
668                          "Request id: %d", request_id);
669       }
670
671       if (check_col(fd, COL_INFO)) {
672         col_add_fstr(fd, COL_INFO, "CancelRequest %d", request_id);
673       }
674
675       break;
676
677     case CloseConnection :
678       if (check_col(fd, COL_INFO)) {
679         col_add_str(fd, COL_INFO, "CloseConnection");
680       }
681       break;
682
683     case MessageError :
684       if (check_col(fd, COL_INFO)) {
685         col_add_str(fd, COL_INFO, "MessageError");
686       }
687       break;
688
689     case Fragment :
690       if (check_col(fd, COL_INFO)) {
691         col_add_str(fd, COL_INFO, "Fragment");
692       }
693       break;
694
695     default :
696       break;
697
698   } /* switch message_type */
699
700
701   offset = first_offset + GIOP_HEADER_SIZE + message_size;
702
703   if (IS_DATA_IN_FRAME(offset)) {
704     dissect_data(pd, offset, fd, tree);
705   }
706
707 } /* dissect_giop */
708
709 void 
710 proto_register_giop(void)
711 {
712   static hf_register_info hf[] = {
713     { &hf_giop_message_type,
714       { "Message type",         "giop.type",    FT_UINT8,       BASE_DEC, NULL, 0x0,
715         "" }},
716     { &hf_giop_message_size,
717       { "Message size",         "giop.len",     FT_UINT32,      BASE_DEC, NULL, 0x0,
718         "" }},
719   };
720   static gint *ett[] = {
721     &ett_giop,
722   };
723
724   proto_giop = proto_register_protocol("General Inter-ORB Protocol", "giop");
725   proto_register_field_array(proto_giop, hf, array_length(hf));
726   proto_register_subtree_array(ett, array_length(ett));
727 }