Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/file-mmodule.c
Line
Count
Source
1
/* file-mmodule.c
2
 *
3
 * M‑Module file handler dissector for .m files produced by Bachmann tooling
4
 *
5
 * Copyright 2025, Daniel Salloum <daniel.salloum@gmail.com>
6
 *
7
 * SPDX-License-Identifier: GPL-2.0-or-later
8
 */
9
10
#include "config.h"
11
12
#include <epan/packet.h>
13
#include <wiretap/wtap.h>
14
15
0
#define RECORD_SIZE_DEFAULT  0x30
16
17
static dissector_handle_t mm_handle;
18
static dissector_handle_t elf_handle = NULL;
19
20
/* protocol and fields */
21
static int proto_m_module;
22
static int hf_tag;
23
static int hf_ftag;
24
static int hf_mod;
25
static int hf_mainv;
26
static int hf_subv;
27
static int hf_off;
28
static int hf_tstamp;
29
static int hf_vtype;
30
static int hf_vcode0;
31
static int hf_vcode1;
32
static int hf_vcode2;
33
static int hf_tot;
34
static int hf_nentries;
35
static int hf_ood;
36
static int hf_cksm;
37
static int hf_fna;
38
static int hf_len;
39
static int hf_elen;
40
static int hf_mentry;
41
static int hf_mcontent;
42
43
44
/* subtree array */
45
static int ett_m_module;
46
static int ett_m_module_entry_header;
47
static int ett_m_module_entry_content;
48
49
struct mentry_info {
50
    uint32_t       tag;
51
    uint32_t       start;
52
    uint32_t       len;
53
};
54
55
56
/* Tables */
57
static const value_string filetagmeaning[] = {
58
    {1 ,"Driver for M1 IO module"},
59
    //{2 ,"Regulator (old definition)"},
60
    {2 ,"VxWorks software module"},
61
    {3 ,"PLC 1131 software module"},
62
    {4 ,"Other module"},
63
    {5 ,"PLC library plm-file"},
64
    {6 ,"@deprecated since MSys 4.00R - Java software module"},
65
    {7 ,"Software service"},
66
    {8 ,"VxWorks software library"},
67
    {9 ,"Logic and firmware file"},
68
    {10,"M-Target software module"},
69
    { 0, NULL }
70
};
71
72
static const value_string modemeaning[] = {
73
    {1,"Object is in normal format (internal file)"},
74
    {2,"Object is compressed (internal file)"},
75
    {4,"Object is external (extra file)"},
76
    {0, NULL}
77
};
78
79
static const value_string vtypemeaning[] = {
80
    { 1,"Alpha-Version"},
81
    { 2,"Beta-Version"},
82
    { 3,"Release-Version"},
83
    {0, NULL}
84
};
85
86
static const value_string tagmeaning[] = {
87
    { 1  , "Object code"},
88
    { 2  , "Configuration data module.cfg" },
89
    { 3  , "Libraryxyz.lib"},
90
    { 4  , "Attribute specification"},
91
    { 5  , "Configuration rule module.bcr"},
92
    { 6  , "Java archive for modules xyz.jar"},
93
    { 10 , "Help infomodule.hlp" },
94
    { 11 , "Readme info module.txt" },
95
    { 12 , "Source code module.src" },
96
    { 13 , "Internal PLC library" },
97
    { 14 , "Packed C project sources module.tgz" },
98
    { 15 , "External PLC library module" },
99
    { 16 , "Software version requirement" },
100
    { 17 , "3S SDB-Filemodule.sdb" },
101
    { 19 , "Logic*.mch" },
102
    { 20 , "FIRMWARE*.h86" },
103
    { 21 , "Advanced test description*.xvd" },
104
    { 22 , "Compressed configuration rule*.zcr" },
105
    { 23 , "M-Target project*.mtp" },
106
    { 24 , "Any ZIP archive*.zip" },
107
    { 100, "Card/Driver type definition (CDI)"},
108
    { 200, "User-Defineable Objects"},
109
    {0, NULL}
110
};
111
112
static uint32_t calculate_checksum(tvbuff_t *tvb)
113
0
{
114
0
    uint32_t i=0, checksum=0;
115
0
    uint32_t file_len = (uint32_t)tvb_captured_length(tvb);
116
117
0
    for (; i<(file_len/4) ; i++ ) {
118
0
        if (i == 11) {
119
0
                continue;
120
0
        }
121
0
        else{
122
0
                checksum += tvb_get_letohl(tvb,i*4);
123
0
        }
124
0
    }
125
126
    // Requires two's compliment
127
0
    checksum = ~checksum + 1;
128
129
0
    return checksum;
130
0
}
131
132
static bool verify_checksum(tvbuff_t *tvb)
133
0
{
134
0
    uint32_t currsum = tvb_get_letohl(tvb,0x2c);
135
136
0
    return calculate_checksum(tvb) == currsum;
137
0
}
138
139
static bool verify_len(tvbuff_t *tvb)
140
0
{
141
0
    uint32_t file_len = (uint32_t)tvb_captured_length(tvb);
142
143
0
    if (file_len < RECORD_SIZE_DEFAULT)
144
0
        return false;
145
146
0
    uint32_t reported_len = tvb_get_letohl(tvb,0x20);
147
148
0
    return file_len == reported_len;
149
0
}
150
151
static bool verify_ftag(tvbuff_t *tvb)
152
0
{
153
0
    uint32_t ftag = tvb_get_letohl(tvb,0x00);
154
0
    if (ftag > 10 || ftag < 1)
155
0
        return false;
156
0
    return true;
157
0
}
158
159
static bool verify_vtag(tvbuff_t *tvb)
160
0
{
161
0
    uint32_t vtag = tvb_get_letohl(tvb,0x10);
162
0
    if (vtag > 3 || vtag < 1)
163
0
        return false;
164
0
    return true;
165
0
}
166
167
static void add_mentry_m_module(proto_tree *subtree,tvbuff_t *tvb, packet_info *pinfo, int offset, int entrysz, struct mentry_info * mentry)
168
0
{
169
0
    const uint8_t * fname = NULL;
170
0
    proto_item * hdr = proto_tree_add_item(subtree, hf_mentry, tvb, offset, entrysz, ENC_NA);
171
0
    proto_tree * mentry_hdr = proto_item_add_subtree(hdr, ett_m_module_entry_header);
172
0
    proto_tree_add_item_ret_uint(mentry_hdr, hf_tag, tvb, offset+ 0x00, 4, ENC_LITTLE_ENDIAN, &mentry->tag);
173
0
    proto_tree_add_item(mentry_hdr, hf_mod, tvb, offset+0x04, 4, ENC_LITTLE_ENDIAN);
174
0
    proto_tree_add_item_ret_uint(mentry_hdr, hf_len, tvb, offset+0x08, 4, ENC_LITTLE_ENDIAN, &mentry->len);
175
0
    proto_tree_add_item_ret_uint(mentry_hdr, hf_off, tvb, offset+0x0c, 4, ENC_LITTLE_ENDIAN, &mentry->start);
176
0
    proto_tree_add_item(mentry_hdr, hf_vtype, tvb, offset+0x10, 4, ENC_LITTLE_ENDIAN);
177
0
    proto_tree_add_item(mentry_hdr, hf_vcode0, tvb, offset+0x14, 4, ENC_LITTLE_ENDIAN);
178
0
    proto_tree_add_item(mentry_hdr, hf_vcode1, tvb, offset+0x18, 4, ENC_LITTLE_ENDIAN);
179
0
    proto_tree_add_item(mentry_hdr, hf_vcode2, tvb, offset+0x1c, 4, ENC_LITTLE_ENDIAN);
180
0
    proto_tree_add_item_ret_string(mentry_hdr, hf_fna, tvb, offset+0x20, 13, ENC_ASCII, pinfo->pool, &fname);
181
0
    proto_item_append_text(hdr,": %s",fname);
182
0
}
183
184
static void setup_mentry_table_content(proto_tree *subtree, tvbuff_t *tvb , packet_info *pinfo, struct mentry_info * mentry)
185
0
{
186
187
0
    proto_item * blob = proto_tree_add_item(subtree, hf_mcontent, tvb, mentry->start, mentry->len , ENC_NA);
188
0
    proto_tree * blob_tree = proto_item_add_subtree(blob, ett_m_module_entry_content);
189
0
    tvbuff_t *sub_tvb = tvb_new_subset_length(tvb, mentry->start, mentry->len);
190
0
    if (mentry->tag == 0x01) {
191
0
        if (elf_handle != NULL)
192
0
            call_dissector(elf_handle, sub_tvb, pinfo, blob_tree);
193
0
    }
194
    // If we want to do more sub-format parsing, add if conditions here
195
0
}
196
197
static int dissect_m_module(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
198
0
{
199
0
    int offset = 0;
200
0
    int len = tvb_captured_length(tvb);
201
0
    uint32_t nentries, entsz;
202
203
0
    if (len < RECORD_SIZE_DEFAULT) {
204
0
        return 0;
205
0
    }
206
207
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "M_Module");
208
0
    proto_item *ti = proto_tree_add_item(tree, proto_m_module, tvb, 0, len, ENC_NA);
209
0
    proto_tree *subtree = proto_item_add_subtree(ti, ett_m_module);
210
211
    // Extract file header
212
0
    proto_tree_add_item(subtree, hf_ftag, tvb, 0x00, 4, ENC_LITTLE_ENDIAN);
213
0
    proto_tree_add_item(subtree, hf_mainv, tvb, 0x04, 4, ENC_LITTLE_ENDIAN);
214
0
    proto_tree_add_item(subtree, hf_subv, tvb, 0x08, 4, ENC_LITTLE_ENDIAN);
215
0
    proto_tree_add_item(subtree, hf_tstamp, tvb, 0x0c, 4, ENC_LITTLE_ENDIAN);
216
0
    proto_tree_add_item(subtree, hf_vtype, tvb, 0x10, 4, ENC_LITTLE_ENDIAN);
217
0
    proto_tree_add_item(subtree, hf_vcode0, tvb, 0x14, 4, ENC_LITTLE_ENDIAN);
218
0
    proto_tree_add_item(subtree, hf_vcode1, tvb, 0x18, 4, ENC_LITTLE_ENDIAN);
219
0
    proto_tree_add_item(subtree, hf_vcode2, tvb, 0x1c, 4, ENC_LITTLE_ENDIAN);
220
0
    proto_tree_add_item(subtree, hf_tot, tvb, 0x20, 4, ENC_LITTLE_ENDIAN);
221
0
    proto_tree_add_item_ret_uint(subtree, hf_nentries, tvb, 0x24, 4, ENC_LITTLE_ENDIAN, &nentries);
222
0
    proto_tree_add_item_ret_uint(subtree, hf_ood, tvb, 0x28, 4, ENC_LITTLE_ENDIAN, &entsz);
223
0
    proto_tree_add_item(subtree, hf_cksm, tvb, 0x2c, 4, ENC_NA);
224
225
0
    struct mentry_info * mentry = wmem_new(pinfo->pool, struct mentry_info);
226
227
0
    for (uint32_t i = 0; i<nentries; i++){
228
0
        offset += entsz;
229
0
        add_mentry_m_module(subtree,tvb,pinfo,offset,entsz,mentry);
230
0
        setup_mentry_table_content(subtree,tvb,pinfo ,mentry);
231
0
    }
232
233
    // Returning record size, which in this case is entire m-file
234
0
    return len;
235
0
}
236
237
static bool dissect_m_module_heur(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data)
238
0
{
239
    /* These are checked from wtap layer.
240
     * However, if heuristics are checked from network traffic, these
241
     * should be in place. Check in order of resource consumption.
242
     */
243
0
    if (verify_ftag(tvb) == false)
244
0
        return false;
245
0
    if (verify_vtag(tvb) == false)
246
0
        return false;
247
0
    if (verify_len(tvb) == false)
248
0
        return false;
249
0
    if (verify_checksum(tvb) == false)
250
0
        return false;
251
252
0
    int r = dissect_m_module(tvb, pinfo, tree, data);
253
254
0
    return r > 0;
255
0
}
256
257
void proto_register_m_module(void)
258
16
{
259
    /* set up header field registrations */
260
16
    static hf_register_info m_module_hf[] = {
261
16
      { &hf_ftag,    {"File Tag", "m_module.ftag",FT_UINT32,BASE_DEC,VALS(filetagmeaning),0x0,NULL,HFILL}},
262
16
      { &hf_tag,     {"Header Tag", "m_module.tag",FT_UINT32,BASE_DEC,VALS(tagmeaning),0x0,NULL,HFILL}},
263
16
      { &hf_mod,     {"Mode","m_module.mode",FT_UINT32,BASE_DEC,VALS(modemeaning),0x0,NULL,HFILL}},
264
16
      { &hf_mainv,   {"Main Version", "m_module.mainv",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
265
16
      { &hf_len,     {"Length", "m_module.len",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
266
16
      { &hf_subv,    {"Sub Version", "m_module.subv",FT_UINT32, BASE_DEC,NULL,0x0,NULL,HFILL}},
267
16
      { &hf_off,     {"File Offset","m_module.off",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
268
16
      { &hf_tstamp,  {"Timestamp","m_module.tstamp",FT_ABSOLUTE_TIME,ABSOLUTE_TIME_UTC,NULL,0x0,NULL,HFILL}},
269
16
      { &hf_vtype,   {"Version Type","m_module.vtype",FT_UINT32,BASE_DEC,VALS(vtypemeaning),0x0,NULL,HFILL}},
270
16
      { &hf_vcode0,  {"Version Code[0]","m_module.vcode0",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
271
16
      { &hf_vcode1,  {"Version Code[1]","m_module.vcode1",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
272
16
      { &hf_vcode2,  {"Version Code[2]","m_module.vcode2",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
273
16
      { &hf_ood,     {"Offset to object descriptor","m_module.ood",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
274
16
      { &hf_tot,     {"Total Bytes In File (for checksum)","m_module.tot",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
275
16
      { &hf_nentries,{"Entry Count","m_module.nentries",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
276
16
      { &hf_cksm,    {"Checksum","m_module.cksm",FT_BYTES,SEP_SPACE,NULL,0x0,NULL,HFILL}},
277
16
      { &hf_fna,     {"Embedded file name","m_module.fname",FT_STRINGZ,BASE_NONE,NULL,0x0,NULL,HFILL}},
278
16
      { &hf_elen,    {"Entry Length","m_module.elen",FT_UINT32,BASE_DEC,NULL,0x0,NULL,HFILL}},
279
16
      { &hf_mentry,  {"Header Entry","m_module.mentry",FT_NONE,BASE_NONE,NULL,0x0,NULL,HFILL}},
280
16
      { &hf_mcontent,{"Content","m_module.content",FT_NONE,BASE_NONE,NULL,0x0,NULL,HFILL}},
281
16
    };
282
283
16
    static int* ett[] = {
284
16
            &ett_m_module,
285
16
            &ett_m_module_entry_header,
286
16
            &ett_m_module_entry_content,
287
16
    };
288
289
16
    proto_m_module = proto_register_protocol("M-Module", "M-Module", "m_module");
290
291
16
    proto_register_field_array(proto_m_module, m_module_hf, array_length(m_module_hf));
292
16
    proto_register_subtree_array(ett, array_length(ett));
293
294
16
    mm_handle = register_dissector("m_module", dissect_m_module, proto_m_module);
295
16
}
296
297
void proto_reg_handoff_m_module(void)
298
16
{
299
16
    elf_handle = find_dissector("elf");
300
301
16
    dissector_add_uint("wtap_encap", WTAP_ENCAP_MMODULE, mm_handle);
302
303
    // Returned when serving over python's http.server
304
16
    dissector_add_string("media_type", "application/vnd.wolfram.mathematica.package", mm_handle);
305
306
16
    heur_dissector_add("wtap_file", dissect_m_module_heur, "M-module file", "m_module_wtap", proto_m_module, HEURISTIC_ENABLE);
307
16
}
308
309
/*
310
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
311
 *
312
 * Local variables:
313
 * c-basic-offset: 4
314
 * tab-width: 8
315
 * indent-tabs-mode: nil
316
 * End:
317
 *
318
 * vi: set shiftwidth=4 tabstop=8 expandtab:
319
 * :indentSize=4:tabSize=8:noTabs=true:
320
 */