"col_format_to_pref_str()" is used only in "prefs.c", and knows about
[obnox/wireshark/wip.git] / packet-ipp.c
1 /* packet-ipp.c
2  * Routines for IPP packet disassembly
3  *
4  * Guy Harris <guy@alum.mit.edu>
5  *
6  * $Id: packet-ipp.c,v 1.22 2001/01/11 06:30:54 guy 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
41 #include <glib.h>
42 #include "packet.h"
43 #include "strutil.h"
44 #include "packet-http.h"
45
46 static int proto_ipp = -1;
47
48 static gint ett_ipp = -1;
49 static gint ett_ipp_as = -1;
50 static gint ett_ipp_attr = -1;
51
52 #define PRINT_JOB               0x0002
53 #define PRINT_URI               0x0003
54 #define VALIDATE_JOB            0x0004
55 #define CREATE_JOB              0x0005
56 #define SEND_DOCUMENT           0x0006
57 #define SEND_URI                0x0007
58 #define CANCEL_JOB              0x0008
59 #define GET_JOB_ATTRIBUTES      0x0009
60 #define GET_JOBS                0x000A
61 #define GET_PRINTER_ATTRIBUTES  0x000B
62
63 static const value_string operation_vals[] = {
64     { PRINT_JOB,              "Print-Job" },
65     { PRINT_URI,              "Print-URI" },
66     { VALIDATE_JOB,           "Validate-Job" },
67     { CREATE_JOB,             "Create-Job" },
68     { SEND_DOCUMENT,          "Send-Document" },
69     { SEND_URI,               "Send-URI" },
70     { CANCEL_JOB,             "Cancel-Job" },
71     { GET_JOB_ATTRIBUTES,     "Get-Job-Attributes" },
72     { GET_JOBS,               "Get-Jobs" },
73     { GET_PRINTER_ATTRIBUTES, "Get-Printer-Attributes" },
74     { 0,                      NULL }
75 };
76
77 #define STATUS_SUCCESSFUL       0x0000
78 #define STATUS_INFORMATIONAL    0x0100
79 #define STATUS_REDIRECTION      0x0200
80 #define STATUS_CLIENT_ERROR     0x0400
81 #define STATUS_SERVER_ERROR     0x0500
82
83 #define STATUS_TYPE_MASK        0xFF00
84
85 #define SUCCESSFUL_OK                           0x0000
86 #define SUCCESSFUL_OK_IGN_OR_SUB_ATTR           0x0001
87 #define SUCCESSFUL_OK_CONFLICTING_ATTR          0x0002
88
89 #define CLIENT_ERROR_BAD_REQUEST                0x0400
90 #define CLIENT_ERROR_FORBIDDEN                  0x0401
91 #define CLIENT_ERROR_NOT_AUTHENTICATED          0x0402
92 #define CLIENT_ERROR_NOT_AUTHORIZED             0x0403
93 #define CLIENT_ERROR_NOT_POSSIBLE               0x0404
94 #define CLIENT_ERROR_TIMEOUT                    0x0405
95 #define CLIENT_ERROR_NOT_FOUND                  0x0406
96 #define CLIENT_ERROR_GONE                       0x0407
97 #define CLIENT_ERROR_REQ_ENTITY_TOO_LRG         0x0408
98 #define CLIENT_ERROR_REQ_VALUE_TOO_LONG         0x0409
99 #define CLIENT_ERROR_DOC_FMT_NOT_SUPP           0x040A
100 #define CLIENT_ERROR_ATTR_OR_VAL_NOT_SUPP       0x040B
101 #define CLIENT_ERROR_URI_SCHEME_NOT_SUPP        0x040C
102 #define CLIENT_ERROR_CHARSET_NOT_SUPP           0x040D
103 #define CLIENT_ERROR_CONFLICTING_ATTRS          0x040E
104
105 #define SERVER_ERROR_INTERNAL_ERROR             0x0500
106 #define SERVER_ERROR_OPERATION_NOT_SUPP         0x0501
107 #define SERVER_ERROR_SERVICE_UNAVAIL            0x0502
108 #define SERVER_ERROR_VERSION_NOT_SUPP           0x0503
109 #define SERVER_ERROR_DEVICE_ERROR               0x0504
110 #define SERVER_ERROR_TEMPORARY_ERROR            0x0505
111 #define SERVER_ERROR_NOT_ACCEPTING_JOBS         0x0506
112 #define SERVER_ERROR_BUSY                       0x0507
113 #define SERVER_ERROR_JOB_CANCELED               0x0508
114
115 static const value_string status_vals[] = {
116     { SUCCESSFUL_OK,                     "Successful-OK" },
117     { SUCCESSFUL_OK_IGN_OR_SUB_ATTR,     "Successful-OK-Ignored-Or-Substituted-Attributes" },
118     { SUCCESSFUL_OK_CONFLICTING_ATTR,    "Successful-OK-Conflicting-Attributes" },
119     { CLIENT_ERROR_BAD_REQUEST,          "Client-Error-Bad-Request" },
120     { CLIENT_ERROR_FORBIDDEN,            "Client-Error-Forbidden" },
121     { CLIENT_ERROR_NOT_AUTHENTICATED,    "Client-Error-Not-Authenticated" },
122     { CLIENT_ERROR_NOT_AUTHORIZED,       "Client-Error-Not-Authorized" },
123     { CLIENT_ERROR_NOT_POSSIBLE,         "Client-Error-Not-Possible" },
124     { CLIENT_ERROR_TIMEOUT,              "Client-Error-Timeout" },
125     { CLIENT_ERROR_NOT_FOUND,            "Client-Error-Not-Found" },
126     { CLIENT_ERROR_GONE,                 "Client-Error-Gone" },
127     { CLIENT_ERROR_REQ_ENTITY_TOO_LRG,   "Client-Error-Request-Entity-Too-Large" },
128     { CLIENT_ERROR_REQ_VALUE_TOO_LONG,   "Client-Error-Request-Value-Too-Long" },
129     { CLIENT_ERROR_DOC_FMT_NOT_SUPP,     "Client-Error-Document-Format-Not-Supported" },
130     { CLIENT_ERROR_ATTR_OR_VAL_NOT_SUPP, "Client-Error-Attributes-Or-Values-Not-Supported" },
131     { CLIENT_ERROR_URI_SCHEME_NOT_SUPP,  "Client-Error-URI-Scheme-Not-Supported" },
132     { CLIENT_ERROR_CHARSET_NOT_SUPP,     "Client-Error-Charset-Not-Supported" },
133     { CLIENT_ERROR_CONFLICTING_ATTRS,    "Client-Error-Conflicting-Attributes" },
134     { SERVER_ERROR_INTERNAL_ERROR,       "Server-Error-Internal-Error" },
135     { SERVER_ERROR_OPERATION_NOT_SUPP,   "Server-Error-Operation-Not-Supported" },
136     { SERVER_ERROR_SERVICE_UNAVAIL,      "Server-Error-Service-Unavailable" },
137     { SERVER_ERROR_VERSION_NOT_SUPP,     "Server-Error-Version-Not-Supported" },
138     { SERVER_ERROR_DEVICE_ERROR,         "Server-Error-Device-Error" },
139     { SERVER_ERROR_TEMPORARY_ERROR,      "Server-Error-Temporary-Error" },
140     { SERVER_ERROR_NOT_ACCEPTING_JOBS,   "Server-Error-Not-Accepting-Jobs" },
141     { SERVER_ERROR_BUSY,                 "Server-Error-Busy" },
142     { SERVER_ERROR_JOB_CANCELED,         "Server-Error-Job-Canceled" },
143     { 0,                                 NULL }
144 };
145
146 static int parse_attributes(tvbuff_t *tvb, int offset, proto_tree *tree);
147 static proto_tree *add_integer_tree(proto_tree *tree, tvbuff_t *tvb,
148     int offset, int name_length, int value_length);
149 static void add_integer_value(guint tag, gchar *tag_desc, proto_tree *tree,
150     tvbuff_t *tvb, int offset, int name_length, int value_length);
151 static proto_tree *add_octetstring_tree(proto_tree *tree, tvbuff_t *tvb,
152     int offset, int name_length, int value_length);
153 static void add_octetstring_value(guint tag, gchar *tag_desc, proto_tree *tree,
154     tvbuff_t *tvb, int offset, int name_length, int value_length);
155 static proto_tree *add_charstring_tree(proto_tree *tree, tvbuff_t *tvb,
156     int offset, int name_length, int value_length);
157 static void add_charstring_value(guint tag, gchar *tag_desc, proto_tree *tree,
158     tvbuff_t *tvb, int offset, int name_length, int value_length);
159 static int add_value_head(guint tag, gchar *tag_desc, proto_tree *tree,
160     tvbuff_t *tvb, int offset, int name_length, int value_length);
161
162 static void
163 dissect_ipp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
164 {
165         proto_tree *ipp_tree;
166         proto_item *ti;
167         int offset = 0;
168         gboolean is_request = (pinfo->destport == 631);
169         guint16 status_code;
170         gchar *status_fmt;
171
172         CHECK_DISPLAY_AS_DATA(proto_ipp, tvb, pinfo, tree);
173
174         pinfo->current_proto = "IPP";
175
176         if (check_col(pinfo->fd, COL_PROTOCOL))
177                 col_set_str(pinfo->fd, COL_PROTOCOL, "IPP");
178         if (check_col(pinfo->fd, COL_INFO)) {
179                 if (is_request)
180                         col_set_str(pinfo->fd, COL_INFO, "IPP request");
181                 else
182                         col_set_str(pinfo->fd, COL_INFO, "IPP response");
183         }
184
185         if (tree) {
186                 ti = proto_tree_add_item(tree, proto_ipp, tvb, offset,
187                     tvb_length_remaining(tvb, offset), FALSE);
188                 ipp_tree = proto_item_add_subtree(ti, ett_ipp);
189
190                 proto_tree_add_text(ipp_tree, tvb, offset, 2, "Version: %u.%u",
191                     tvb_get_guint8(tvb, offset),
192                     tvb_get_guint8(tvb, offset + 1));
193                 offset += 2;
194
195                 if (is_request) {
196                         proto_tree_add_text(ipp_tree, tvb, offset, 2, "Operation-id: %s",
197                             val_to_str(tvb_get_ntohs(tvb, offset), operation_vals,
198                                 "Unknown (0x%04x)"));
199                 } else {
200                         status_code = tvb_get_ntohs(tvb, offset);
201                         switch (status_code & STATUS_TYPE_MASK) {
202
203                         case STATUS_SUCCESSFUL:
204                                 status_fmt = "Successful (0x%04x)";
205                                 break;
206
207                         case STATUS_INFORMATIONAL:
208                                 status_fmt = "Informational (0x%04x)";
209                                 break;
210
211                         case STATUS_REDIRECTION:
212                                 status_fmt = "Redirection (0x%04x)";
213                                 break;
214
215                         case STATUS_CLIENT_ERROR:
216                                 status_fmt = "Client error (0x%04x)";
217                                 break;
218
219                         case STATUS_SERVER_ERROR:
220                                 status_fmt = "Server error (0x%04x)";
221                                 break;
222
223                         default:
224                                 status_fmt = "Unknown (0x%04x)";
225                                 break;
226                         }
227                         proto_tree_add_text(ipp_tree, tvb, offset, 2, "Status-code: %s",
228                             val_to_str(status_code, status_vals, status_fmt));
229                 }
230                 offset += 2;
231
232                 proto_tree_add_text(ipp_tree, tvb, offset, 4, "Request ID: %u",
233                     tvb_get_ntohl(tvb, offset));
234                 offset += 4;
235
236                 offset = parse_attributes(tvb, offset, ipp_tree);
237
238                 if (tvb_offset_exists(tvb, offset))
239                         dissect_data(tvb, offset, pinfo, ipp_tree);
240         }
241 }
242
243 #define TAG_TYPE(tag)           ((tag) & 0xF0)
244 #define TAG_TYPE_DELIMITER      0x00
245 #define TAG_TYPE_INTEGER        0x20
246 #define TAG_TYPE_OCTETSTRING    0x30
247 #define TAG_TYPE_CHARSTRING     0x40
248
249 #define TAG_END_OF_ATTRIBUTES   0x03
250
251 #define TAG_INTEGER             0x21
252 #define TAG_BOOLEAN             0x22
253 #define TAG_ENUM                0x23
254
255 #define TAG_OCTETSTRING         0x30
256 #define TAG_DATETIME            0x31
257 #define TAG_RESOLUTION          0x32
258 #define TAG_RANGEOFINTEGER      0x33
259 #define TAG_TEXTWITHLANGUAGE    0x35
260 #define TAG_NAMEWITHLANGUAGE    0x36
261
262 #define TAG_TEXTWITHOUTLANGUAGE 0x41
263 #define TAG_NAMEWITHOUTLANGUAGE 0x42
264 #define TAG_KEYWORD             0x44
265 #define TAG_URI                 0x45
266 #define TAG_URISCHEME           0x46
267 #define TAG_CHARSET             0x47
268 #define TAG_NATURALLANGUAGE     0x48
269 #define TAG_MIMEMEDIATYPE       0x49
270
271 static const value_string tag_vals[] = {
272         /* Delimiter tags */
273         { 0x01,                    "Operation attributes" },
274         { 0x02,                    "Job attributes" },
275         { TAG_END_OF_ATTRIBUTES,   "End of attributes" },
276         { 0x04,                    "Printer attributes" },
277         { 0x05,                    "Unsupported attributes" },
278
279         /* Value tags */
280         { 0x10,                    "Unsupported" },
281         { 0x12,                    "Unknown" },
282         { 0x13,                    "No value" },
283         { TAG_INTEGER,             "Integer" },
284         { TAG_BOOLEAN,             "Boolean" },
285         { TAG_ENUM,                "Enum" },
286         { TAG_OCTETSTRING,         "Octet string" },
287         { TAG_DATETIME,            "Date/Time" },
288         { TAG_RESOLUTION,          "Resolution" },
289         { TAG_RANGEOFINTEGER,      "Range of integer" },
290         { TAG_TEXTWITHLANGUAGE,    "Text with language" },
291         { TAG_NAMEWITHLANGUAGE,    "Name with language" },
292         { TAG_TEXTWITHOUTLANGUAGE, "Text without language" },
293         { TAG_NAMEWITHOUTLANGUAGE, "Name without language" },
294         { TAG_KEYWORD,             "Keyword" },
295         { TAG_URI,                 "URI" },
296         { TAG_URISCHEME,           "URI scheme" },
297         { TAG_CHARSET,             "Character set" },
298         { TAG_NATURALLANGUAGE,     "Natural language" },
299         { TAG_MIMEMEDIATYPE,       "MIME media type" },
300         { 0,                       NULL }
301 };
302
303 static int
304 parse_attributes(tvbuff_t *tvb, int offset, proto_tree *tree)
305 {
306         guint8 tag;
307         gchar *tag_desc;
308         int name_length, value_length;
309         proto_tree *as_tree = tree;
310         proto_item *tas = NULL;
311         int start_offset = offset;
312         proto_tree *attr_tree = tree;
313
314         while (tvb_offset_exists(tvb, offset)) {
315                 tag = tvb_get_guint8(tvb, offset);
316                 tag_desc = val_to_str(tag, tag_vals, "Reserved (0x%02x)");
317                 if (TAG_TYPE(tag) == TAG_TYPE_DELIMITER) {
318                         /*
319                          * If we had an attribute sequence we were
320                          * working on, we're done with it; set its
321                          * length to the length of all the stuff
322                          * we've done so far.
323                          */
324                         if (tas != NULL)
325                                 proto_item_set_len(tas, offset - start_offset);
326
327                         /*
328                          * This tag starts a new attribute sequence;
329                          * create a new tree under this tag when we see
330                          * a non-delimiter tag, under which to put
331                          * those attributes.
332                          */
333                         as_tree = NULL;
334                         attr_tree = tree;
335
336                         /*
337                          * Remember the offset at which this attribute
338                          * sequence started, so we can use it to compute
339                          * its length when it's finished.
340                          */
341                         start_offset = offset;
342
343                         /*
344                          * Now create a new item for this tag.
345                          */
346                         tas = proto_tree_add_text(tree, tvb, offset, 1,
347                             "%s", tag_desc);
348                         offset++;
349                         if (tag == TAG_END_OF_ATTRIBUTES) {
350                                 /*
351                                  * No more attributes.
352                                  */
353                                 break;
354                         }
355                 } else {
356                         /*
357                          * Value tag - get the name length.
358                          */
359                         name_length = tvb_get_ntohs(tvb, offset + 1);
360
361                         /*
362                          * OK, get the value length.
363                          */
364                         value_length = tvb_get_ntohs(tvb, offset + 1 + 2 + name_length);
365
366                         /*
367                          * OK, does the value run past the end of the
368                          * frame?
369                          */
370                         if (as_tree == NULL) {
371                                 /*
372                                  * OK, there's an attribute to hang
373                                  * under a delimiter tag, but we don't
374                                  * have a tree for that tag yet; create
375                                  * a tree.
376                                  */
377                                 as_tree = proto_item_add_subtree(tas,
378                                     ett_ipp_as);
379                                 attr_tree = as_tree;
380                         }
381
382                         switch (TAG_TYPE(tag)) {
383
384                         case TAG_TYPE_INTEGER:
385                                 if (name_length != 0) {
386                                         /*
387                                          * This is an attribute, not
388                                          * an additional value, so
389                                          * start a tree for it.
390                                          */
391                                         attr_tree = add_integer_tree(as_tree,
392                                             tvb, offset, name_length,
393                                             value_length);
394                                 }
395                                 add_integer_value(tag, tag_desc, attr_tree, tvb,
396                                     offset, name_length, value_length);
397                                 break;
398
399                         case TAG_TYPE_OCTETSTRING:
400                                 if (name_length != 0) {
401                                         /*
402                                          * This is an attribute, not
403                                          * an additional value, so
404                                          * start a tree for it.
405                                          */
406                                         attr_tree = add_octetstring_tree(as_tree,
407                                             tvb, offset, name_length,
408                                             value_length);
409                                 }
410                                 add_octetstring_value(tag, tag_desc,
411                                     attr_tree, tvb, offset, name_length,
412                                     value_length);
413                                 break;
414
415                         case TAG_TYPE_CHARSTRING:
416                                 if (name_length != 0) {
417                                         /*
418                                          * This is an attribute, not
419                                          * an additional value, so
420                                          * start a tree for it.
421                                          */
422                                         attr_tree = add_charstring_tree(as_tree,
423                                             tvb, offset, name_length,
424                                             value_length);
425                                 }
426                                 add_charstring_value(tag, tag_desc,
427                                     attr_tree, tvb, offset, name_length,
428                                     value_length);
429                                 break;
430                         }
431                         offset += 1 + 2 + name_length + 2 + value_length;
432                 }
433         }
434
435         return offset;
436 }
437
438 static proto_tree *
439 add_integer_tree(proto_tree *tree, tvbuff_t *tvb, int offset,
440     int name_length, int value_length)
441 {
442         proto_item *ti;
443
444         if (value_length != 4) {
445                 ti = proto_tree_add_text(tree, tvb, offset,
446                     1 + 2 + name_length + 2 + value_length,
447                     "%.*s: Invalid integer (length is %u, should be 4)",
448                     name_length,
449                     tvb_get_ptr(tvb, offset + 1 + 2, name_length),
450                     value_length);
451         } else {
452                 ti = proto_tree_add_text(tree, tvb, offset,
453                     1 + 2 + name_length + 2 + value_length,
454                     "%.*s: %u",
455                     name_length,
456                     tvb_get_ptr(tvb, offset + 1 + 2, name_length),
457                     tvb_get_ntohl(tvb, offset + 1 + 2 + name_length + 2));
458         }
459         return proto_item_add_subtree(ti, ett_ipp_attr);
460 }
461
462 static void
463 add_integer_value(guint tag, gchar *tag_desc, proto_tree *tree,
464     tvbuff_t *tvb, int offset, int name_length, int value_length)
465 {
466         offset = add_value_head(tag, tag_desc, tree, tvb, offset,
467             name_length, value_length);
468         if (value_length == 4) {
469                 proto_tree_add_text(tree, tvb, offset, value_length,
470                     "Value: %u", tvb_get_ntohl(tvb, offset));
471         }
472 }
473
474 static proto_tree *
475 add_octetstring_tree(proto_tree *tree, tvbuff_t *tvb, int offset,
476     int name_length, int value_length)
477 {
478         proto_item *ti;
479
480         ti = proto_tree_add_text(tree, tvb, offset,
481             1 + 2 + name_length + 2 + value_length,
482             "%.*s: %s",
483             name_length,
484             tvb_get_ptr(tvb, offset + 1 + 2, name_length),
485             tvb_bytes_to_str(tvb, offset + 1 + 2 + name_length + 2, value_length));
486         return proto_item_add_subtree(ti, ett_ipp_attr);
487 }
488
489 static void
490 add_octetstring_value(guint tag, gchar *tag_desc, proto_tree *tree,
491     tvbuff_t *tvb, int offset, int name_length, int value_length)
492 {
493         offset = add_value_head(tag, tag_desc, tree, tvb, offset,
494             name_length, value_length);
495         proto_tree_add_text(tree, tvb, offset, value_length,
496             "Value: %s", tvb_bytes_to_str(tvb, offset, value_length));
497 }
498
499 static proto_tree *
500 add_charstring_tree(proto_tree *tree, tvbuff_t *tvb, int offset,
501     int name_length, int value_length)
502 {
503         proto_item *ti;
504
505         ti = proto_tree_add_text(tree, tvb, offset,
506             1 + 2 + name_length + 2 + value_length,
507             "%.*s: %.*s",
508             name_length,
509             tvb_get_ptr(tvb, offset + 1 + 2, name_length),
510             value_length,
511             tvb_get_ptr(tvb, offset + 1 + 2 + name_length + 2, value_length));
512         return proto_item_add_subtree(ti, ett_ipp_attr);
513 }
514
515 static void
516 add_charstring_value(guint tag, gchar *tag_desc, proto_tree *tree,
517     tvbuff_t *tvb, int offset, int name_length, int value_length)
518 {
519         offset = add_value_head(tag, tag_desc, tree, tvb, offset,
520             name_length, value_length);
521         proto_tree_add_text(tree, tvb, offset, value_length,
522             "Value: %.*s", value_length, tvb_get_ptr(tvb, offset, value_length));
523 }
524
525 static int
526 add_value_head(guint tag, gchar *tag_desc, proto_tree *tree,
527     tvbuff_t *tvb, int offset, int name_length, int value_length)
528 {
529         proto_tree_add_text(tree, tvb, offset, 1, "Tag: %s", tag_desc);
530         offset += 1;
531         proto_tree_add_text(tree, tvb, offset, 2, "Name length: %u",
532             name_length);
533         offset += 2;
534         if (name_length != 0) {
535                 proto_tree_add_text(tree, tvb, offset, name_length,
536                     "Name: %.*s", name_length,
537                     tvb_get_ptr(tvb, offset, name_length));
538         }
539         offset += name_length;
540         proto_tree_add_text(tree, tvb, offset, 2, "Value length: %u",
541             value_length);
542         offset += 2;
543         return offset;
544 }
545
546 void
547 proto_register_ipp(void)
548 {
549 /*        static hf_register_info hf[] = {
550                 { &variable,
551                 { "Name",           "ipp.abbreviation", TYPE, VALS_POINTER }},
552         };*/
553         static gint *ett[] = {
554                 &ett_ipp,
555                 &ett_ipp_as,
556                 &ett_ipp_attr,
557         };
558
559         proto_ipp = proto_register_protocol("Internet Printing Protocol",
560             "IPP", "ipp");
561  /*       proto_register_field_array(proto_ipp, hf, array_length(hf));*/
562         proto_register_subtree_array(ett, array_length(ett));
563 }
564
565 void
566 proto_reg_handoff_ipp(void)
567 {
568         /*
569          * Register ourselves as running atop HTTP and using port 631.
570          */
571         http_dissector_add(631, dissect_ipp, proto_ipp);
572 }