epan/dissectors/pidl/samr/samr.cnf cnf_dissect_lsa_BinaryString => lsarpc_dissect_str...
[wireshark-sm.git] / epan / dissectors / packet-lsdp.c
blobd3c2976415ba93ef0265eae8011c0d77ddabf077
1 /* packet-lsdp.c
2 * Dissector for Lenbrook Service Discovery Protocol
4 * Copyright (c) 2024 by Martin Mayer <martin.mayer@m2-it-solutions.de>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include "config.h"
14 #include <epan/packet.h>
15 #include <epan/expert.h>
16 #include <wsutil/str_util.h>
18 #define LSDP_UDP_PORT 11430
19 #define LSDP_HEADER_LEN 6
20 #define LSDP_HEADER_VER 1
21 #define LSDP_MAGIC "LSDP"
23 void proto_register_lsdp(void);
24 void proto_reg_handoff_lsdp(void);
26 static dissector_handle_t lsdp_handle;
28 static int proto_lsdp;
30 /* Header Fields */
31 static int hf_lsdp_header_length;
32 static int hf_lsdp_header_magic_word;
33 static int hf_lsdp_header_proto_version;
35 /* Common Message Fields */
36 static int hf_lsdp_msg_length;
37 static int hf_lsdp_msg_type;
38 static int hf_lsdp_node_id_length;
39 static int hf_lsdp_node_id_mac;
40 static int hf_lsdp_node_id;
42 /* Query Message */
43 static int hf_lsdp_query;
44 static int hf_lsdp_query_count;
45 static int hf_lsdp_query_class;
47 /* Announce Message */
48 static int hf_lsdp_announce;
49 static int hf_lsdp_announce_addr_length;
50 static int hf_lsdp_announce_addr_ipv4;
51 static int hf_lsdp_announce_addr_ipv6;
52 static int hf_lsdp_announce_count;
54 /* Announce Message Records */
55 static int hf_lsdp_announce_record;
56 static int hf_lsdp_announce_record_class;
57 static int hf_lsdp_announce_record_count;
59 /* Announce Message Record TXT-Records */
60 static int hf_lsdp_announce_record_txt;
61 static int hf_lsdp_announce_record_txt_key_length;
62 static int hf_lsdp_announce_record_txt_key;
63 static int hf_lsdp_announce_record_txt_value_length;
64 static int hf_lsdp_announce_record_txt_value;
66 /* Delete Message */
67 static int hf_lsdp_delete;
68 static int hf_lsdp_delete_count;
69 static int hf_lsdp_delete_class;
71 /* Trees */
72 static int ett_lsdp;
73 static int ett_lsdp_node_id;
74 static int ett_lsdp_msg;
75 static int ett_lsdp_msg_rec;
76 static int ett_lsdp_msg_rec_txt;
78 /* Expert fields */
79 static expert_field ei_lsdp_unknown_msg_type;
80 static expert_field ei_lsdp_invalid_addr_len;
82 #define CLASS_PLAYER 0x0001
83 #define CLASS_SERVER 0x0002
84 #define CLASS_PLAYER_MZ 0x0003
85 #define CLASS_SOVI_MFG 0x0004
86 #define CLASS_SOVI_KEYPAD 0x0005
87 #define CLASS_PLAYER_SLAVE 0x0006
88 #define CLASS_REMOTE_APP 0x0007
89 #define CLASS_HUB 0x0008
90 #define CLASS_ALL 0xFFFF
92 static const value_string lsdp_class_id_vals[] = {
93 { CLASS_PLAYER, "BluOS Player" },
94 { CLASS_SERVER, "BluOS Server" },
95 { CLASS_PLAYER_MZ, "BluOS Player (secondary in multi-zone)" },
96 { CLASS_SOVI_MFG, "sovi-mfg (used for manufacturing testing)" },
97 { CLASS_SOVI_KEYPAD, "sovi-keypad" },
98 { CLASS_PLAYER_SLAVE, "BluOS Player (pair slave)" },
99 { CLASS_REMOTE_APP, "Remote Web App (AVR OSD Web Page)" },
100 { CLASS_HUB, "BluOS Hub" },
101 { CLASS_ALL, "All Classes (Query Message)" },
102 { 0, NULL }
105 static const value_string lsdp_class_id_short_vals[] = {
106 { CLASS_PLAYER, "Player" },
107 { CLASS_SERVER, "Server" },
108 { CLASS_PLAYER_MZ, "Multizone Player" },
109 { CLASS_SOVI_MFG, "sovi-mfg" },
110 { CLASS_SOVI_KEYPAD, "sovi-keypad" },
111 { CLASS_PLAYER_SLAVE, "Slave Player" },
112 { CLASS_REMOTE_APP, "Web App" },
113 { CLASS_HUB, "Hub" },
114 { CLASS_ALL, "All Classes" },
115 { 0, NULL }
118 #define MSG_TYPE_ANNOUNCE 0x41 // Chr. 'A'
119 #define MSG_TYPE_DELETE 0x44 // Chr. 'D'
120 #define MSG_TYPE_QUERY_BCR 0x51 // Chr. 'Q'
121 #define MSG_TYPE_QUERY_UCR 0x52 // Chr. 'R'
123 static const value_string lsdp_msg_type_vals[] = {
124 { MSG_TYPE_ANNOUNCE, "Announce Message" },
125 { MSG_TYPE_DELETE, "Delete Message" },
126 { MSG_TYPE_QUERY_BCR, "Query Message (for broadcast response)" },
127 { MSG_TYPE_QUERY_UCR, "Query Message (for unicast response)" },
128 { 0, NULL }
131 static int
132 dissect_lsdp_node_info(tvbuff_t *tvb, proto_tree *tree, int offset)
134 uint8_t node_id_len;
135 int start_offset = offset;
136 proto_item *pi_id;
137 proto_tree *id_tree;
139 node_id_len = tvb_get_uint8(tvb, offset);
140 proto_tree_add_item(tree, hf_lsdp_node_id_length, tvb, offset, 1, ENC_BIG_ENDIAN);
141 offset += 1;
144 Normally, Node-ID is the MAC address of an interface, but COULD also be an arbitrary ID.
145 Populate Node-ID field and also MAC if length is 6.
147 pi_id = proto_tree_add_item(tree, hf_lsdp_node_id, tvb, offset, node_id_len, ENC_NA);
148 if(node_id_len == 6) {
149 id_tree = proto_item_add_subtree(pi_id, ett_lsdp_node_id);
150 proto_tree_add_item(id_tree, hf_lsdp_node_id_mac, tvb, offset, node_id_len, ENC_NA);
152 offset += node_id_len;
154 return offset - start_offset;
157 static int
158 dissect_lsdp_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset)
160 uint8_t msg_type, msg_len;
161 uint32_t addr_len, count, txt_count, k_len, v_len, class;
162 int offset_s1, offset_s2;
163 proto_item *msg_item, *rec_item, *txt_item;
164 proto_tree *msg_tree, *rec_tree, *txt_tree;
165 const uint8_t *key, *val;
167 msg_len = tvb_get_uint8(tvb, offset);
168 msg_type = tvb_get_uint8(tvb, offset+1);
170 col_append_str(pinfo->cinfo, COL_INFO, val_to_str_const(msg_type, lsdp_msg_type_vals, "Unknown Message Type"));
172 switch (msg_type) {
173 case MSG_TYPE_QUERY_BCR:
174 case MSG_TYPE_QUERY_UCR:
176 msg_item = proto_tree_add_item(tree, hf_lsdp_query, tvb, offset, msg_len, ENC_NA);
177 msg_tree = proto_item_add_subtree(msg_item, ett_lsdp_msg);
179 proto_tree_add_item(msg_tree, hf_lsdp_msg_length, tvb, offset, 1, ENC_BIG_ENDIAN);
180 offset += 1;
182 proto_tree_add_item(msg_tree, hf_lsdp_msg_type, tvb, offset, 1, ENC_BIG_ENDIAN);
183 offset += 1;
185 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_query_count, tvb, offset, 1, ENC_BIG_ENDIAN, &count);
186 offset += 1;
188 for(uint32_t i=0; i < count; i++) {
189 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_query_class, tvb, offset, 2, ENC_BIG_ENDIAN, &class);
190 col_append_fstr(pinfo->cinfo, COL_INFO, " [%s]", val_to_str_const(class, lsdp_class_id_short_vals, "Unknown Class"));
191 offset += 2;
194 break;
196 case MSG_TYPE_ANNOUNCE:
198 msg_item = proto_tree_add_item(tree, hf_lsdp_announce, tvb, offset, msg_len, ENC_NA);
199 msg_tree = proto_item_add_subtree(msg_item, ett_lsdp_msg);
201 proto_tree_add_item(msg_tree, hf_lsdp_msg_length, tvb, offset, 1, ENC_BIG_ENDIAN);
202 offset += 1;
204 proto_tree_add_item(msg_tree, hf_lsdp_msg_type, tvb, offset, 1, ENC_BIG_ENDIAN);
205 offset += 1;
207 offset += dissect_lsdp_node_info(tvb, msg_tree, offset);
209 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_announce_addr_length, tvb, offset, 1, ENC_BIG_ENDIAN, &addr_len);
210 offset += 1;
212 if(addr_len == 4) {
213 proto_tree_add_item(msg_tree, hf_lsdp_announce_addr_ipv4, tvb, offset, addr_len, ENC_NA);
214 } else if (addr_len==16) {
215 proto_tree_add_item(msg_tree, hf_lsdp_announce_addr_ipv6, tvb, offset, addr_len, ENC_NA);
216 } else {
217 expert_add_info(pinfo, msg_tree, &ei_lsdp_invalid_addr_len);
219 offset += addr_len;
221 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_announce_count, tvb, offset, 1, ENC_BIG_ENDIAN, &count);
222 proto_item_append_text(msg_item, " (%d Record%s)", count, plurality(count, "", "s"));
223 offset += 1;
225 /* Loop Announce Records */
226 for(uint32_t i=0; i < count; i++) {
228 offset_s1 = offset;
230 rec_item = proto_tree_add_item(msg_tree, hf_lsdp_announce_record, tvb, offset, 0, ENC_NA);
231 rec_tree = proto_item_add_subtree(rec_item, ett_lsdp_msg_rec);
233 proto_tree_add_item_ret_uint(rec_tree, hf_lsdp_announce_record_class, tvb, offset, 2, ENC_BIG_ENDIAN, &class);
234 offset += 2;
236 col_append_fstr(pinfo->cinfo, COL_INFO, " [%s]", val_to_str_const(class, lsdp_class_id_short_vals, "Unknown Class"));
238 proto_tree_add_item_ret_uint(rec_tree, hf_lsdp_announce_record_count, tvb, offset, 1, ENC_BIG_ENDIAN, &txt_count);
239 proto_item_append_text(rec_item, " (%d TXT-Record%s)", txt_count, plurality(txt_count, "", "s"));
240 offset += 1;
242 /* Loop TXT records (key-value pairs) */
243 for(uint32_t j=0; j < txt_count; j++) {
245 offset_s2 = offset;
247 txt_item = proto_tree_add_item(rec_tree, hf_lsdp_announce_record_txt, tvb, offset, 0, ENC_NA);
248 txt_tree = proto_item_add_subtree(txt_item, ett_lsdp_msg_rec_txt);
250 proto_tree_add_item_ret_uint(txt_tree, hf_lsdp_announce_record_txt_key_length, tvb, offset, 1, ENC_BIG_ENDIAN, &k_len);
251 offset += 1;
253 proto_tree_add_item_ret_string(txt_tree, hf_lsdp_announce_record_txt_key, tvb, offset, k_len, ENC_UTF_8, pinfo->pool, &key);
254 offset += k_len;
256 proto_tree_add_item_ret_uint(txt_tree, hf_lsdp_announce_record_txt_value_length, tvb, offset, 1, ENC_BIG_ENDIAN, &v_len);
257 offset += 1;
259 proto_tree_add_item_ret_string(txt_tree, hf_lsdp_announce_record_txt_value, tvb, offset, v_len, ENC_UTF_8, pinfo->pool, &val);
260 offset += v_len;
262 proto_item_append_text(txt_item, " (%s: %s)", key, val);
263 proto_item_set_len(txt_item, offset - offset_s2);
265 /* Keys of interest for info column */
267 strcmp(key, "name") == 0 ||
268 strcmp(key, "model") == 0 ||
269 strcmp(key, "version") == 0
271 col_append_fstr(pinfo->cinfo, COL_INFO, " %s='%s'", key, val);
275 proto_item_set_len(rec_item, offset - offset_s1);
278 break;
280 case MSG_TYPE_DELETE:
282 msg_item = proto_tree_add_item(tree, hf_lsdp_delete, tvb, offset, msg_len, ENC_NA);
283 msg_tree = proto_item_add_subtree(msg_item, ett_lsdp_msg);
285 proto_tree_add_item(msg_tree, hf_lsdp_msg_length, tvb, offset, 1, ENC_BIG_ENDIAN);
286 offset += 1;
288 proto_tree_add_item(msg_tree, hf_lsdp_msg_type, tvb, offset, 1, ENC_BIG_ENDIAN);
289 offset += 1;
291 offset += dissect_lsdp_node_info(tvb, msg_tree, offset);
293 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_delete_count, tvb, offset, 1, ENC_BIG_ENDIAN, &count);
294 offset += 1;
296 for(uint32_t i=0; i < count; i++) {
297 proto_tree_add_item_ret_uint(msg_tree, hf_lsdp_delete_class, tvb, offset, 2, ENC_BIG_ENDIAN, &class);
298 col_append_fstr(pinfo->cinfo, COL_INFO, " [%s]", val_to_str_const(class, lsdp_class_id_short_vals, "Unknown Class"));
299 offset += 2;
302 break;
304 default:
305 expert_add_info(pinfo, tree, &ei_lsdp_unknown_msg_type);
306 break;
309 return msg_len;
312 static int
313 dissect_lsdp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
315 int offset;
316 proto_item *ti;
317 proto_tree *lsdp_tree;
319 /* Basic tests for LSDP */
321 tvb_reported_length(tvb) < LSDP_HEADER_LEN || // Header must be available
322 tvb_get_uint8(tvb, 0) != LSDP_HEADER_LEN || // Header length must be fixed
323 tvb_memeql(tvb, 1, LSDP_MAGIC, strlen(LSDP_MAGIC)) != 0 || // Magic Word must match
324 tvb_get_uint8(tvb, 5) != LSDP_HEADER_VER // We only support version 1
326 return 0;
328 col_set_str(pinfo->cinfo, COL_PROTOCOL, "LSDP");
329 col_clear(pinfo->cinfo,COL_INFO);
331 ti = proto_tree_add_item(tree, proto_lsdp, tvb, 0, 0, ENC_NA);
332 lsdp_tree = proto_item_add_subtree(ti, ett_lsdp);
334 offset = 0;
336 proto_tree_add_item(lsdp_tree, hf_lsdp_header_length, tvb, offset, 1, ENC_BIG_ENDIAN);
337 offset += 1;
339 proto_tree_add_item(lsdp_tree, hf_lsdp_header_magic_word, tvb, offset, 4, ENC_ASCII);
340 offset += 4;
342 proto_tree_add_item(lsdp_tree, hf_lsdp_header_proto_version, tvb, offset, 1, ENC_BIG_ENDIAN);
343 offset += 1;
345 /* One packet can contain multiple messages */
346 while((unsigned)offset < tvb_reported_length(tvb)) {
349 Ensure there are enough bytes remaining for another message
350 - at least 2 bytes (length, type) must be available
351 - length must not be zero - ensure offset to advance
354 tvb_reported_length_remaining(tvb, offset) < 2 ||
355 tvb_get_uint8(tvb, offset) == 0
357 break;
359 offset += dissect_lsdp_message(tvb, pinfo, lsdp_tree, offset);
363 proto_item_set_len(ti, offset);
364 return offset;
367 void
368 proto_register_lsdp(void)
370 static hf_register_info hf[] = {
371 { &hf_lsdp_header_length,
372 { "Header Length", "lsdp.header.length",
373 FT_UINT8, BASE_DEC,
374 NULL, 0x0,
375 NULL, HFILL }
377 { &hf_lsdp_header_magic_word,
378 { "Magic Word", "lsdp.header.magic_word",
379 FT_STRING, BASE_NONE,
380 NULL, 0x0,
381 NULL, HFILL }
383 { &hf_lsdp_header_proto_version,
384 { "Protocol Version", "lsdp.header.proto_version",
385 FT_UINT8, BASE_DEC,
386 NULL, 0x0,
387 NULL, HFILL }
389 { &hf_lsdp_msg_length,
390 { "Message Length", "lsdp.msg.length",
391 FT_UINT8, BASE_DEC,
392 NULL, 0x0,
393 NULL, HFILL }
395 { &hf_lsdp_msg_type,
396 { "Message Type", "lsdp.msg.type",
397 FT_UINT8, BASE_HEX,
398 VALS(lsdp_msg_type_vals), 0x0,
399 NULL, HFILL }
401 { &hf_lsdp_node_id_length,
402 { "Node ID Length", "lsdp.node_id.length",
403 FT_UINT8, BASE_DEC,
404 NULL, 0x0,
405 NULL, HFILL }
407 { &hf_lsdp_node_id_mac,
408 { "Node ID (MAC)", "lsdp.node_id.mac",
409 FT_ETHER, BASE_NONE,
410 NULL, 0x0,
411 NULL, HFILL }
413 { &hf_lsdp_node_id,
414 { "Node ID", "lsdp.node_id",
415 FT_BYTES, BASE_NONE,
416 NULL, 0x0,
417 NULL, HFILL }
419 { &hf_lsdp_query,
420 { "Query Message", "lsdp.query",
421 FT_NONE, BASE_NONE,
422 NULL, 0x0,
423 NULL, HFILL }
425 { &hf_lsdp_query_count,
426 { "Count", "lsdp.query.count",
427 FT_UINT8, BASE_DEC,
428 NULL, 0x0,
429 NULL, HFILL }
431 { &hf_lsdp_query_class,
432 { "Class", "lsdp.query.class",
433 FT_UINT16, BASE_HEX,
434 VALS(lsdp_class_id_vals), 0x0,
435 NULL, HFILL }
437 { &hf_lsdp_announce,
438 { "Announce Message", "lsdp.announce",
439 FT_NONE, BASE_NONE,
440 NULL, 0x0,
441 NULL, HFILL }
443 { &hf_lsdp_announce_addr_length,
444 { "Address Length", "lsdp.announce.addr.length",
445 FT_UINT8, BASE_DEC,
446 NULL, 0x0,
447 NULL, HFILL }
449 { &hf_lsdp_announce_addr_ipv4,
450 { "Address", "lsdp.announce.addr_ipv4",
451 FT_IPv4, BASE_NONE,
452 NULL, 0x0,
453 NULL, HFILL }
455 { &hf_lsdp_announce_addr_ipv6,
456 { "Address", "lsdp.announce.addr_ipv6",
457 FT_IPv6, BASE_NONE,
458 NULL, 0x0,
459 NULL, HFILL }
461 { &hf_lsdp_announce_count,
462 { "Count", "lsdp.announce.count",
463 FT_UINT8, BASE_DEC,
464 NULL, 0x0,
465 NULL, HFILL }
467 { &hf_lsdp_announce_record,
468 { "Announce Record", "lsdp.announce.record",
469 FT_NONE, BASE_NONE,
470 NULL, 0x0,
471 NULL, HFILL }
473 { &hf_lsdp_announce_record_class,
474 { "Class", "lsdp.announce.record.class",
475 FT_UINT16, BASE_HEX,
476 VALS(lsdp_class_id_vals), 0x0,
477 NULL, HFILL }
479 { &hf_lsdp_announce_record_count,
480 { "Count", "lsdp.announce.record.count",
481 FT_UINT8, BASE_DEC,
482 NULL, 0x0,
483 NULL, HFILL }
485 { &hf_lsdp_announce_record_txt,
486 { "TXT-Record", "lsdp.announce.record.txt",
487 FT_NONE, BASE_NONE,
488 NULL, 0x0,
489 NULL, HFILL }
491 { &hf_lsdp_announce_record_txt_key_length,
492 { "Key Length", "lsdp.announce.record.txt.key.length",
493 FT_UINT8, BASE_DEC,
494 NULL, 0x0,
495 NULL, HFILL }
497 { &hf_lsdp_announce_record_txt_key,
498 { "Key", "lsdp.announce.record.txt.key",
499 FT_STRING, BASE_NONE,
500 NULL, 0x0,
501 NULL, HFILL }
503 { &hf_lsdp_announce_record_txt_value_length,
504 { "Value Length", "lsdp.announce.record.txt.val.length",
505 FT_UINT8, BASE_DEC,
506 NULL, 0x0,
507 NULL, HFILL }
509 { &hf_lsdp_announce_record_txt_value,
510 { "Key", "lsdp.announce.record.txt.val",
511 FT_STRING, BASE_NONE,
512 NULL, 0x0,
513 NULL, HFILL }
515 { &hf_lsdp_delete,
516 { "Delete Message", "lsdp.delete",
517 FT_NONE, BASE_NONE,
518 NULL, 0x0,
519 NULL, HFILL }
521 { &hf_lsdp_delete_count,
522 { "Count", "lsdp.delete.count",
523 FT_UINT8, BASE_DEC,
524 NULL, 0x0,
525 NULL, HFILL }
527 { &hf_lsdp_delete_class,
528 { "Class", "lsdp.delete.class",
529 FT_UINT16, BASE_HEX,
530 VALS(lsdp_class_id_vals), 0x0,
531 NULL, HFILL }
535 static ei_register_info ei[] = {
536 { &ei_lsdp_unknown_msg_type,
537 { "lsdp.unknown_msg_type", PI_MALFORMED, PI_ERROR,
538 "Message is of invalid type", EXPFILL }
540 { &ei_lsdp_invalid_addr_len,
541 { "lsdp.invalid_addr_len", PI_MALFORMED, PI_ERROR,
542 "Address has an invalid length", EXPFILL }
546 static int *ett[] = {
547 &ett_lsdp,
548 &ett_lsdp_node_id,
549 &ett_lsdp_msg,
550 &ett_lsdp_msg_rec,
551 &ett_lsdp_msg_rec_txt
554 expert_module_t* expert_lsdp;
556 proto_lsdp = proto_register_protocol ("Lenbrook Service Discovery Protocol", "LSDP", "lsdp");
558 proto_register_field_array(proto_lsdp, hf, array_length(hf));
559 proto_register_subtree_array(ett, array_length(ett));
561 expert_lsdp = expert_register_protocol(proto_lsdp);
562 expert_register_field_array(expert_lsdp, ei, array_length(ei));
564 lsdp_handle = register_dissector("lsdp", dissect_lsdp, proto_lsdp);
567 void
568 proto_reg_handoff_lsdp(void)
570 dissector_add_uint("udp.port", LSDP_UDP_PORT, lsdp_handle);
574 * Editor modelines - https://www.wireshark.org/tools/modelines.html
576 * Local variables:
577 * c-basic-offset: 4
578 * tab-width: 8
579 * indent-tabs-mode: nil
580 * End:
582 * vi: set shiftwidth=4 tabstop=8 expandtab:
583 * :indentSize=4:tabSize=8:noTabs=true: