/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 | | */ |