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
13 * Wireshark - Network traffic analyzer
14 * By Gerald Combs <gerald@wireshark.org>
15 * Copyright 1998 Gerald Combs
17 * SPDX-License-Identifier: GPL-2.0-or-later
22 #include <epan/packet.h>
23 #include <wiretap/wtap.h>
24 #include <epan/expert.h>
25 #include <epan/address_types.h>
26 #include <epan/to_str.h>
27 #include "packet-mstp.h"
29 void proto_register_mstp(void);
30 void proto_reg_handoff_mstp(void);
32 /* Probably should be a preference, but here for now */
33 #define BACNET_MSTP_SUMMARY_IN_TREE
34 #define BACNET_MSTP_CHECKSUM_VALIDATE
36 /* MS/TP Frame Type */
37 /* Frame Types 8 through 127 are reserved by ASHRAE. */
38 #define MSTP_TOKEN 0x00
39 #define MSTP_POLL_FOR_MASTER 0x01
40 #define MSTP_REPLY_TO_POLL_FOR_MASTER 0x02
41 #define MSTP_TEST_REQUEST 0x03
42 #define MSTP_TEST_RESPONSE 0x04
43 #define MSTP_BACNET_DATA_EXPECTING_REPLY 0x05
44 #define MSTP_BACNET_DATA_NOT_EXPECTING_REPLY 0x06
45 #define MSTP_REPLY_POSTPONED 0x07
46 #define MSTP_BACNET_EXTENDED_DATA_EXPECTING_REPLY 0x20
47 #define MSTP_BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY 0x21
50 static const value_string
51 bacnet_mstp_frame_type_name
[] = {
52 {MSTP_TOKEN
, "Token"},
53 {MSTP_POLL_FOR_MASTER
, "Poll For Master"},
54 {MSTP_REPLY_TO_POLL_FOR_MASTER
, "Reply To Poll For Master"},
55 {MSTP_TEST_REQUEST
, "Test_Request"},
56 {MSTP_TEST_RESPONSE
, "Test_Response"},
57 {MSTP_BACNET_DATA_EXPECTING_REPLY
, "BACnet Data Expecting Reply"},
58 {MSTP_BACNET_DATA_NOT_EXPECTING_REPLY
, "BACnet Data Not Expecting Reply"},
59 {MSTP_REPLY_POSTPONED
, "Reply Postponed"},
60 {MSTP_BACNET_EXTENDED_DATA_EXPECTING_REPLY
, "BACnet Extended Data Expecting Reply"},
61 {MSTP_BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY
, "BACnet Extended Data Not Expecting Reply"},
62 /* Frame Types 128 through 255: Proprietary Frames */
66 static dissector_table_t subdissector_table
;
68 static int proto_mstp
;
70 static int ett_bacnet_mstp
;
71 static int ett_bacnet_mstp_checksum
;
73 static int hf_mstp_preamble_55
;
74 static int hf_mstp_preamble_FF
;
75 static int hf_mstp_frame_type
;
76 static int hf_mstp_frame_destination
;
77 static int hf_mstp_frame_source
;
78 static int hf_mstp_frame_vendor_id
;
79 static int hf_mstp_frame_pdu_len
;
80 static int hf_mstp_frame_crc8
;
81 static int hf_mstp_frame_crc16
;
82 static int hf_mstp_frame_checksum_status
;
84 static expert_field ei_mstp_frame_pdu_len
;
85 static expert_field ei_mstp_frame_checksum_bad
;
87 static int mstp_address_type
= -1;
89 static dissector_handle_t mstp_handle
;
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. */
103 crc
= crcValue
^ dataValue
; /* XOR C7..C0 with D7..D0 */
105 /* Exclusive OR the terms in the table (top down) */
106 crc
= crc
^ (crc
<< 1) ^ (crc
<< 2) ^ (crc
<< 3)
107 ^ (crc
<< 4) ^ (crc
<< 5) ^ (crc
<< 6)
110 /* Combine bits shifted out left hand end */
111 return (crc
& 0xfe) ^ ((crc
>> 8) & 1);
115 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
116 /* Accumulate "dataValue" into the CRC in crcValue. */
117 /* Return value is updated CRC */
118 /* The ^ operator means exclusive OR. */
119 /* Note: This function is copied directly from the BACnet standard. */
127 crcLow
= (crcValue
& 0xff) ^ dataValue
; /* XOR C7..C0 with D7..D0 */
129 /* Exclusive OR the terms in the table (top down) */
130 return (crcValue
>> 8) ^ (crcLow
<< 8) ^ (crcLow
<< 3)
131 ^ (crcLow
<< 12) ^ (crcLow
>> 4)
132 ^ (crcLow
& 0x0f) ^ ((crcLow
& 0x0f) << 7);
136 /* Common frame type text */
138 mstp_frame_type_text(uint32_t val
)
140 return val_to_str(val
,
141 bacnet_mstp_frame_type_name
,
142 "Unknown Frame Type (%u)");
145 static int mstp_str_len(const address
* addr _U_
)
150 static int mstp_to_str(const address
* addr
, char *buf
, int buf_len _U_
)
154 buf
= bytes_to_hexstr(buf
, (const uint8_t *)addr
->data
, 1);
155 *buf
= '\0'; /* NULL terminate */
157 return mstp_str_len(addr
);
160 static const char* mstp_col_filter_str(const address
* addr _U_
, bool is_src
)
168 static int mstp_len(void)
173 static uint32_t calc_data_crc32(uint8_t dataValue
, uint32_t crc32kValue
)
182 for (b
= 0; b
< 8; b
++)
184 if ((data
& 1) ^ (crc
& 1))
201 * Decodes 'length' octets of data located at 'from' and
202 * writes the original client data at 'to', restoring any
203 * 'mask' octets that may present in the encoded data.
204 * Returns the length of the encoded data or zero if error.
205 * The length of the encoded value is always smaller or equal to 'length'.
207 static size_t cobs_decode(uint8_t *to
, const uint8_t *from
, size_t length
, uint8_t mask
)
209 size_t read_index
= 0;
210 size_t write_index
= 0;
214 while (read_index
< length
)
216 code
= from
[read_index
] ^ mask
;
219 * A code octet equal to zero or greater than the length is illegal.
221 if (code
== 0 || read_index
+ code
> length
)
226 * Decode data octets. The code octet is included in the length, but the
227 * terminating zero octet is not. (Note that a data octet of zero should not
228 * occur here since the whole point of COBS encoding is to remove zeroes.)
231 to
[write_index
++] = from
[read_index
++] ^ mask
;
234 * Restore the implicit zero at the end of each decoded block
235 * except when it contains exactly 254 non-zero octets or the
236 * end of data has been reached.
238 if ((last_code
!= 255) && (read_index
< length
))
239 to
[write_index
++] = 0;
245 #define SIZEOF_ENC_CRC 5
246 #define CRC32K_INITIAL_VALUE 0xFFFFFFFF
247 #define CRC32K_RESIDUE 0x0843323B
248 #define MSTP_PREAMBLE_X55 0x55
251 * Decodes Encoded Data and Encoded CRC-32K fields at 'from' (of length 'length')
252 * and writes the decoded client data at 'to'.
253 * Returns length of decoded Data in octets or zero if error.
254 * NOTE: Safe to call with 'output' <= 'input' (decodes in place).
256 static size_t cobs_frame_decode(uint8_t *to
, const uint8_t *from
, size_t length
)
263 /* Must have enough room for the encoded CRC-32K value. */
264 if (length
< SIZEOF_ENC_CRC
)
268 * Calculate the CRC32K over the Encoded Data octets before decoding.
269 * NOTE: Adjust 'length' by removing size of Encoded CRC-32K field.
271 data_len
= length
- SIZEOF_ENC_CRC
;
272 crc32K
= CRC32K_INITIAL_VALUE
;
273 for (i
= 0; i
< data_len
; i
++)
274 crc32K
= calc_data_crc32(from
[i
], crc32K
);
276 data_len
= cobs_decode(to
, from
, data_len
, MSTP_PREAMBLE_X55
);
278 * Decode the Encoded CRC-32K field and append to data.
280 crc_len
= cobs_decode((uint8_t *)(to
+ data_len
),
281 (uint8_t *)(from
+ length
- SIZEOF_ENC_CRC
),
282 SIZEOF_ENC_CRC
, MSTP_PREAMBLE_X55
);
285 * Sanity check length of decoded CRC32K.
287 if (crc_len
!= sizeof(uint32_t))
291 * Verify CRC32K of incoming frame.
293 for (i
= 0; i
< crc_len
; i
++)
294 crc32K
= calc_data_crc32((to
+ data_len
)[i
], crc32K
);
296 if (crc32K
== CRC32K_RESIDUE
)
302 /* dissects a BACnet MS/TP frame */
303 /* preamble 0x55 0xFF is not included in Cimetrics U+4 output */
305 dissect_mstp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
306 proto_tree
*subtree
, int offset
)
308 uint8_t mstp_frame_type
= 0;
309 uint16_t mstp_frame_pdu_len
= 0;
310 uint16_t mstp_tvb_pdu_len
= 0;
311 uint16_t vendorid
= 0;
312 tvbuff_t
*next_tvb
= NULL
;
314 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
315 /* used to calculate the crc value */
317 uint16_t crc16
= 0xFFFF;
319 uint16_t i
; /* loop counter */
320 uint16_t max_len
= 0;
323 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "BACnet");
324 col_set_str(pinfo
->cinfo
, COL_INFO
, "BACnet MS/TP");
325 mstp_frame_type
= tvb_get_uint8(tvb
, offset
);
326 mstp_frame_pdu_len
= tvb_get_ntohs(tvb
, offset
+3);
327 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " %s",
328 mstp_frame_type_text(mstp_frame_type
));
330 /* Add the items to the tree */
331 proto_tree_add_item(subtree
, hf_mstp_frame_type
, tvb
,
332 offset
, 1, ENC_LITTLE_ENDIAN
);
333 proto_tree_add_item(subtree
, hf_mstp_frame_destination
, tvb
,
334 offset
+1, 1, ENC_LITTLE_ENDIAN
);
335 proto_tree_add_item(subtree
, hf_mstp_frame_source
, tvb
,
336 offset
+2, 1, ENC_LITTLE_ENDIAN
);
337 item
= proto_tree_add_item(subtree
, hf_mstp_frame_pdu_len
, tvb
,
338 offset
+3, 2, ENC_BIG_ENDIAN
);
339 mstp_tvb_pdu_len
= tvb_reported_length_remaining(tvb
, offset
+6);
340 /* check the length - which does not include the crc16 checksum */
341 if (mstp_tvb_pdu_len
> 2) {
342 if (mstp_frame_pdu_len
> (mstp_tvb_pdu_len
-2)) {
343 expert_add_info(pinfo
, item
, &ei_mstp_frame_pdu_len
);
346 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
347 /* calculate checksum to validate */
348 for (i
= 0; i
< 5; i
++) {
349 crcdata
= tvb_get_uint8(tvb
, offset
+i
);
350 crc8
= CRC_Calc_Header(crcdata
, crc8
);
353 proto_tree_add_checksum(subtree
, tvb
, offset
+5, hf_mstp_frame_crc8
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
, pinfo
, crc8
,
354 ENC_BIG_ENDIAN
, PROTO_CHECKSUM_VERIFY
);
356 proto_tree_add_checksum(subtree
, tvb
, offset
+5, hf_mstp_frame_crc8
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
, pinfo
, 0,
357 PROTO_CHECKSUM_NO_FLAGS
);
360 /* dissect BACnet PDU if there is one */
363 if (mstp_frame_type
== MSTP_BACNET_EXTENDED_DATA_EXPECTING_REPLY
||
364 mstp_frame_type
== MSTP_BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY
) {
365 /* handle extended frame types differently because their data need to
366 be 'decoded' first */
367 uint8_t *decode_base
;
368 tvbuff_t
*decoded_tvb
;
369 uint16_t decoded_len
= mstp_frame_pdu_len
;
371 decode_base
= (uint8_t *)tvb_memdup(pinfo
->pool
, tvb
, offset
, mstp_frame_pdu_len
+ 2);
372 decoded_len
= (uint16_t)cobs_frame_decode(decode_base
, decode_base
, decoded_len
+ 2);
373 if (decoded_len
> 0) {
374 decoded_tvb
= tvb_new_real_data(decode_base
, decoded_len
, decoded_len
);
375 tvb_set_child_real_data_tvbuff(tvb
, decoded_tvb
);
376 add_new_data_source(pinfo
, decoded_tvb
, "Decoded Data");
378 if (!(dissector_try_uint(subdissector_table
, (vendorid
<< 16) + mstp_frame_type
,
379 decoded_tvb
, pinfo
, tree
))) {
380 /* Unknown function - dissect the payload as data */
381 call_data_dissector(decoded_tvb
, pinfo
, tree
);
384 proto_tree_add_checksum(subtree
, tvb
, offset
+ mstp_frame_pdu_len
, hf_mstp_frame_crc16
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
,
385 pinfo
, tvb_get_ntohs(tvb
, offset
+ mstp_frame_pdu_len
), ENC_BIG_ENDIAN
, PROTO_CHECKSUM_VERIFY
);
387 next_tvb
= tvb_new_subset_length(tvb
, offset
,
389 call_data_dissector(next_tvb
, pinfo
, tree
);
390 proto_tree_add_checksum(subtree
, tvb
, offset
+ mstp_frame_pdu_len
, hf_mstp_frame_crc16
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
, pinfo
, 0,
391 ENC_BIG_ENDIAN
, PROTO_CHECKSUM_NO_FLAGS
);
394 else if (mstp_tvb_pdu_len
> 2) {
395 /* remove the 16-bit crc checksum bytes */
396 mstp_tvb_pdu_len
-= 2;
397 if (mstp_frame_type
< 128) {
399 next_tvb
= tvb_new_subset_length(tvb
, offset
,
403 vendorid
= tvb_get_ntohs(tvb
, offset
);
405 /* Write Vendor ID as tree */
406 proto_tree_add_item(subtree
, hf_mstp_frame_vendor_id
, tvb
,
407 offset
, 2, ENC_BIG_ENDIAN
);
409 /* NPDU - call the Vendor specific dissector */
410 next_tvb
= tvb_new_subset_length_caplen(tvb
, offset
+2,
411 mstp_tvb_pdu_len
-2, mstp_frame_pdu_len
);
414 if (!(dissector_try_uint(subdissector_table
, (vendorid
<<16) + mstp_frame_type
,
415 next_tvb
, pinfo
, tree
))) {
416 /* Unknown function - dissect the payload as data */
417 call_data_dissector(next_tvb
, pinfo
, tree
);
419 #if defined(BACNET_MSTP_CHECKSUM_VALIDATE)
420 /* 16-bit checksum - calculate to validate */
421 max_len
= MIN(mstp_frame_pdu_len
, mstp_tvb_pdu_len
);
422 for (i
= 0; i
< max_len
; i
++) {
423 crcdata
= tvb_get_uint8(tvb
, offset
+i
);
424 crc16
= CRC_Calc_Data(crcdata
, crc16
);
427 /* convert it to on-the-wire format */
428 crc16
= g_htons(crc16
);
430 proto_tree_add_checksum(subtree
, tvb
, offset
+mstp_frame_pdu_len
, hf_mstp_frame_crc16
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
, pinfo
, crc16
,
431 ENC_BIG_ENDIAN
, PROTO_CHECKSUM_VERIFY
);
433 proto_tree_add_checksum(subtree
, tvb
, offset
+mstp_frame_pdu_len
, hf_mstp_frame_crc16
, hf_mstp_frame_checksum_status
, &ei_mstp_frame_checksum_bad
, pinfo
, 0,
434 ENC_BIG_ENDIAN
, PROTO_CHECKSUM_NO_FLAGS
);
440 dissect_mstp_wtap(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
)
445 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
446 uint8_t mstp_frame_type
= 0;
447 uint8_t mstp_frame_source
= 0;
448 uint8_t mstp_frame_destination
= 0;
451 /* set the MS/TP MAC address in the source/destination */
452 set_address_tvb(&pinfo
->dl_dst
, mstp_address_type
, 1, tvb
, offset
+3);
453 copy_address_shallow(&pinfo
->dst
, &pinfo
->dl_dst
);
454 set_address_tvb(&pinfo
->dl_src
, mstp_address_type
, 1, tvb
, offset
+4);
455 copy_address_shallow(&pinfo
->src
, &pinfo
->dl_src
);
457 #ifdef BACNET_MSTP_SUMMARY_IN_TREE
458 mstp_frame_type
= tvb_get_uint8(tvb
, offset
+2);
459 mstp_frame_destination
= tvb_get_uint8(tvb
, offset
+3);
460 mstp_frame_source
= tvb_get_uint8(tvb
, offset
+4);
461 ti
= proto_tree_add_protocol_format(tree
, proto_mstp
, tvb
, offset
, 8,
462 "BACnet MS/TP, Src (%u), Dst (%u), %s",
463 mstp_frame_source
, mstp_frame_destination
,
464 mstp_frame_type_text(mstp_frame_type
));
466 ti
= proto_tree_add_item(tree
, proto_mstp
, tvb
, offset
, 8, ENC_NA
);
468 subtree
= proto_item_add_subtree(ti
, ett_bacnet_mstp
);
469 proto_tree_add_item(subtree
, hf_mstp_preamble_55
, tvb
,
470 offset
, 1, ENC_LITTLE_ENDIAN
);
471 proto_tree_add_item(subtree
, hf_mstp_preamble_FF
, tvb
,
472 offset
+1, 1, ENC_LITTLE_ENDIAN
);
473 dissect_mstp(tvb
, pinfo
, tree
, subtree
, offset
+2);
474 return tvb_captured_length(tvb
);
478 proto_register_mstp(void)
480 static hf_register_info hf
[] = {
481 { &hf_mstp_preamble_55
,
482 { "Preamble 55", "mstp.preamble_55",
483 FT_UINT8
, BASE_HEX
, NULL
, 0,
484 "MS/TP Preamble 55", HFILL
}
486 { &hf_mstp_preamble_FF
,
487 { "Preamble FF", "mstp.preamble_FF",
488 FT_UINT8
, BASE_HEX
, NULL
, 0,
489 "MS/TP Preamble FF", HFILL
}
491 { &hf_mstp_frame_type
,
492 { "Frame Type", "mstp.frame_type",
493 FT_UINT8
, BASE_DEC
, VALS(bacnet_mstp_frame_type_name
), 0,
494 "MS/TP Frame Type", HFILL
}
496 { &hf_mstp_frame_destination
,
497 { "Destination Address", "mstp.dst",
498 FT_UINT8
, BASE_DEC
, NULL
, 0,
499 "Destination MS/TP MAC Address", HFILL
}
501 { &hf_mstp_frame_source
,
502 { "Source Address", "mstp.src",
503 FT_UINT8
, BASE_DEC
, NULL
, 0,
504 "Source MS/TP MAC Address", HFILL
}
506 { &hf_mstp_frame_vendor_id
,
507 { "VendorID", "mstp.vendorid",
508 FT_UINT16
, BASE_DEC
, NULL
, 0,
509 "MS/TP Vendor ID of proprietary frametypes", HFILL
}
511 { &hf_mstp_frame_pdu_len
,
512 { "Length", "mstp.len",
513 FT_UINT16
, BASE_DEC
, NULL
, 0,
514 "MS/TP Data Length", HFILL
}
516 { &hf_mstp_frame_crc8
,
517 { "Header CRC", "mstp.hdr_crc",
518 FT_UINT8
, BASE_HEX
, NULL
, 0,
519 "MS/TP Header CRC", HFILL
}
521 { &hf_mstp_frame_crc16
,
522 { "Data CRC", "mstp.data_crc",
523 FT_UINT16
, BASE_HEX
, NULL
, 0,
524 "MS/TP Data CRC", HFILL
}
526 { &hf_mstp_frame_checksum_status
,
527 { "Checksum status", "mstp.checksum.status",
528 FT_UINT8
, BASE_NONE
, VALS(proto_checksum_vals
), 0x0,
533 static int *ett
[] = {
535 &ett_bacnet_mstp_checksum
538 static ei_register_info ei
[] = {
539 { &ei_mstp_frame_pdu_len
, { "mstp.len.bad", PI_MALFORMED
, PI_ERROR
, "Length field value goes past the end of the payload", EXPFILL
}},
540 { &ei_mstp_frame_checksum_bad
, { "mstp.checksum_bad.expert", PI_CHECKSUM
, PI_WARN
, "Bad Checksum", EXPFILL
}},
543 expert_module_t
* expert_mstp
;
545 proto_mstp
= proto_register_protocol("BACnet MS/TP", "BACnet MS/TP", "mstp");
547 proto_register_field_array(proto_mstp
, hf
, array_length(hf
));
548 proto_register_subtree_array(ett
, array_length(ett
));
549 expert_mstp
= expert_register_protocol(proto_mstp
);
550 expert_register_field_array(expert_mstp
, ei
, array_length(ei
));
552 mstp_handle
= register_dissector("mstp", dissect_mstp_wtap
, proto_mstp
);
554 subdissector_table
= register_dissector_table("mstp.vendor_frame_type",
555 "MSTP Vendor specific Frametypes", proto_mstp
, FT_UINT24
, BASE_DEC
);
556 /* Table_type: (Vendor ID << 16) + Frametype */
558 mstp_address_type
= address_type_dissector_register("AT_MSTP", "BACnet MS/TP Address", mstp_to_str
, mstp_str_len
, NULL
, mstp_col_filter_str
, mstp_len
, NULL
, NULL
);
562 proto_reg_handoff_mstp(void)
564 dissector_handle_t bacnet_handle
;
566 dissector_add_uint("wtap_encap", WTAP_ENCAP_BACNET_MS_TP
, mstp_handle
);
567 dissector_add_uint("wtap_encap", WTAP_ENCAP_BACNET_MS_TP_WITH_PHDR
, mstp_handle
);
569 bacnet_handle
= find_dissector("bacnet");
571 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_EXPECTING_REPLY
, bacnet_handle
);
572 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_DATA_NOT_EXPECTING_REPLY
, bacnet_handle
);
573 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_EXTENDED_DATA_EXPECTING_REPLY
, bacnet_handle
);
574 dissector_add_uint("mstp.vendor_frame_type", (0/*VendorID ASHRAE*/ << 16) + MSTP_BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY
, bacnet_handle
);
578 * Editor modelines - https://www.wireshark.org/tools/modelines.html
583 * indent-tabs-mode: t
586 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
587 * :indentSize=8:tabSize=8:noTabs=false: