Coverage Report

Created: 2025-02-15 06:25

/src/wireshark/epan/dissectors/packet-mdb.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * packet-mdb.c
3
 * Routines for MDB dissection
4
 * Copyright 2023 Martin Kaiser for PayTec AG (www.paytec.ch)
5
 *
6
 * Wireshark - Network traffic analyzer
7
 * By Gerald Combs <gerald@wireshark.org>
8
 * Copyright 1998 Gerald Combs
9
 *
10
 * SPDX-License-Identifier: GPL-2.0-or-later
11
 */
12
13
/*
14
 * The MDB (Multi-Drop Bus) protocol is used inside a vending machine. MDB
15
 * defines the communication between the main control board (VMC = Vending
16
 * Machine Controller) and peripheral components, e.g. a payment terminal
17
 * or a bill validator.
18
 *
19
 * The VMC acts as bus master and sends a request to one peripheral at a time.
20
 * A peripheral may send data only in response to such a request.
21
 *
22
 * The MDB specification is maintained by the National Automatic Merchandising
23
 * Association (NAMA). As of August 2023, the current version of the MDB
24
 * specification is 4.3. It is available from
25
 * https://namanow.org/nama-releases-mdb-version-4-3/
26
 *
27
 * The pcap input format for this dissector is documented at
28
 * https://www.kaiser.cx/pcap-mdb.html
29
 */
30
31
#include "config.h"
32
#include <epan/expert.h>
33
#include <epan/packet.h>
34
#include <epan/tfs.h>
35
#include <wsutil/array.h>
36
#include <wiretap/wtap.h>
37
38
void proto_register_mdb(void);
39
40
static dissector_handle_t mdb_handle;
41
42
static int proto_mdb;
43
44
static int ett_mdb;
45
static int ett_mdb_hdr;
46
static int ett_mdb_cl;
47
static int ett_mdb_cgw;
48
49
static int hf_mdb_hdr_ver;
50
static int hf_mdb_event;
51
static int hf_mdb_addr;
52
static int hf_mdb_cmd;
53
static int hf_mdb_cl_setup_sub;
54
static int hf_mdb_cl_feat_lvl;
55
static int hf_mdb_cl_cols;
56
static int hf_mdb_cl_rows;
57
static int hf_mdb_cl_disp_info;
58
static int hf_mdb_cl_max_price;
59
static int hf_mdb_cl_min_price;
60
static int hf_mdb_cl_vend_sub;
61
static int hf_mdb_cl_item_price;
62
static int hf_mdb_cl_item_num;
63
static int hf_mdb_cl_reader_sub;
64
static int hf_mdb_cl_resp;
65
static int hf_mdb_cl_scale;
66
static int hf_mdb_cl_dec_pl;
67
static int hf_mdb_cl_max_rsp_time;
68
static int hf_mdb_cl_vend_amt;
69
static int hf_mdb_cl_expns_sub;
70
static int hf_mdb_cl_manuf_code;
71
static int hf_mdb_cl_ser_num;
72
static int hf_mdb_cl_mod_num;
73
static int hf_mdb_cl_opt_feat;
74
static int hf_mdb_cgw_feat_lvl;
75
static int hf_mdb_cgw_scale;
76
static int hf_mdb_cgw_dec_pl;
77
static int hf_mdb_cgw_resp;
78
static int hf_mdb_cgw_max_rsp_time;
79
static int hf_mdb_cgw_report_sub;
80
static int hf_mdb_cgw_dts_evt_code;
81
static int hf_mdb_cgw_duration;
82
static int hf_mdb_cgw_activity;
83
static int hf_mdb_cgw_expns_sub;
84
static int hf_mdb_cgw_opt_feat;
85
static int hf_mdb_cgw_manuf_code;
86
static int hf_mdb_cgw_ser_num;
87
static int hf_mdb_cgw_mod_num;
88
static int hf_mdb_ack;
89
static int hf_mdb_data;
90
static int hf_mdb_chk;
91
92
static expert_field ei_mdb_short_packet;
93
94
0
#define MDB_EVT_DATA_MST_PER 0xFF
95
0
#define MDB_EVT_DATA_PER_MST 0xFE
96
0
#define MDB_EVT_BUS_RESET    0xFD
97
98
static const value_string mdb_event[] = {
99
    { MDB_EVT_DATA_MST_PER, "Data transfer Master -> Peripheral" },
100
    { MDB_EVT_DATA_PER_MST, "Data transfer Peripheral -> Master" },
101
    { MDB_EVT_BUS_RESET, "Bus reset" },
102
    { 0, NULL }
103
};
104
105
0
#define ADDR_VMC "VMC"
106
107
0
#define ADDR_CASHLESS1 0x10
108
0
#define ADDR_COMMS_GW  0x18
109
110
static const value_string mdb_addr[] = {
111
    { 0x08, "Changer" },
112
    { ADDR_CASHLESS1, "Cashless #1" },
113
    { ADDR_COMMS_GW, "Communications Gateway" },
114
    { 0x30, "Bill Validator" },
115
    { 0x60, "Cashless #2" },
116
    { 0x68, "Age Verification Device" },
117
    { 0, NULL }
118
};
119
120
static const value_string mdb_ack[] = {
121
    { 0x00, "ACK" },
122
    { 0xAA, "RET" },
123
    { 0xFF, "NAK" },
124
    { 0, NULL }
125
};
126
127
/*
128
 * These are just the command bits in the address + command byte. MDB supports
129
 * two Cashless peripherals (Cashless #1 and #2) with different addresses,
130
 * both use the same commands.
131
 */
132
0
#define MDB_CL_CMD_SETUP  0x01
133
0
#define MDB_CL_CMD_VEND   0x03
134
0
#define MDB_CL_CMD_READER 0x04
135
0
#define MDB_CL_CMD_EXPNS  0x07
136
137
static const value_string mdb_cl_cmd[] = {
138
    { 0x00, "Reset" },
139
    { MDB_CL_CMD_SETUP, "Setup" },
140
    { 0x02, "Poll" },
141
    { MDB_CL_CMD_VEND, "Vend" },
142
    { MDB_CL_CMD_READER, "Reader" },
143
    { MDB_CL_CMD_EXPNS, "Expansion" },
144
    { 0, NULL }
145
};
146
147
0
#define MDB_CL_SETUP_CFG_DATA 0x00
148
0
#define MDB_CL_SETUP_MAX_MIN  0x01
149
150
static const value_string mdb_cl_setup_sub_cmd[] = {
151
    { MDB_CL_SETUP_CFG_DATA, "Config Data" },
152
    { MDB_CL_SETUP_MAX_MIN, "Max/Min Prices" },
153
    { 0, NULL }
154
};
155
156
0
#define MDB_CL_VEND_REQ 0x00
157
0
#define MDB_CL_VEND_SUC 0x02
158
159
static const value_string mdb_cl_vend_sub_cmd[] = {
160
    { MDB_CL_VEND_REQ, "Vend Request" },
161
    { MDB_CL_VEND_SUC, "Vend Success" },
162
    { 0x04, "Session Complete" },
163
    { 0, NULL }
164
};
165
166
static const value_string mdb_cl_reader_sub_cmd[] = {
167
    { 0x00, "Reader Disable" },
168
    { 0x01, "Reader Enable" },
169
    { 0, NULL }
170
};
171
172
0
#define MDB_CL_EXPNS_REQ_ID  0x00
173
0
#define MDB_CL_EXPNS_OPT_ENA 0x04
174
175
static const value_string mdb_cl_expns_sub_cmd[] = {
176
    { MDB_CL_EXPNS_REQ_ID, "Request ID" },
177
    { MDB_CL_EXPNS_OPT_ENA, "Optional Feature Enabled" },
178
    { 0, NULL }
179
};
180
181
0
#define MDB_CL_RESP_RD_CFG_DATA 0x01
182
0
#define MDB_CL_RESP_VEND_APRV   0x05
183
0
#define MDB_CL_RESP_PER_ID      0x09
184
185
static const value_string mdb_cl_resp[] = {
186
    { 0x00, "Just Reset" },
187
    { MDB_CL_RESP_RD_CFG_DATA, "Reader Config Data" },
188
    { 0x03, "Begin Session" },
189
    { MDB_CL_RESP_VEND_APRV, "Vend Approved" },
190
    { 0x06, "Vend Denied" },
191
    { 0x07, "End Session" },
192
    { MDB_CL_RESP_PER_ID, "Peripheral ID" },
193
    { 0x0b, "Cmd Out Of Sequence" },
194
    { 0, NULL }
195
};
196
197
/*
198
 * For the Communications Gateway, we use the complete address + command byte
199
 * as value for the value string. The values here match those in the MDB
200
 * specification.
201
 *
202
 * There's only one Communications Gateway, the address bits are always the
203
 * same. (This is different from the Cashless peripherals, see above.)
204
 */
205
0
#define MDB_CGW_ADDR_CMD_SETUP  0x19
206
0
#define MDB_CGW_ADDR_CMD_REPORT 0x1B
207
0
#define MDB_CGW_ADDR_CMD_EXPNS  0x1F
208
209
static const value_string mdb_cgw_addr_cmd[] = {
210
    { 0x18, "Reset" },
211
    { MDB_CGW_ADDR_CMD_SETUP, "Setup" },
212
    { 0x1A, "Poll" },
213
    { MDB_CGW_ADDR_CMD_REPORT, "Report" },
214
    { MDB_CGW_ADDR_CMD_EXPNS, "Expansion" },
215
    { 0, NULL }
216
};
217
218
0
#define MDB_CGW_REPORT_DTS_EVT 0x02
219
220
static const value_string mdb_cgw_report_sub_cmd[] = {
221
    { 0x01, "Transaction" },
222
    { MDB_CGW_REPORT_DTS_EVT, "DTS Event" },
223
    { 0, NULL }
224
};
225
226
0
#define MDB_CGW_EXPNS_FEAT_ENA 0x01
227
228
static const value_string mdb_cgw_expns_sub_cmd[] = {
229
    { 0x00, "Identification" },
230
    { MDB_CGW_EXPNS_FEAT_ENA, "Feature enable" },
231
    { 0x02, "Time/Date Request" },
232
    { 0, NULL }
233
};
234
235
0
#define MDB_CGW_RESP_CFG    0x01
236
0
#define MDB_CGW_RESP_PER_ID 0x06
237
238
static const value_string mdb_cgw_resp[] = {
239
    { 0x00, "Just Reset" },
240
    { MDB_CGW_RESP_CFG, "Comms Gateway Config" },
241
    { 0x05, "DTS Event Acknowledge" },
242
    { MDB_CGW_RESP_PER_ID, "Peripheral ID" },
243
    { 0, NULL }
244
};
245
246
static void dissect_mdb_ack(tvbuff_t *tvb, int offset,
247
        packet_info *pinfo, proto_tree *tree)
248
0
{
249
0
    uint32_t ack;
250
251
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_ack, tvb, offset, 1,
252
0
                ENC_BIG_ENDIAN, &ack);
253
0
    col_set_str(pinfo->cinfo, COL_INFO,
254
0
            val_to_str_const(ack, mdb_ack, "Invalid ack byte"));
255
0
}
256
257
static void mdb_set_addrs(uint8_t event, uint8_t addr, packet_info *pinfo)
258
0
{
259
0
    const char *periph = val_to_str(addr, mdb_addr, "Unknown (0x%02x)");
260
261
    /* pinfo->p2p_dir is from the perspective of the master (VMC) */
262
263
0
    if (event == MDB_EVT_DATA_MST_PER) {
264
0
        set_address(&pinfo->src, AT_STRINGZ, (int)strlen(ADDR_VMC)+1, ADDR_VMC);
265
0
        set_address(&pinfo->dst, AT_STRINGZ, (int)strlen(periph)+1, periph);
266
0
        pinfo->p2p_dir = P2P_DIR_SENT;
267
0
    }
268
0
    else if (event == MDB_EVT_DATA_PER_MST) {
269
0
        set_address(&pinfo->src, AT_STRINGZ, (int)strlen(periph)+1, periph);
270
0
        set_address(&pinfo->dst, AT_STRINGZ, (int)strlen(ADDR_VMC)+1, ADDR_VMC);
271
0
        pinfo->p2p_dir = P2P_DIR_RECV;
272
0
    }
273
0
}
274
275
static void dissect_mdb_cl_setup(tvbuff_t *tvb, int offset,
276
        packet_info *pinfo, proto_tree *tree)
277
0
{
278
0
    uint32_t sub_cmd, price;
279
0
    const char *s;
280
0
    proto_item *pi;
281
282
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_cl_setup_sub,
283
0
                    tvb, offset, 1, ENC_BIG_ENDIAN, &sub_cmd);
284
0
    s = try_val_to_str(sub_cmd, mdb_cl_setup_sub_cmd);
285
0
    if (s) {
286
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
287
0
    }
288
0
    offset++;
289
290
0
    switch (sub_cmd) {
291
0
        case MDB_CL_SETUP_CFG_DATA:
292
0
            proto_tree_add_item(tree, hf_mdb_cl_feat_lvl, tvb, offset, 1,
293
0
                    ENC_BIG_ENDIAN);
294
0
            offset++;
295
0
            proto_tree_add_item(tree, hf_mdb_cl_cols, tvb, offset, 1,
296
0
                    ENC_BIG_ENDIAN);
297
0
            offset++;
298
0
            proto_tree_add_item(tree, hf_mdb_cl_rows, tvb, offset, 1,
299
0
                    ENC_BIG_ENDIAN);
300
0
            offset++;
301
0
            proto_tree_add_item(tree, hf_mdb_cl_disp_info, tvb, offset, 1,
302
0
                    ENC_BIG_ENDIAN);
303
0
            break;
304
305
0
        case MDB_CL_SETUP_MAX_MIN:
306
0
            if (tvb_reported_length_remaining(tvb, offset) == 5) {
307
                /* This is the "default version" of Max/Min Prices. */
308
309
                /* XXX - convert the scaled prices into actual amounts */
310
0
                price = tvb_get_ntohs(tvb, offset);
311
0
                pi = proto_tree_add_uint_format(tree, hf_mdb_cl_max_price,
312
0
                        tvb, offset, 2, price, "Maximum price: 0x%04x", price);
313
0
                if (price == 0xFFFF) {
314
0
                    proto_item_append_text(pi, " (unknown)");
315
0
                }
316
0
                offset += 2;
317
318
0
                price = tvb_get_ntohs(tvb, offset);
319
0
                pi = proto_tree_add_uint_format(tree, hf_mdb_cl_min_price,
320
0
                        tvb, offset, 2, price, "Minimum price: 0x%04x", price);
321
0
                if (price == 0x0000) {
322
0
                    proto_item_append_text(pi, " (unknown)");
323
0
                }
324
0
            }
325
0
            else if (tvb_reported_length_remaining(tvb, offset) == 11) {
326
                /* This is the "expanded currency version" of Max/Min Prices. */
327
328
0
                proto_tree_add_item(tree, hf_mdb_cl_max_price, tvb, offset, 4,
329
0
                        ENC_BIG_ENDIAN);
330
0
                offset += 4;
331
0
                proto_tree_add_item(tree, hf_mdb_cl_min_price, tvb, offset, 4,
332
0
                        ENC_BIG_ENDIAN);
333
0
            }
334
            /* XXX - expert info for other lengths */
335
0
            break;
336
0
    }
337
0
}
338
339
static void dissect_mdb_cl_vend(tvbuff_t *tvb, int offset,
340
        packet_info *pinfo, proto_tree *tree)
341
0
{
342
0
    uint32_t sub_cmd, price, item;
343
0
    const char *s;
344
345
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_cl_vend_sub, tvb, offset, 1,
346
0
            ENC_BIG_ENDIAN, &sub_cmd);
347
0
    s = try_val_to_str(sub_cmd, mdb_cl_vend_sub_cmd);
348
0
    if (s) {
349
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
350
0
    }
351
0
    offset++;
352
353
0
    switch (sub_cmd) {
354
0
        case MDB_CL_VEND_REQ:
355
0
            if (tvb_reported_length_remaining(tvb, offset) == 5) {
356
0
                proto_tree_add_item_ret_uint(tree, hf_mdb_cl_item_price, tvb,
357
0
                        offset, 2, ENC_BIG_ENDIAN, &price);
358
0
                offset += 2;
359
0
                proto_tree_add_item_ret_uint(tree, hf_mdb_cl_item_num, tvb,
360
0
                        offset, 2, ENC_BIG_ENDIAN, &item);
361
0
                col_append_fstr(pinfo->cinfo, COL_INFO, " (item %d, price %d)",
362
0
                        item, price);
363
0
            }
364
            /* XXX - dissect the longer request in Expanded Currency Mode */
365
0
            break;
366
0
        case MDB_CL_VEND_SUC:
367
0
                proto_tree_add_item(tree, hf_mdb_cl_item_num, tvb, offset, 2,
368
0
                        ENC_BIG_ENDIAN);
369
0
            break;
370
0
    }
371
0
}
372
373
static int
374
dissect_mdb_cl_id_fields(tvbuff_t *tvb, int offset, proto_tree *tree)
375
0
{
376
0
    proto_tree_add_item(tree, hf_mdb_cl_manuf_code, tvb, offset, 3, ENC_ASCII);
377
0
    offset += 3;
378
0
    proto_tree_add_item(tree, hf_mdb_cl_ser_num, tvb, offset, 12, ENC_ASCII);
379
0
    offset += 12;
380
0
    proto_tree_add_item(tree, hf_mdb_cl_mod_num, tvb, offset, 12, ENC_ASCII);
381
0
    offset += 12;
382
    /* XXX - dissect the Software Version bytes */
383
0
    offset += 2;
384
385
0
    return offset;
386
0
}
387
388
static void dissect_mdb_cl_expns(tvbuff_t *tvb, int offset, packet_info *pinfo,
389
        proto_tree *tree)
390
0
{
391
0
    uint32_t sub_cmd;
392
0
    const char *s;
393
394
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_cl_expns_sub,
395
0
                    tvb, offset, 1, ENC_BIG_ENDIAN, &sub_cmd);
396
0
    s = try_val_to_str(sub_cmd, mdb_cl_expns_sub_cmd);
397
0
    if (s) {
398
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
399
0
    }
400
0
    offset++;
401
402
0
    switch (sub_cmd) {
403
0
        case MDB_CL_EXPNS_REQ_ID:
404
0
            dissect_mdb_cl_id_fields(tvb, offset, tree);
405
0
            break;
406
0
        case MDB_CL_EXPNS_OPT_ENA:
407
            /* XXX - add a bitmask for the Optional Feature Bits */
408
0
            proto_tree_add_item(tree, hf_mdb_cl_opt_feat, tvb, offset, 4,
409
0
                    ENC_BIG_ENDIAN);
410
0
            break;
411
0
    }
412
0
}
413
414
static void dissect_mdb_cl_rd_cfg_data(tvbuff_t *tvb, int offset,
415
        packet_info *pinfo _U_, proto_tree *tree)
416
0
{
417
0
    proto_tree_add_item(tree, hf_mdb_cl_feat_lvl, tvb, offset, 1,
418
0
            ENC_BIG_ENDIAN);
419
0
    offset++;
420
    /* XXX - dissect Country/Currency Code */
421
0
    offset += 2;
422
0
    proto_tree_add_item(tree, hf_mdb_cl_scale, tvb, offset, 1, ENC_BIG_ENDIAN);
423
0
    offset++;
424
0
    proto_tree_add_item(tree, hf_mdb_cl_dec_pl, tvb, offset, 1, ENC_BIG_ENDIAN);
425
0
    offset++;
426
0
    proto_tree_add_item(tree, hf_mdb_cl_max_rsp_time, tvb, offset, 1,
427
0
            ENC_TIME_SECS | ENC_BIG_ENDIAN);
428
0
}
429
430
static void dissect_mdb_mst_per_cl( tvbuff_t *tvb, int offset, int len _U_,
431
        packet_info *pinfo, proto_tree *tree, proto_item *cmd_it,
432
        uint8_t addr_byte)
433
0
{
434
0
    uint8_t cmd = addr_byte & 0x07; /* the 3-bit command */
435
0
    proto_tree *cl_tree;
436
0
    uint32_t sub_cmd;
437
0
    const char *s;
438
439
0
    s = val_to_str_const(cmd, mdb_cl_cmd, "Unknown");
440
0
    proto_item_append_text(cmd_it, " (%s)", s);
441
0
    col_set_str(pinfo->cinfo, COL_INFO, s);
442
443
0
    cl_tree = proto_tree_add_subtree(tree, tvb, offset, len, ett_mdb_cl,
444
0
            NULL, "Cashless");
445
446
0
    s = NULL;
447
0
    switch (cmd) {
448
0
        case MDB_CL_CMD_SETUP:
449
0
            dissect_mdb_cl_setup(tvb, offset, pinfo, cl_tree);
450
0
            break;
451
0
        case MDB_CL_CMD_VEND:
452
0
            dissect_mdb_cl_vend(tvb, offset, pinfo, cl_tree);
453
0
            break;
454
0
        case MDB_CL_CMD_READER:
455
0
            proto_tree_add_item_ret_uint(cl_tree, hf_mdb_cl_reader_sub,
456
0
                    tvb, offset, 1, ENC_BIG_ENDIAN, &sub_cmd);
457
0
            s = try_val_to_str(sub_cmd, mdb_cl_reader_sub_cmd);
458
0
            break;
459
0
        case MDB_CL_CMD_EXPNS:
460
0
            dissect_mdb_cl_expns(tvb, offset, pinfo, cl_tree);
461
0
            break;
462
0
    }
463
0
    if (s)
464
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
465
0
}
466
467
static void dissect_mdb_per_mst_cl( tvbuff_t *tvb, int offset,
468
        int len _U_, packet_info *pinfo, proto_tree *tree)
469
0
{
470
0
    proto_tree *cl_tree;
471
0
    uint32_t cl_resp;
472
473
0
    cl_tree = proto_tree_add_subtree(tree, tvb, offset, len, ett_mdb_cl,
474
0
            NULL, "Cashless");
475
476
0
    proto_tree_add_item_ret_uint(cl_tree, hf_mdb_cl_resp, tvb, offset, 1,
477
0
            ENC_BIG_ENDIAN, &cl_resp);
478
0
    col_set_str(pinfo->cinfo,
479
0
            COL_INFO, val_to_str_const(cl_resp, mdb_cl_resp, "Unknown"));
480
0
    offset++;
481
482
0
    switch (cl_resp) {
483
0
        case MDB_CL_RESP_RD_CFG_DATA:
484
0
            dissect_mdb_cl_rd_cfg_data(tvb, offset, pinfo, cl_tree);
485
0
            break;
486
0
        case MDB_CL_RESP_VEND_APRV:
487
0
            if (tvb_reported_length_remaining(tvb, offset) == 3) {
488
0
                proto_tree_add_item(cl_tree, hf_mdb_cl_vend_amt, tvb, offset,
489
0
                        2, ENC_BIG_ENDIAN);
490
0
            }
491
            /* XXX - dissect the longer response in Expanded Currency Mode */
492
0
            break;
493
0
        case MDB_CL_RESP_PER_ID:
494
0
            dissect_mdb_cl_id_fields(tvb, offset, tree);
495
            /* XXX - check if we have Optional Feature Bits */
496
0
            break;
497
0
    }
498
0
}
499
500
static void dissect_mdb_cgw_report(tvbuff_t *tvb, int offset,
501
        packet_info *pinfo, proto_tree *tree)
502
0
{
503
0
    uint32_t sub_cmd;
504
0
    const char *s;
505
506
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_cgw_report_sub,
507
0
                    tvb, offset, 1, ENC_BIG_ENDIAN, &sub_cmd);
508
0
    s = try_val_to_str(sub_cmd, mdb_cgw_report_sub_cmd);
509
0
    if (s) {
510
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
511
0
    }
512
0
    offset++;
513
514
0
    switch (sub_cmd) {
515
0
        case MDB_CGW_REPORT_DTS_EVT:
516
0
            proto_tree_add_item(tree, hf_mdb_cgw_dts_evt_code, tvb, offset, 10,
517
0
                    ENC_ASCII);
518
0
            offset += 10;
519
            /* XXX - dissect Date */
520
0
            offset += 4;
521
            /* XXX - dissect Time */
522
0
            offset += 2;
523
0
            proto_tree_add_item(tree, hf_mdb_cgw_duration, tvb, offset, 4,
524
0
                    ENC_BIG_ENDIAN);
525
0
            offset += 4;
526
0
            proto_tree_add_item(tree, hf_mdb_cgw_activity, tvb, offset, 1,
527
0
                    ENC_BIG_ENDIAN);
528
0
            break;
529
0
    }
530
0
}
531
532
static void dissect_mdb_cgw_expns(tvbuff_t *tvb, int offset,
533
        packet_info *pinfo, proto_tree *tree)
534
0
{
535
0
    uint32_t sub_cmd;
536
0
    const char *s;
537
538
0
    proto_tree_add_item_ret_uint(tree, hf_mdb_cgw_expns_sub,
539
0
                    tvb, offset, 1, ENC_BIG_ENDIAN, &sub_cmd);
540
0
    s = try_val_to_str(sub_cmd, mdb_cgw_expns_sub_cmd);
541
0
    if (s) {
542
0
        col_set_str(pinfo->cinfo, COL_INFO, s);
543
0
    }
544
0
    offset++;
545
546
0
    switch (sub_cmd) {
547
0
        case MDB_CGW_EXPNS_FEAT_ENA:
548
0
            proto_tree_add_item(tree, hf_mdb_cgw_opt_feat, tvb, offset, 4,
549
0
                    ENC_BIG_ENDIAN);
550
0
            break;
551
0
    }
552
0
}
553
554
static void dissect_mdb_mst_per_cgw( tvbuff_t *tvb, int offset, int len,
555
        packet_info *pinfo, proto_tree *tree, proto_item *cmd_it,
556
        uint8_t addr_cmd_byte)
557
0
{
558
0
    proto_tree *cgw_tree;
559
0
    const char *s;
560
561
0
    s = val_to_str_const(addr_cmd_byte, mdb_cgw_addr_cmd, "Unknown");
562
0
    proto_item_append_text(cmd_it, " (%s)", s);
563
0
    col_set_str(pinfo->cinfo, COL_INFO, s);
564
565
0
    cgw_tree = proto_tree_add_subtree(tree, tvb, offset, len, ett_mdb_cgw,
566
0
            NULL, "Communications Gateway");
567
568
0
    switch (addr_cmd_byte) {
569
0
        case MDB_CGW_ADDR_CMD_SETUP:
570
0
            proto_tree_add_item(cgw_tree, hf_mdb_cgw_feat_lvl, tvb, offset, 1,
571
0
                    ENC_BIG_ENDIAN);
572
0
            offset++;
573
0
            proto_tree_add_item(cgw_tree, hf_mdb_cgw_scale, tvb, offset, 1,
574
0
                    ENC_BIG_ENDIAN);
575
0
            offset++;
576
0
            proto_tree_add_item(cgw_tree, hf_mdb_cgw_dec_pl, tvb, offset, 1,
577
0
                    ENC_BIG_ENDIAN);
578
0
            break;
579
0
        case MDB_CGW_ADDR_CMD_REPORT:
580
0
            dissect_mdb_cgw_report(tvb, offset, pinfo, cgw_tree);
581
0
            break;
582
0
        case MDB_CGW_ADDR_CMD_EXPNS:
583
0
            dissect_mdb_cgw_expns(tvb, offset, pinfo, cgw_tree);
584
0
            break;
585
0
    }
586
0
}
587
588
static void dissect_mdb_per_mst_cgw( tvbuff_t *tvb, int offset,
589
        int len, packet_info *pinfo _U_, proto_tree *tree)
590
0
{
591
0
    proto_tree *cgw_tree;
592
0
    uint32_t cgw_resp;
593
594
0
    cgw_tree = proto_tree_add_subtree(tree, tvb, offset, len, ett_mdb_cgw,
595
0
            NULL, "Communications Gateway");
596
597
0
    proto_tree_add_item_ret_uint(cgw_tree, hf_mdb_cgw_resp, tvb, offset, 1,
598
0
            ENC_BIG_ENDIAN, &cgw_resp);
599
0
    col_set_str(pinfo->cinfo,
600
0
            COL_INFO, val_to_str_const(cgw_resp, mdb_cgw_resp, "Unknown"));
601
0
    offset++;
602
603
0
    switch (cgw_resp) {
604
0
        case MDB_CGW_RESP_CFG:
605
0
            proto_tree_add_item(cgw_tree, hf_mdb_cgw_feat_lvl, tvb, offset, 1,
606
0
                    ENC_BIG_ENDIAN);
607
0
            offset++;
608
0
            proto_tree_add_item(cgw_tree, hf_mdb_cgw_max_rsp_time, tvb, offset,
609
0
                    2, ENC_TIME_SECS | ENC_BIG_ENDIAN);
610
0
            break;
611
0
        case MDB_CGW_RESP_PER_ID:
612
0
            proto_tree_add_item(tree, hf_mdb_cgw_manuf_code, tvb, offset, 3,
613
0
                    ENC_ASCII);
614
0
            offset += 3;
615
0
            proto_tree_add_item(tree, hf_mdb_cgw_ser_num, tvb, offset, 12,
616
0
                    ENC_ASCII);
617
0
            offset += 12;
618
0
            proto_tree_add_item(tree, hf_mdb_cgw_mod_num, tvb, offset, 12,
619
0
                    ENC_ASCII);
620
0
            offset += 12;
621
            /* XXX - dissect the Software Version bytes */
622
0
            offset += 2;
623
0
            proto_tree_add_item(tree, hf_mdb_cgw_opt_feat, tvb, offset, 4,
624
0
                    ENC_BIG_ENDIAN);
625
0
            break;
626
0
    }
627
0
}
628
629
static void dissect_mdb_mst_per(tvbuff_t *tvb, int offset, packet_info *pinfo,
630
        proto_tree *tree)
631
0
{
632
0
    uint8_t addr_byte, addr;
633
0
    int mst_per_len;
634
0
    unsigned data_len;
635
0
    proto_item *cmd_it;
636
637
0
    mst_per_len = tvb_reported_length_remaining(tvb, offset);
638
0
    if (mst_per_len <= 0) {
639
0
        expert_add_info(pinfo, tree, &ei_mdb_short_packet);
640
0
        return;
641
0
    }
642
643
0
    if (mst_per_len == 1) {
644
0
        dissect_mdb_ack(tvb, offset, pinfo, tree);
645
0
        return;
646
0
    }
647
648
    /*
649
     * Our packet has one address byte, an optional data block and one
650
     * checksum byte.
651
     */
652
653
0
    data_len = mst_per_len - 2;
654
655
    /*
656
     * The address byte is 5-bit address | 3-bit command.
657
     *
658
     * The specification uses 8-bit addresses which are the address byte
659
     * with the three lowest bits set to 0.
660
     *
661
     * The commands are defined as the complete address byte (i.e. they
662
     * include the address part). This does not make much sense: Cashless #1
663
     * and #2 have different addresses but exactly the same 3-bit commands.
664
     *
665
     * In this dissector, we try to use the same values as the specification.
666
     */
667
0
    addr_byte = tvb_get_uint8(tvb, offset);
668
0
    addr = addr_byte & 0xF8;
669
0
    proto_tree_add_uint_bits_format_value(tree, hf_mdb_addr,
670
0
            tvb, 8*offset, 5, addr, ENC_BIG_ENDIAN, "0x%02x", addr);
671
0
    cmd_it = proto_tree_add_uint(tree, hf_mdb_cmd, tvb, offset, 1, addr_byte);
672
0
    mdb_set_addrs(MDB_EVT_DATA_MST_PER, addr, pinfo);
673
0
    offset++;
674
675
    /*
676
     * We call the peripheral functions even if data_len == 0 so they can fix
677
     * up the command with peripheral-specific info.
678
     */
679
0
    switch (addr) {
680
0
        case ADDR_CASHLESS1:
681
0
            dissect_mdb_mst_per_cl(tvb, offset, data_len, pinfo, tree,
682
0
                    cmd_it, addr_byte);
683
0
            break;
684
0
        case ADDR_COMMS_GW:
685
0
            dissect_mdb_mst_per_cgw(tvb, offset, data_len, pinfo, tree,
686
0
                    cmd_it, addr_byte);
687
0
            break;
688
0
        default:
689
0
            if (data_len > 0) {
690
0
                proto_tree_add_item(tree, hf_mdb_data,
691
0
                        tvb, offset, data_len, ENC_NA);
692
0
            }
693
0
            break;
694
0
    }
695
0
    offset += data_len;
696
697
    /* XXX - verify the checksum */
698
0
    proto_tree_add_item(tree, hf_mdb_chk, tvb, offset, 1, ENC_BIG_ENDIAN);
699
0
}
700
701
static void dissect_mdb_per_mst(tvbuff_t *tvb, int offset, packet_info *pinfo,
702
        proto_tree *tree, uint8_t addr)
703
0
{
704
0
    int per_mst_len;
705
0
    unsigned data_len;
706
707
    /*
708
     * A packet from peripheral to master is either a single ACK/NAK byte or
709
     * a non-empty data block followed by one checksum byte.
710
     */
711
712
0
    per_mst_len = tvb_reported_length_remaining(tvb, offset);
713
0
    if (per_mst_len <= 0) {
714
0
        expert_add_info(pinfo, tree, &ei_mdb_short_packet);
715
0
        return;
716
0
    }
717
718
0
    if (per_mst_len == 1) {
719
0
        dissect_mdb_ack(tvb, offset, pinfo, tree);
720
0
        return;
721
0
    }
722
723
0
    data_len = per_mst_len - 1;
724
0
    switch (addr) {
725
0
        case ADDR_CASHLESS1:
726
0
            dissect_mdb_per_mst_cl(tvb, offset, data_len, pinfo, tree);
727
0
            break;
728
0
        case ADDR_COMMS_GW:
729
0
            dissect_mdb_per_mst_cgw(tvb, offset, data_len, pinfo, tree);
730
0
            break;
731
0
        default:
732
0
            proto_tree_add_item(tree, hf_mdb_data, tvb, offset, data_len, ENC_NA);
733
0
            break;
734
0
    }
735
0
    offset += data_len;
736
737
    /* XXX - verify the checksum */
738
0
    proto_tree_add_item(tree, hf_mdb_chk, tvb, offset, 1, ENC_BIG_ENDIAN);
739
0
}
740
741
static int dissect_mdb(tvbuff_t *tvb,
742
        packet_info *pinfo, proto_tree *tree, void *data _U_)
743
0
{
744
0
    int offset = 0, offset_ver, offset_evt;
745
0
    uint8_t version, event, addr;
746
0
    proto_tree *mdb_tree, *hdr_tree;
747
0
    proto_item *tree_ti, *hdr_ti;
748
749
    /* We need at least the shortest possible pseudo header. */
750
0
    if (tvb_captured_length(tvb) < 3)
751
0
        return 0;
752
753
0
    offset_ver = offset;
754
0
    version = tvb_get_uint8(tvb, offset++);
755
0
    if (version != 0)
756
0
        return 0;
757
758
0
    offset_evt = offset;
759
0
    event = tvb_get_uint8(tvb, offset++);
760
0
    if (!try_val_to_str(event, mdb_event))
761
0
        return 0;
762
763
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "MDB");
764
0
    col_clear(pinfo->cinfo, COL_INFO);
765
766
0
    tree_ti = proto_tree_add_protocol_format(tree, proto_mdb,
767
0
            tvb, 0, tvb_reported_length(tvb), "MDB");
768
0
    mdb_tree = proto_item_add_subtree(tree_ti, ett_mdb);
769
770
0
    hdr_tree = proto_tree_add_subtree(mdb_tree, tvb, 0, -1, ett_mdb_hdr,
771
0
            &hdr_ti, "Pseudo header");
772
773
0
    proto_tree_add_item(hdr_tree, hf_mdb_hdr_ver,
774
0
            tvb, offset_ver, 1, ENC_BIG_ENDIAN);
775
0
    proto_tree_add_item(hdr_tree, hf_mdb_event,
776
0
            tvb, offset_evt, 1, ENC_BIG_ENDIAN);
777
778
    /* Packets from peripheral to master always have an address byte in their
779
       pseudo header. */
780
0
    if (event == MDB_EVT_DATA_PER_MST) {
781
        /* See the comment in dissect_mdb_mst_per about MDB addresses. */
782
0
        addr = tvb_get_uint8(tvb, offset) & 0xF8;
783
0
        proto_tree_add_uint_bits_format_value(hdr_tree, hf_mdb_addr,
784
0
                tvb, 8*offset, 5, addr, ENC_BIG_ENDIAN, "0x%02x", addr);
785
0
        offset++;
786
0
        mdb_set_addrs(event, addr, pinfo);
787
0
    }
788
789
    /* We're now at the end of the pseudo header. */
790
0
    proto_item_set_len(hdr_ti, offset);
791
792
0
    if (event == MDB_EVT_BUS_RESET)
793
0
        return offset;
794
795
0
    if (event == MDB_EVT_DATA_MST_PER)
796
0
        dissect_mdb_mst_per(tvb, offset, pinfo, mdb_tree);
797
0
    else if (event == MDB_EVT_DATA_PER_MST)
798
0
        dissect_mdb_per_mst(tvb, offset, pinfo, mdb_tree, addr);
799
800
0
    return tvb_reported_length(tvb);
801
0
}
802
803
void proto_register_mdb(void)
804
14
{
805
14
    expert_module_t* expert_mdb;
806
807
14
    static int *ett[] = {
808
14
        &ett_mdb,
809
14
        &ett_mdb_hdr,
810
14
        &ett_mdb_cl,
811
14
        &ett_mdb_cgw
812
14
    };
813
814
14
    static hf_register_info hf[] = {
815
14
        { &hf_mdb_hdr_ver,
816
14
            { "Version", "mdb.hdr_ver",
817
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
818
14
        },
819
14
        { &hf_mdb_event,
820
14
            { "Event", "mdb.event",
821
14
                FT_UINT8, BASE_HEX, VALS(mdb_event), 0, NULL, HFILL }
822
14
        },
823
14
        { &hf_mdb_addr,
824
14
            { "Address", "mdb.addr",
825
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
826
14
        },
827
14
        { &hf_mdb_cmd,
828
14
            { "Command", "mdb.cmd",
829
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
830
14
        },
831
14
        { &hf_mdb_cl_setup_sub,
832
14
            { "Sub-command", "mdb.cashless.setup_sub_cmd",
833
14
                FT_UINT8, BASE_HEX, VALS(mdb_cl_setup_sub_cmd), 0, NULL, HFILL }
834
14
        },
835
14
        { &hf_mdb_cl_feat_lvl,
836
14
            { "Feature level", "mdb.cashless.feature_level",
837
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
838
14
        },
839
14
        { &hf_mdb_cl_cols,
840
14
            { "Columns on display", "mdb.cashless.columns",
841
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
842
14
        },
843
14
        { &hf_mdb_cl_rows,
844
14
            { "Rows on display", "mdb.cashless.rows",
845
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
846
14
        },
847
14
        { &hf_mdb_cl_disp_info,
848
14
            { "Display information", "mdb.cashless.disp_info",
849
14
                FT_UINT8, BASE_HEX, NULL, 0x07, NULL, HFILL }
850
14
        },
851
14
        { &hf_mdb_cl_max_price,
852
14
            { "Maximum price", "mdb.cashless.max_price",
853
14
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
854
14
        },
855
14
        { &hf_mdb_cl_min_price,
856
14
            { "Minimum price", "mdb.cashless.min_price",
857
14
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
858
14
        },
859
14
        { &hf_mdb_cl_vend_sub,
860
14
            { "Sub-command", "mdb.cashless.vend_sub_cmd",
861
14
                FT_UINT8, BASE_HEX, VALS(mdb_cl_vend_sub_cmd), 0, NULL, HFILL }
862
14
        },
863
14
        { &hf_mdb_cl_item_price,
864
14
            { "Item Price", "mdb.cashless.item_price",
865
14
                FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
866
14
        },
867
14
        { &hf_mdb_cl_item_num,
868
14
            { "Item Number", "mdb.cashless.item_number",
869
14
                FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
870
14
        },
871
14
        { &hf_mdb_cl_reader_sub,
872
14
            { "Sub-command", "mdb.cashless.reader_sub_cmd",
873
14
                FT_UINT8, BASE_HEX, VALS(mdb_cl_reader_sub_cmd), 0, NULL, HFILL }
874
14
        },
875
14
        { &hf_mdb_cl_resp,
876
14
            { "Response", "mdb.cashless.resp",
877
14
                FT_UINT8, BASE_HEX, VALS(mdb_cl_resp), 0, NULL, HFILL }
878
14
        },
879
14
        { &hf_mdb_cl_scale,
880
14
            { "Scale factor", "mdb.cashless.scale_factor",
881
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
882
14
        },
883
14
        { &hf_mdb_cl_dec_pl,
884
14
            { "Decimal places", "mdb.cashless.decimal_places",
885
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
886
14
        },
887
14
        { &hf_mdb_cl_max_rsp_time,
888
14
            { "Application maximum response time", "mdb.cashless.max_rsp_time",
889
14
                FT_RELATIVE_TIME, BASE_NONE, NULL, 0, NULL, HFILL }
890
14
        },
891
14
        { &hf_mdb_cl_vend_amt,
892
14
            { "Vend Amount", "mdb.cashless.vend_amount",
893
14
                FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
894
14
        },
895
14
        { &hf_mdb_cl_expns_sub,
896
14
            { "Sub-command", "mdb.cashless.expansion_sub_cmd",
897
14
                FT_UINT8, BASE_HEX, VALS(mdb_cl_expns_sub_cmd), 0, NULL, HFILL }
898
14
        },
899
14
        { &hf_mdb_cl_manuf_code,
900
14
            { "Manufacturer Code", "mdb.cashless.manuf_code",
901
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
902
14
        },
903
14
        { &hf_mdb_cl_ser_num,
904
14
            { "Serial Number", "mdb.cashless.serial_number",
905
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
906
14
        },
907
14
        { &hf_mdb_cl_mod_num,
908
14
            { "Model Number", "mdb.cashless.model_number",
909
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
910
14
        },
911
14
        { &hf_mdb_cl_opt_feat,
912
14
            { "Optional Feature Bits", "mdb.cashless.opt_feature_bits",
913
14
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
914
14
        },
915
14
        { &hf_mdb_cgw_feat_lvl,
916
14
            { "Feature level", "mdb.comms_gw.feature_level",
917
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
918
14
        },
919
14
        { &hf_mdb_cgw_scale,
920
14
            { "Scale factor", "mdb.comms_gw.scale_factor",
921
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
922
14
        },
923
14
        { &hf_mdb_cgw_dec_pl,
924
14
            { "Decimal places", "mdb.comms_gw.decimal_places",
925
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
926
14
        },
927
14
        { &hf_mdb_cgw_resp,
928
14
            { "Response", "mdb.comms_gw.resp",
929
14
                FT_UINT8, BASE_HEX, VALS(mdb_cgw_resp), 0, NULL, HFILL }
930
14
        },
931
14
        { &hf_mdb_cgw_max_rsp_time,
932
14
            { "Application maximum response time", "mdb.comms_gw.max_rsp_time",
933
14
                FT_RELATIVE_TIME, BASE_NONE, NULL, 0, NULL, HFILL }
934
14
        },
935
14
        { &hf_mdb_cgw_report_sub,
936
14
            { "Sub-command", "mdb.comms_gw.report_sub_cmd", FT_UINT8,
937
14
                BASE_HEX, VALS(mdb_cgw_report_sub_cmd), 0, NULL, HFILL }
938
14
        },
939
14
        { &hf_mdb_cgw_dts_evt_code,
940
14
            { "DTS Event Code", "mdb.comms_gw.dts_event_code",
941
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
942
14
        },
943
14
        { &hf_mdb_cgw_duration,
944
14
            { "Duration", "mdb.comms_gw.duration",
945
14
                FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL }
946
14
        },
947
14
        { &hf_mdb_cgw_activity,
948
14
            { "Activity", "mdb.comms_gw.activity",
949
14
                FT_BOOLEAN, 8, TFS(&tfs_active_inactive), 0x1, NULL, HFILL }
950
14
        },
951
14
        { &hf_mdb_cgw_expns_sub,
952
14
            { "Sub-command", "mdb.comms_gw.expansion_sub_cmd", FT_UINT8,
953
14
                BASE_HEX, VALS(mdb_cgw_expns_sub_cmd), 0, NULL, HFILL }
954
14
        },
955
14
        { &hf_mdb_cgw_opt_feat,
956
14
            { "Optional Feature Bits", "mdb.comms_gw.opt_feature_bits",
957
14
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
958
14
        },
959
14
        { &hf_mdb_cgw_manuf_code,
960
14
            { "Manufacturer Code", "mdb.comms_gw.manuf_code",
961
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
962
14
        },
963
14
        { &hf_mdb_cgw_ser_num,
964
14
            { "Serial Number", "mdb.comms_gw.serial_number",
965
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
966
14
        },
967
14
        { &hf_mdb_cgw_mod_num,
968
14
            { "Model Number", "mdb.comms_gw.model_number",
969
14
                FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
970
14
        },
971
14
        { &hf_mdb_ack,
972
14
            { "Ack byte", "mdb.ack",
973
14
                FT_UINT8, BASE_HEX, VALS(mdb_ack), 0, NULL, HFILL }
974
14
        },
975
14
        { &hf_mdb_data,
976
14
            { "Data", "mdb.data",
977
14
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
978
14
        },
979
14
        { &hf_mdb_chk,
980
14
            { "Checksum", "mdb.chk",
981
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
982
14
        }
983
14
    };
984
985
14
    static ei_register_info ei[] = {
986
14
        { &ei_mdb_short_packet,
987
14
            { "mdb.short_packet", PI_PROTOCOL, PI_ERROR,
988
14
                "MDB packet without payload", EXPFILL }}
989
14
    };
990
991
14
    proto_mdb = proto_register_protocol("Multi-Drop Bus", "MDB", "mdb");
992
14
    proto_register_subtree_array(ett, array_length(ett));
993
14
    proto_register_field_array(proto_mdb, hf, array_length(hf));
994
14
    expert_mdb = expert_register_protocol(proto_mdb);
995
14
    expert_register_field_array(expert_mdb, ei, array_length(ei));
996
14
    mdb_handle = register_dissector("mdb", dissect_mdb, proto_mdb);
997
14
}
998
999
void proto_reg_handoff_mdb(void)
1000
14
{
1001
14
    dissector_add_uint("wtap_encap", WTAP_ENCAP_MDB, mdb_handle);
1002
14
}
1003
1004
/*
1005
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1006
 *
1007
 * Local variables:
1008
 * c-basic-offset: 4
1009
 * tab-width: 8
1010
 * indent-tabs-mode: nil
1011
 * End:
1012
 *
1013
 * vi: set shiftwidth=4 tabstop=8 expandtab:
1014
 * :indentSize=4:tabSize=8:noTabs=true:
1015
 */