2 * Routines for BACnet MS/TP datalink dissection
3 * Copyright 2008 Steve Karg <skarg@users.sourceforge.net> Alabama
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
35 #include <epan/packet.h>
37 #include <epan/llcsaps.h>
38 #include <epan/expert.h>
39 #include "packet-llc.h"
40 #include "packet-mstp.h"
42 /* Probably should be a preference, but here for now */
43 #define BACNET_MSTP_SUMMARY_IN_TREE
44 #define BACNET_MSTP_CHECKSUM_VALIDATE
46 /* MS/TP Frame Type */
47 /* Frame Types 8 through 127 are reserved by ASHRAE. */
49 #define MSTP_POLL_FOR_MASTER 1
50 #define MSTP_REPLY_TO_POLL_FOR_MASTER 2
51 #define MSTP_TEST_REQUEST 3
52 #define MSTP_TEST_RESPONSE 4
53 #define MSTP_BACNET_DATA_EXPECTING_REPLY 5
54 #define MSTP_BACNET_DATA_NOT_EXPECTING_REPLY 6
55 #define MSTP_REPLY_POSTPONED 7
57 static const value_string
58 bacnet_mstp_frame_type_name[] = {
59 {MSTP_TOKEN, "Token"},
60 {MSTP_POLL_FOR_MASTER, "Poll For Master"},
61 {MSTP_REPLY_TO_POLL_FOR_MASTER, "Reply To Poll For Master"},
62 {MSTP_TEST_REQUEST, "Test_Request"},
63 {MSTP_TEST_RESPONSE, "Test_Response"},
64 {MSTP_BACNET_DATA_EXPECTING_REPLY, "BACnet Data Expecting Reply"},
65 {MSTP_BACNET_DATA_NOT_EXPECTING_REPLY, "BACnet Data Not Expecting Reply"},
66 {MSTP_REPLY_POSTPONED, "Reply Postponed"},
67 /* Frame Types 128 through 255: Proprietary Frames */
71 static dissector_handle_t data_handle;
72 static dissector_table_t subdissector_table;
74 static int proto_mstp = -1;
76 static gint ett_bacnet_mstp = -1;
77 static gint ett_bacnet_mstp_checksum = -1;
79 static int hf_mstp_preamble_55 = -1;
80 static int hf_mstp_preamble_FF = -1;
81 static int hf_mstp_frame_type = -1;
82 static int hf_mstp_frame_destination = -1;
83 static int hf_mstp_frame_source = -1;
84 static int hf_mstp_frame_vendor_id = -1;
85 static int hf_mstp_frame_pdu_len = -1;
86 static int hf_mstp_frame_crc8 = -1;
87 static int hf_mstp_frame_crc16 = -1;
88 static int hf_mstp_frame_checksum_bad = -1;
89 static int hf_mstp_frame_checksum_good = -1;
91 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
92 /* Accumulate "dataValue" into the CRC in crcValue. */
93 /* Return value is updated CRC */
94 /* The ^ operator means exclusive OR. */
95 /* Note: This function is copied directly from the BACnet standard. */
96 static guint8 CRC_Calc_Header(
102 crc = crcValue ^ dataValue; /* XOR C7..C0 with D7..D0 */
104 /* Exclusive OR the terms in the table (top down) */
105 crc = crc ^ (crc << 1) ^ (crc << 2) ^ (crc << 3)
106 ^ (crc << 4) ^ (crc << 5) ^ (crc << 6)
109 /* Combine bits shifted out left hand end */
110 return (crc & 0xfe) ^ ((crc >> 8) & 1);
114 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
115 /* Accumulate "dataValue" into the CRC in crcValue. */
116 /* Return value is updated CRC */
117 /* The ^ operator means exclusive OR. */
118 /* Note: This function is copied directly from the BACnet standard. */
119 static guint16 CRC_Calc_Data(
125 crcLow = (crcValue & 0xff) ^ dataValue; /* XOR C7..C0 with D7..D0 */
127 /* Exclusive OR the terms in the table (top down) */
128 return (crcValue >> 8) ^ (crcLow << 8) ^ (crcLow << 3)
129 ^ (crcLow << 12) ^ (crcLow >> 4)
130 ^ (crcLow & 0x0f) ^ ((crcLow & 0x0f) << 7);
134 /* Common frame type text */
135 const gchar *mstp_frame_type_text(guint32 val)
137 return val_to_str(val,
138 bacnet_mstp_frame_type_name,
139 "Unknown Frame Type (%u)");
142 /* dissects a BACnet MS/TP frame */
143 /* preamble 0x55 0xFF is not included in Cimetrics U+4 output */
145 dissect_mstp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
146 proto_tree *subtree, gint offset)
148 guint8 mstp_frame_type = 0;
149 guint8 mstp_frame_source = 0;
150 guint8 mstp_frame_destination = 0;
151 guint16 mstp_frame_pdu_len = 0;
152 guint16 mstp_tvb_pdu_len = 0;
153 guint16 vendorid = 0;
154 tvbuff_t *next_tvb = NULL;
156 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
157 /* used to calculate the crc value */
158 guint8 crc8 = 0xFF, framecrc8;
159 guint16 crc16 = 0xFFFF, framecrc16;
161 guint16 i; /* loop counter */
163 proto_tree *checksum_tree;
166 col_set_str(pinfo->cinfo, COL_PROTOCOL, "BACnet");
167 col_set_str(pinfo->cinfo, COL_INFO, "BACnet MS/TP");
168 mstp_frame_type = tvb_get_guint8(tvb, offset);
169 mstp_frame_destination = tvb_get_guint8(tvb, offset+1);
170 mstp_frame_source = tvb_get_guint8(tvb, offset+2);
171 mstp_frame_pdu_len = tvb_get_ntohs(tvb, offset+3);
172 if (check_col(pinfo->cinfo, COL_INFO)) {
173 col_append_fstr(pinfo->cinfo, COL_INFO, " %s",
174 mstp_frame_type_text(mstp_frame_type));
176 /* Add the items to the tree */
177 proto_tree_add_item(subtree, hf_mstp_frame_type, tvb,
179 proto_tree_add_item(subtree, hf_mstp_frame_destination, tvb,
181 proto_tree_add_item(subtree, hf_mstp_frame_source, tvb,
183 item = proto_tree_add_item(subtree, hf_mstp_frame_pdu_len, tvb,
185 mstp_tvb_pdu_len = tvb_length_remaining(tvb, offset+6);
186 /* check the length - which does not include the crc16 checksum */
187 if (mstp_tvb_pdu_len > 2) {
188 if (mstp_frame_pdu_len > (mstp_tvb_pdu_len-2)) {
189 expert_add_info_format(pinfo, item, PI_MALFORMED, PI_ERROR,
190 "Length field value goes past the end of the payload");
193 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
194 /* calculate checksum to validate */
195 for (i = 0; i < 5; i++) {
196 crcdata = tvb_get_guint8(tvb, offset+i);
197 crc8 = CRC_Calc_Header(crcdata, crc8);
200 framecrc8 = tvb_get_guint8(tvb, offset+5);
201 if (framecrc8 == crc8) {
202 item = proto_tree_add_uint_format(subtree, hf_mstp_frame_crc8,
203 tvb, offset+5, 1, framecrc8,
204 "Header CRC: 0x%02x [correct]", framecrc8);
205 checksum_tree = proto_item_add_subtree(item, ett_bacnet_mstp_checksum);
206 item = proto_tree_add_boolean(checksum_tree,
207 hf_mstp_frame_checksum_good,
208 tvb, offset+5, 1, TRUE);
209 PROTO_ITEM_SET_GENERATED(item);
210 item = proto_tree_add_boolean(checksum_tree,
211 hf_mstp_frame_checksum_bad,
212 tvb, offset+5, 1, FALSE);
213 PROTO_ITEM_SET_GENERATED(item);
215 item = proto_tree_add_uint_format(subtree, hf_mstp_frame_crc8,
216 tvb, offset+5, 1, framecrc8,
217 "Header CRC: 0x%02x [incorrect, should be 0x%02x]",
219 checksum_tree = proto_item_add_subtree(item, ett_bacnet_mstp_checksum);
220 item = proto_tree_add_boolean(checksum_tree,
221 hf_mstp_frame_checksum_good,
222 tvb, offset+5, 1, FALSE);
223 PROTO_ITEM_SET_GENERATED(item);
224 item = proto_tree_add_boolean(checksum_tree,
225 hf_mstp_frame_checksum_bad,
226 tvb, offset+5, 1, TRUE);
227 PROTO_ITEM_SET_GENERATED(item);
228 expert_add_info_format(pinfo, item, PI_CHECKSUM, PI_ERROR,
232 proto_tree_add_item(subtree, hf_mstp_frame_crc8,
233 tvb, offset+5, 1, TRUE);
236 /* dissect BACnet PDU if there is one */
238 if (mstp_tvb_pdu_len > 2) {
239 /* remove the 16-bit crc checksum bytes */
240 mstp_tvb_pdu_len -= 2;
241 if (mstp_frame_type < 128) {
243 next_tvb = tvb_new_subset(tvb, offset,
244 mstp_tvb_pdu_len, mstp_frame_pdu_len);
247 vendorid = tvb_get_ntohs(tvb, offset);
249 /* Write Vendor ID as tree */
250 proto_tree_add_item(subtree, hf_mstp_frame_vendor_id, tvb,
253 /* NPDU - call the Vendor specific dissector */
254 next_tvb = tvb_new_subset(tvb, offset+2,
255 mstp_tvb_pdu_len-2, mstp_frame_pdu_len);
258 if (!(dissector_try_port(subdissector_table, (vendorid<<16) + mstp_frame_type,
259 next_tvb, pinfo, tree))) {
260 /* Unknown function - dissect the payload as data */
261 call_dissector(data_handle, next_tvb, pinfo, tree);
263 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
264 /* 16-bit checksum - calculate to validate */
265 max_len = min(mstp_frame_pdu_len, mstp_tvb_pdu_len);
266 for (i = 0; i < max_len; i++) {
267 crcdata = tvb_get_guint8(tvb, offset+i);
268 crc16 = CRC_Calc_Data(crcdata, crc16);
271 /* convert it to on-the-wire format */
272 crc16 = g_htons(crc16);
273 /* get the actual CRC from the frame */
274 framecrc16 = tvb_get_ntohs(tvb, offset+mstp_frame_pdu_len);
275 if (framecrc16 == crc16) {
276 item = proto_tree_add_uint_format(subtree, hf_mstp_frame_crc16,
277 tvb, offset+mstp_frame_pdu_len, 2, framecrc16,
278 "Data CRC: 0x%04x [correct]", framecrc16);
279 checksum_tree = proto_item_add_subtree(item,
280 ett_bacnet_mstp_checksum);
281 item = proto_tree_add_boolean(checksum_tree,
282 hf_mstp_frame_checksum_good,
283 tvb, offset+mstp_frame_pdu_len, 2, TRUE);
284 PROTO_ITEM_SET_GENERATED(item);
285 item = proto_tree_add_boolean(checksum_tree,
286 hf_mstp_frame_checksum_bad,
287 tvb, offset+mstp_frame_pdu_len, 2, FALSE);
288 PROTO_ITEM_SET_GENERATED(item);
290 item = proto_tree_add_uint_format(subtree, hf_mstp_frame_crc16,
291 tvb, offset+mstp_frame_pdu_len, 2, framecrc16,
292 "Data CRC: 0x%04x [incorrect, should be 0x%04x]",
294 checksum_tree = proto_item_add_subtree(item,
295 ett_bacnet_mstp_checksum);
296 item = proto_tree_add_boolean(checksum_tree,
297 hf_mstp_frame_checksum_good,
298 tvb, offset+mstp_frame_pdu_len, 2, FALSE);
299 PROTO_ITEM_SET_GENERATED(item);
300 item = proto_tree_add_boolean(checksum_tree,
301 hf_mstp_frame_checksum_bad,
302 tvb, offset+mstp_frame_pdu_len, 2, TRUE);
303 PROTO_ITEM_SET_GENERATED(item);
304 expert_add_info_format(pinfo, item, PI_CHECKSUM, PI_ERROR,
308 proto_tree_add_item(subtree, hf_mstp_frame_crc16,
309 tvb, offset+mstp_frame_pdu_len, 2, TRUE);
315 dissect_mstp_wtap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
320 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
321 guint8 mstp_frame_type = 0;
322 guint8 mstp_frame_source = 0;
323 guint8 mstp_frame_destination = 0;
326 /* set the MS/TP MAC address in the source/destination */
327 /* Use AT_ARCNET since it is similar to BACnet MS/TP */
328 SET_ADDRESS(&pinfo->dl_dst, AT_ARCNET, 1, tvb_get_ptr(tvb, offset+3, 1));
329 SET_ADDRESS(&pinfo->dst, AT_ARCNET, 1, tvb_get_ptr(tvb, offset+3, 1));
330 SET_ADDRESS(&pinfo->dl_src, AT_ARCNET, 1, tvb_get_ptr(tvb, offset+4, 1));
331 SET_ADDRESS(&pinfo->src, AT_ARCNET, 1, tvb_get_ptr(tvb, offset+4, 1));
333 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
334 mstp_frame_type = tvb_get_guint8(tvb, offset+2);
335 mstp_frame_destination = tvb_get_guint8(tvb, offset+3);
336 mstp_frame_source = tvb_get_guint8(tvb, offset+4);
337 ti = proto_tree_add_protocol_format(tree, proto_mstp, tvb, offset, 8,
338 "BACnet MS/TP, Src (%u), Dst (%u), %s",
339 mstp_frame_source, mstp_frame_destination,
340 mstp_frame_type_text(mstp_frame_type));
342 ti = proto_tree_add_item(tree, proto_mstp, tvb, offset, 8, FALSE);
344 subtree = proto_item_add_subtree(ti, ett_bacnet_mstp);
345 proto_tree_add_item(subtree, hf_mstp_preamble_55, tvb,
347 proto_tree_add_item(subtree, hf_mstp_preamble_FF, tvb,
349 dissect_mstp(tvb, pinfo, tree, subtree, offset+2);
353 proto_register_mstp(void)
355 static hf_register_info hf[] = {
356 { &hf_mstp_preamble_55,
357 { "Preamble 55", "mstp.preamble_55",
358 FT_UINT8, BASE_HEX, NULL, 0,
359 "MS/TP Preamble 55", HFILL }
361 { &hf_mstp_preamble_FF,
362 { "Preamble FF", "mstp.preamble_FF",
363 FT_UINT8, BASE_HEX, NULL, 0,
364 "MS/TP Preamble FF", HFILL }
366 { &hf_mstp_frame_type,
367 { "Frame Type", "mstp.frame_type",
368 FT_UINT8, BASE_DEC, VALS(bacnet_mstp_frame_type_name), 0,
369 "MS/TP Frame Type", HFILL }
371 { &hf_mstp_frame_destination,
372 { "Destination Address", "mstp.dst",
373 FT_UINT8, BASE_DEC, NULL, 0,
374 "Destination MS/TP MAC Address", HFILL }
376 { &hf_mstp_frame_source,
377 { "Source Address", "mstp.src",
378 FT_UINT8, BASE_DEC, NULL, 0,
379 "Source MS/TP MAC Address", HFILL }
381 { &hf_mstp_frame_vendor_id,
382 { "VendorID", "mstp.vendorid",
383 FT_UINT16, BASE_DEC, NULL, 0,
384 "MS/TP Vendor ID of proprietary frametypes", HFILL }
386 { &hf_mstp_frame_pdu_len,
387 { "Length", "mstp.len",
388 FT_UINT16, BASE_DEC, NULL, 0,
389 "MS/TP Data Length", HFILL }
391 { &hf_mstp_frame_crc8,
392 { "Header CRC", "mstp.hdr_crc",
393 FT_UINT8, BASE_HEX, NULL, 0,
394 "MS/TP Header CRC", HFILL }
396 { &hf_mstp_frame_crc16,
397 { "Data CRC", "mstp.data_crc",
398 FT_UINT16, BASE_HEX, NULL, 0,
399 "MS/TP Data CRC", HFILL }
401 { &hf_mstp_frame_checksum_bad,
402 { "Bad", "mstp.checksum_bad",
403 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
404 "True: checksum doesn't match packet content; False: matches content or not checked", HFILL }
406 { &hf_mstp_frame_checksum_good,
407 { "Good", "mstp.checksum_good",
408 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
409 "True: checksum matches packet content; False: doesn't match content or not checked", HFILL }
413 static gint *ett[] = {
415 &ett_bacnet_mstp_checksum
418 proto_mstp = proto_register_protocol("BACnet MS/TP",
419 "BACnet MS/TP", "mstp");
421 proto_register_field_array(proto_mstp, hf, array_length(hf));
422 proto_register_subtree_array(ett, array_length(ett));
424 register_dissector("mstp", dissect_mstp_wtap, proto_mstp);
426 subdissector_table = register_dissector_table("mstp.vendor_frame_type",
427 "MSTP Vendor specific Frametypes", FT_UINT24, BASE_DEC);
428 /* Table_type: (Vendor ID << 16) + Frametype */
433 proto_reg_handoff_mstp(void)
435 dissector_handle_t mstp_handle;
436 dissector_handle_t bacnet_handle;
438 mstp_handle = find_dissector("mstp");
439 dissector_add("wtap_encap", WTAP_ENCAP_BACNET_MS_TP, mstp_handle);
441 bacnet_handle = find_dissector("bacnet");
442 data_handle = find_dissector("data");
444 dissector_add("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_EXPECTING_REPLY, bacnet_handle);
445 dissector_add("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_NOT_EXPECTING_REPLY, bacnet_handle);