2 * Routines for BACnet MS/TP datalink dissection
3 * Copyright 2008 Steve Karg <skarg@users.sourceforge.net> Alabama
5 * This is described in Clause 9 of ANSI/ASHRAE Standard 135-2004,
6 * BACnet - A Data Communication Protocol for Building Automation
7 * and Contrl Networks; clause 9 "describes a Master-Slave/Token-Passing
8 * (MS/TP) data link protocol, which provides the same services to the
9 * network layer as ISO 8802-2 Logical Link Control. It uses services
10 * provided by the EIA-485 physical layer." See section 9.3 for the
15 * Wireshark - Network traffic analyzer
16 * By Gerald Combs <gerald@wireshark.org>
17 * Copyright 1998 Gerald Combs
19 * This program is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU General Public License
21 * as published by the Free Software Foundation; either version 2
22 * of the License, or (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
38 #include <epan/packet.h>
40 #include <epan/llcsaps.h>
41 #include <epan/expert.h>
42 #include "packet-llc.h"
43 #include "packet-mstp.h"
45 /* Probably should be a preference, but here for now */
46 #define BACNET_MSTP_SUMMARY_IN_TREE
47 #define BACNET_MSTP_CHECKSUM_VALIDATE
49 /* MS/TP Frame Type */
50 /* Frame Types 8 through 127 are reserved by ASHRAE. */
52 #define MSTP_POLL_FOR_MASTER 1
53 #define MSTP_REPLY_TO_POLL_FOR_MASTER 2
54 #define MSTP_TEST_REQUEST 3
55 #define MSTP_TEST_RESPONSE 4
56 #define MSTP_BACNET_DATA_EXPECTING_REPLY 5
57 #define MSTP_BACNET_DATA_NOT_EXPECTING_REPLY 6
58 #define MSTP_REPLY_POSTPONED 7
60 static const value_string
61 bacnet_mstp_frame_type_name
[] = {
62 {MSTP_TOKEN
, "Token"},
63 {MSTP_POLL_FOR_MASTER
, "Poll For Master"},
64 {MSTP_REPLY_TO_POLL_FOR_MASTER
, "Reply To Poll For Master"},
65 {MSTP_TEST_REQUEST
, "Test_Request"},
66 {MSTP_TEST_RESPONSE
, "Test_Response"},
67 {MSTP_BACNET_DATA_EXPECTING_REPLY
, "BACnet Data Expecting Reply"},
68 {MSTP_BACNET_DATA_NOT_EXPECTING_REPLY
, "BACnet Data Not Expecting Reply"},
69 {MSTP_REPLY_POSTPONED
, "Reply Postponed"},
70 /* Frame Types 128 through 255: Proprietary Frames */
74 static dissector_handle_t data_handle
;
75 static dissector_table_t subdissector_table
;
77 static int proto_mstp
= -1;
79 static gint ett_bacnet_mstp
= -1;
80 static gint ett_bacnet_mstp_checksum
= -1;
82 static int hf_mstp_preamble_55
= -1;
83 static int hf_mstp_preamble_FF
= -1;
84 static int hf_mstp_frame_type
= -1;
85 static int hf_mstp_frame_destination
= -1;
86 static int hf_mstp_frame_source
= -1;
87 static int hf_mstp_frame_vendor_id
= -1;
88 static int hf_mstp_frame_pdu_len
= -1;
89 static int hf_mstp_frame_crc8
= -1;
90 static int hf_mstp_frame_crc16
= -1;
91 static int hf_mstp_frame_checksum_bad
= -1;
92 static int hf_mstp_frame_checksum_good
= -1;
94 static expert_field ei_mstp_frame_pdu_len
= EI_INIT
;
95 static expert_field ei_mstp_frame_checksum_bad
= EI_INIT
;
98 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
99 /* Accumulate "dataValue" into the CRC in crcValue. */
100 /* Return value is updated CRC */
101 /* The ^ operator means exclusive OR. */
102 /* Note: This function is copied directly from the BACnet standard. */
110 crc
= crcValue
^ dataValue
; /* XOR C7..C0 with D7..D0 */
112 /* Exclusive OR the terms in the table (top down) */
113 crc
= crc
^ (crc
<< 1) ^ (crc
<< 2) ^ (crc
<< 3)
114 ^ (crc
<< 4) ^ (crc
<< 5) ^ (crc
<< 6)
117 /* Combine bits shifted out left hand end */
118 return (crc
& 0xfe) ^ ((crc
>> 8) & 1);
122 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
123 /* Accumulate "dataValue" into the CRC in crcValue. */
124 /* Return value is updated CRC */
125 /* The ^ operator means exclusive OR. */
126 /* Note: This function is copied directly from the BACnet standard. */
134 crcLow
= (crcValue
& 0xff) ^ dataValue
; /* XOR C7..C0 with D7..D0 */
136 /* Exclusive OR the terms in the table (top down) */
137 return (crcValue
>> 8) ^ (crcLow
<< 8) ^ (crcLow
<< 3)
138 ^ (crcLow
<< 12) ^ (crcLow
>> 4)
139 ^ (crcLow
& 0x0f) ^ ((crcLow
& 0x0f) << 7);
143 /* Common frame type text */
145 mstp_frame_type_text(guint32 val
)
147 return val_to_str(val
,
148 bacnet_mstp_frame_type_name
,
149 "Unknown Frame Type (%u)");
152 /* dissects a BACnet MS/TP frame */
153 /* preamble 0x55 0xFF is not included in Cimetrics U+4 output */
155 dissect_mstp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
156 proto_tree
*subtree
, gint offset
)
158 guint8 mstp_frame_type
= 0;
159 guint16 mstp_frame_pdu_len
= 0;
160 guint16 mstp_tvb_pdu_len
= 0;
161 guint16 vendorid
= 0;
162 tvbuff_t
*next_tvb
= NULL
;
164 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
165 /* used to calculate the crc value */
166 guint8 crc8
= 0xFF, framecrc8
;
167 guint16 crc16
= 0xFFFF, framecrc16
;
169 guint16 i
; /* loop counter */
171 proto_tree
*checksum_tree
;
174 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "BACnet");
175 col_set_str(pinfo
->cinfo
, COL_INFO
, "BACnet MS/TP");
176 mstp_frame_type
= tvb_get_guint8(tvb
, offset
);
177 mstp_frame_pdu_len
= tvb_get_ntohs(tvb
, offset
+3);
178 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " %s",
179 mstp_frame_type_text(mstp_frame_type
));
181 /* Add the items to the tree */
182 proto_tree_add_item(subtree
, hf_mstp_frame_type
, tvb
,
183 offset
, 1, ENC_LITTLE_ENDIAN
);
184 proto_tree_add_item(subtree
, hf_mstp_frame_destination
, tvb
,
185 offset
+1, 1, ENC_LITTLE_ENDIAN
);
186 proto_tree_add_item(subtree
, hf_mstp_frame_source
, tvb
,
187 offset
+2, 1, ENC_LITTLE_ENDIAN
);
188 item
= proto_tree_add_item(subtree
, hf_mstp_frame_pdu_len
, tvb
,
189 offset
+3, 2, ENC_BIG_ENDIAN
);
190 mstp_tvb_pdu_len
= tvb_length_remaining(tvb
, offset
+6);
191 /* check the length - which does not include the crc16 checksum */
192 if (mstp_tvb_pdu_len
> 2) {
193 if (mstp_frame_pdu_len
> (mstp_tvb_pdu_len
-2)) {
194 expert_add_info(pinfo
, item
, &ei_mstp_frame_pdu_len
);
197 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
198 /* calculate checksum to validate */
199 for (i
= 0; i
< 5; i
++) {
200 crcdata
= tvb_get_guint8(tvb
, offset
+i
);
201 crc8
= CRC_Calc_Header(crcdata
, crc8
);
204 framecrc8
= tvb_get_guint8(tvb
, offset
+5);
205 if (framecrc8
== crc8
) {
206 item
= proto_tree_add_uint_format_value(subtree
, hf_mstp_frame_crc8
,
207 tvb
, offset
+5, 1, framecrc8
,
208 "0x%02x [correct]", framecrc8
);
209 checksum_tree
= proto_item_add_subtree(item
, ett_bacnet_mstp_checksum
);
210 item
= proto_tree_add_boolean(checksum_tree
,
211 hf_mstp_frame_checksum_good
,
212 tvb
, offset
+5, 1, TRUE
);
213 PROTO_ITEM_SET_GENERATED(item
);
214 item
= proto_tree_add_boolean(checksum_tree
,
215 hf_mstp_frame_checksum_bad
,
216 tvb
, offset
+5, 1, FALSE
);
217 PROTO_ITEM_SET_GENERATED(item
);
219 item
= proto_tree_add_uint_format_value(subtree
, hf_mstp_frame_crc8
,
220 tvb
, offset
+5, 1, framecrc8
,
221 "0x%02x [incorrect, should be 0x%02x]",
223 checksum_tree
= proto_item_add_subtree(item
, ett_bacnet_mstp_checksum
);
224 item
= proto_tree_add_boolean(checksum_tree
,
225 hf_mstp_frame_checksum_good
,
226 tvb
, offset
+5, 1, FALSE
);
227 PROTO_ITEM_SET_GENERATED(item
);
228 item
= proto_tree_add_boolean(checksum_tree
,
229 hf_mstp_frame_checksum_bad
,
230 tvb
, offset
+5, 1, TRUE
);
231 PROTO_ITEM_SET_GENERATED(item
);
232 expert_add_info(pinfo
, item
, &ei_mstp_frame_checksum_bad
);
235 proto_tree_add_item(subtree
, hf_mstp_frame_crc8
,
236 tvb
, offset
+5, 1, ENC_LITTLE_ENDIAN
);
239 /* dissect BACnet PDU if there is one */
241 if (mstp_tvb_pdu_len
> 2) {
242 /* remove the 16-bit crc checksum bytes */
243 mstp_tvb_pdu_len
-= 2;
244 if (mstp_frame_type
< 128) {
246 next_tvb
= tvb_new_subset(tvb
, offset
,
247 mstp_tvb_pdu_len
, mstp_frame_pdu_len
);
250 vendorid
= tvb_get_ntohs(tvb
, offset
);
252 /* Write Vendor ID as tree */
253 proto_tree_add_item(subtree
, hf_mstp_frame_vendor_id
, tvb
,
254 offset
, 2, ENC_BIG_ENDIAN
);
256 /* NPDU - call the Vendor specific dissector */
257 next_tvb
= tvb_new_subset(tvb
, offset
+2,
258 mstp_tvb_pdu_len
-2, mstp_frame_pdu_len
);
261 if (!(dissector_try_uint(subdissector_table
, (vendorid
<<16) + mstp_frame_type
,
262 next_tvb
, pinfo
, tree
))) {
263 /* Unknown function - dissect the payload as data */
264 call_dissector(data_handle
, next_tvb
, pinfo
, tree
);
266 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
267 /* 16-bit checksum - calculate to validate */
268 max_len
= MIN(mstp_frame_pdu_len
, mstp_tvb_pdu_len
);
269 for (i
= 0; i
< max_len
; i
++) {
270 crcdata
= tvb_get_guint8(tvb
, offset
+i
);
271 crc16
= CRC_Calc_Data(crcdata
, crc16
);
274 /* convert it to on-the-wire format */
275 crc16
= g_htons(crc16
);
276 /* get the actual CRC from the frame */
277 framecrc16
= tvb_get_ntohs(tvb
, offset
+mstp_frame_pdu_len
);
278 if (framecrc16
== crc16
) {
279 item
= proto_tree_add_uint_format_value(subtree
, hf_mstp_frame_crc16
,
280 tvb
, offset
+mstp_frame_pdu_len
, 2, framecrc16
,
281 "0x%04x [correct]", framecrc16
);
282 checksum_tree
= proto_item_add_subtree(item
,
283 ett_bacnet_mstp_checksum
);
284 item
= proto_tree_add_boolean(checksum_tree
,
285 hf_mstp_frame_checksum_good
,
286 tvb
, offset
+mstp_frame_pdu_len
, 2, TRUE
);
287 PROTO_ITEM_SET_GENERATED(item
);
288 item
= proto_tree_add_boolean(checksum_tree
,
289 hf_mstp_frame_checksum_bad
,
290 tvb
, offset
+mstp_frame_pdu_len
, 2, FALSE
);
291 PROTO_ITEM_SET_GENERATED(item
);
293 item
= proto_tree_add_uint_format_value(subtree
, hf_mstp_frame_crc16
,
294 tvb
, offset
+mstp_frame_pdu_len
, 2, framecrc16
,
295 "0x%04x [incorrect, should be 0x%04x]",
297 checksum_tree
= proto_item_add_subtree(item
,
298 ett_bacnet_mstp_checksum
);
299 item
= proto_tree_add_boolean(checksum_tree
,
300 hf_mstp_frame_checksum_good
,
301 tvb
, offset
+mstp_frame_pdu_len
, 2, FALSE
);
302 PROTO_ITEM_SET_GENERATED(item
);
303 item
= proto_tree_add_boolean(checksum_tree
,
304 hf_mstp_frame_checksum_bad
,
305 tvb
, offset
+mstp_frame_pdu_len
, 2, TRUE
);
306 PROTO_ITEM_SET_GENERATED(item
);
307 expert_add_info(pinfo
, item
, &ei_mstp_frame_checksum_bad
);
310 proto_tree_add_item(subtree
, hf_mstp_frame_crc16
,
311 tvb
, offset
+mstp_frame_pdu_len
, 2, ENC_LITTLE_ENDIAN
);
317 dissect_mstp_wtap(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
)
322 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
323 guint8 mstp_frame_type
= 0;
324 guint8 mstp_frame_source
= 0;
325 guint8 mstp_frame_destination
= 0;
328 /* set the MS/TP MAC address in the source/destination */
329 /* Use AT_ARCNET since it is similar to BACnet MS/TP */
330 TVB_SET_ADDRESS(&pinfo
->dl_dst
, AT_ARCNET
, tvb
, offset
+3, 1);
331 TVB_SET_ADDRESS(&pinfo
->dst
, AT_ARCNET
, tvb
, offset
+3, 1);
332 TVB_SET_ADDRESS(&pinfo
->dl_src
, AT_ARCNET
, tvb
, offset
+4, 1);
333 TVB_SET_ADDRESS(&pinfo
->src
, AT_ARCNET
, tvb
, offset
+4, 1);
335 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
336 mstp_frame_type
= tvb_get_guint8(tvb
, offset
+2);
337 mstp_frame_destination
= tvb_get_guint8(tvb
, offset
+3);
338 mstp_frame_source
= tvb_get_guint8(tvb
, offset
+4);
339 ti
= proto_tree_add_protocol_format(tree
, proto_mstp
, tvb
, offset
, 8,
340 "BACnet MS/TP, Src (%u), Dst (%u), %s",
341 mstp_frame_source
, mstp_frame_destination
,
342 mstp_frame_type_text(mstp_frame_type
));
344 ti
= proto_tree_add_item(tree
, proto_mstp
, tvb
, offset
, 8, ENC_NA
);
346 subtree
= proto_item_add_subtree(ti
, ett_bacnet_mstp
);
347 proto_tree_add_item(subtree
, hf_mstp_preamble_55
, tvb
,
348 offset
, 1, ENC_LITTLE_ENDIAN
);
349 proto_tree_add_item(subtree
, hf_mstp_preamble_FF
, tvb
,
350 offset
+1, 1, ENC_LITTLE_ENDIAN
);
351 dissect_mstp(tvb
, pinfo
, tree
, subtree
, offset
+2);
355 proto_register_mstp(void)
357 static hf_register_info hf
[] = {
358 { &hf_mstp_preamble_55
,
359 { "Preamble 55", "mstp.preamble_55",
360 FT_UINT8
, BASE_HEX
, NULL
, 0,
361 "MS/TP Preamble 55", HFILL
}
363 { &hf_mstp_preamble_FF
,
364 { "Preamble FF", "mstp.preamble_FF",
365 FT_UINT8
, BASE_HEX
, NULL
, 0,
366 "MS/TP Preamble FF", HFILL
}
368 { &hf_mstp_frame_type
,
369 { "Frame Type", "mstp.frame_type",
370 FT_UINT8
, BASE_DEC
, VALS(bacnet_mstp_frame_type_name
), 0,
371 "MS/TP Frame Type", HFILL
}
373 { &hf_mstp_frame_destination
,
374 { "Destination Address", "mstp.dst",
375 FT_UINT8
, BASE_DEC
, NULL
, 0,
376 "Destination MS/TP MAC Address", HFILL
}
378 { &hf_mstp_frame_source
,
379 { "Source Address", "mstp.src",
380 FT_UINT8
, BASE_DEC
, NULL
, 0,
381 "Source MS/TP MAC Address", HFILL
}
383 { &hf_mstp_frame_vendor_id
,
384 { "VendorID", "mstp.vendorid",
385 FT_UINT16
, BASE_DEC
, NULL
, 0,
386 "MS/TP Vendor ID of proprietary frametypes", HFILL
}
388 { &hf_mstp_frame_pdu_len
,
389 { "Length", "mstp.len",
390 FT_UINT16
, BASE_DEC
, NULL
, 0,
391 "MS/TP Data Length", HFILL
}
393 { &hf_mstp_frame_crc8
,
394 { "Header CRC", "mstp.hdr_crc",
395 FT_UINT8
, BASE_HEX
, NULL
, 0,
396 "MS/TP Header CRC", HFILL
}
398 { &hf_mstp_frame_crc16
,
399 { "Data CRC", "mstp.data_crc",
400 FT_UINT16
, BASE_HEX
, NULL
, 0,
401 "MS/TP Data CRC", HFILL
}
403 { &hf_mstp_frame_checksum_bad
,
404 { "Bad", "mstp.checksum_bad",
405 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
406 "True: checksum doesn't match packet content; False: matches content or not checked", HFILL
}
408 { &hf_mstp_frame_checksum_good
,
409 { "Good", "mstp.checksum_good",
410 FT_BOOLEAN
, BASE_NONE
, NULL
, 0x0,
411 "True: checksum matches packet content; False: doesn't match content or not checked", HFILL
}
415 static gint
*ett
[] = {
417 &ett_bacnet_mstp_checksum
420 static ei_register_info ei
[] = {
421 { &ei_mstp_frame_pdu_len
, { "mstp.len.bad", PI_MALFORMED
, PI_ERROR
, "Length field value goes past the end of the payload", EXPFILL
}},
422 { &ei_mstp_frame_checksum_bad
, { "mstp.checksum_bad.expert", PI_CHECKSUM
, PI_WARN
, "Bad Checksum", EXPFILL
}},
425 expert_module_t
* expert_mstp
;
427 proto_mstp
= proto_register_protocol("BACnet MS/TP",
428 "BACnet MS/TP", "mstp");
430 proto_register_field_array(proto_mstp
, hf
, array_length(hf
));
431 proto_register_subtree_array(ett
, array_length(ett
));
432 expert_mstp
= expert_register_protocol(proto_mstp
);
433 expert_register_field_array(expert_mstp
, ei
, array_length(ei
));
435 register_dissector("mstp", dissect_mstp_wtap
, proto_mstp
);
437 subdissector_table
= register_dissector_table("mstp.vendor_frame_type",
438 "MSTP Vendor specific Frametypes", FT_UINT24
, BASE_DEC
);
439 /* Table_type: (Vendor ID << 16) + Frametype */
443 proto_reg_handoff_mstp(void)
445 dissector_handle_t mstp_handle
;
446 dissector_handle_t bacnet_handle
;
448 mstp_handle
= find_dissector("mstp");
449 dissector_add_uint("wtap_encap", WTAP_ENCAP_BACNET_MS_TP
, mstp_handle
);
450 dissector_add_uint("wtap_encap", WTAP_ENCAP_BACNET_MS_TP_WITH_PHDR
, mstp_handle
);
452 bacnet_handle
= find_dissector("bacnet");
453 data_handle
= find_dissector("data");
455 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_EXPECTING_REPLY
, bacnet_handle
);
456 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_NOT_EXPECTING_REPLY
, bacnet_handle
);