/src/wireshark/epan/dissectors/packet-rk512.c
Line | Count | Source |
1 | | /* packet-rk512.c |
2 | | * Routines for RK 512 protocol dissection |
3 | | * Copyright 2022 Michael Mann |
4 | | * |
5 | | * SPDX-License-Identifier: GPL-2.0-or-later |
6 | | */ |
7 | | |
8 | | #include "config.h" |
9 | | |
10 | | #include <epan/packet.h> |
11 | | #include <epan/expert.h> |
12 | | #include <epan/dissectors/packet-tcp.h> |
13 | | #include <wsutil/crc16.h> |
14 | | |
15 | | void proto_register_rk512(void); |
16 | | void proto_reg_handoff_rk512(void); |
17 | | |
18 | | static int proto_rk512; |
19 | | |
20 | | //static int hf_rk512_garbage_data; |
21 | | static int hf_rk512_reply_header; |
22 | | static int hf_rk512_data_block_type; |
23 | | static int hf_rk512_size; |
24 | | static int hf_rk512_coordination_flag; |
25 | | static int hf_rk512_device_code; |
26 | | static int hf_rk512_protocol_version; |
27 | | static int hf_rk512_status; |
28 | | static int hf_rk512_scan_number; |
29 | | static int hf_rk512_telegram_number; |
30 | | static int hf_rk512_data_type; |
31 | | static int hf_rk512_measurement_data_type; |
32 | | static int hf_rk512_measurement_data; |
33 | | static int hf_rk512_measurement_data_distance; |
34 | | static int hf_rk512_measurement_data_flags; |
35 | | static int hf_rk512_checksum; |
36 | | static int hf_rk512_checksum_status; |
37 | | |
38 | | static int ett_rk512; |
39 | | static int ett_rk512_measurement_data; |
40 | | static int ett_rk512_measurement_data_value; |
41 | | static int ett_rk512_continuous_data; |
42 | | |
43 | | static expert_field ei_rk512_reply_header; |
44 | | static expert_field ei_rk512_data_type; |
45 | | static expert_field ei_rk512_checksum; |
46 | | |
47 | | //Preferences |
48 | | static unsigned rk512_num_measurements_pts = 541; |
49 | | |
50 | 1.67k | #define RK512_HEADER_SIZE 4 |
51 | 162 | #define RK512_CRC_SIZE 2 |
52 | | //Used to find the start of packets |
53 | | static const uint8_t HEADER_SEQUENCE[RK512_HEADER_SIZE] = { 0, 0, 0, 0 }; |
54 | | static tvbuff_t* tvb_header_signature = NULL; |
55 | | |
56 | 13 | #define MEASUREMENT_DATA 0xBBBB |
57 | 0 | #define REFLECTOR_DATA 0xCCCC |
58 | | |
59 | | |
60 | | static const value_string device_code_vals[] = { |
61 | | { 7, "Host" }, |
62 | | { 8, "Guest" }, |
63 | | { 0, NULL } |
64 | | }; |
65 | | |
66 | | static const value_string status_vals[] = { |
67 | | { 0, "Normal" }, |
68 | | { 1, "Lockout" }, |
69 | | { 0, NULL } |
70 | | }; |
71 | | |
72 | 876 | #define BLOCKNUM_CONTINUOUSDATA 0x0000 // 0 |
73 | 0 | #define BLOCKNUM_SCID 0x0017 // 23 |
74 | 6 | #define BLOCKNUM_TOKEN 0x0019 // 25 |
75 | 0 | #define BLOCKNUM_DATAMODE 0x0067 // 103 |
76 | | |
77 | | static const value_string data_block_type_vals[] = { |
78 | | { BLOCKNUM_CONTINUOUSDATA, "Continuous Data" }, |
79 | | { BLOCKNUM_SCID, "SCID" }, |
80 | | { BLOCKNUM_TOKEN, "Token" }, |
81 | | { BLOCKNUM_DATAMODE, "Data Mode" }, |
82 | | { 0, NULL } |
83 | | }; |
84 | | |
85 | | static const value_string datatype_vals[] = { |
86 | | { MEASUREMENT_DATA, "Measurement data" }, |
87 | | { REFLECTOR_DATA, "Reflector data" }, |
88 | | { 0, NULL } |
89 | | }; |
90 | | |
91 | | static int* const rk512_measurement_data_fields[] = { |
92 | | &hf_rk512_measurement_data_distance, |
93 | | &hf_rk512_measurement_data_flags, |
94 | | NULL, |
95 | | }; |
96 | | |
97 | | static unsigned |
98 | | get_rk512_pdu_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) |
99 | 162 | { |
100 | 162 | uint32_t len = 0; |
101 | | |
102 | 162 | len = tvb_get_ntohs(tvb, offset + RK512_HEADER_SIZE + 2); //length in words |
103 | | |
104 | | //Yes, this is a horrible hack but works with the captures seen |
105 | 162 | if ((rk512_num_measurements_pts == 381) && (len == 392)) |
106 | 0 | len = 390; |
107 | | |
108 | 162 | return ((len*2) + RK512_HEADER_SIZE + 2 + RK512_CRC_SIZE); |
109 | 162 | } |
110 | | |
111 | | static int |
112 | | dissect_rk512_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
113 | 161 | { |
114 | 161 | proto_tree *rk512_tree, *continous_data_tree, *measurement_tree; |
115 | 161 | proto_item *ti, *header_item, *continous_data_item, *data_item; |
116 | 161 | int offset = 0, start_offset; |
117 | 161 | uint32_t tag, block_type, data_type, sub_data_type, size; |
118 | | |
119 | 161 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "RK512"); |
120 | 161 | col_clear(pinfo->cinfo, COL_INFO); |
121 | | |
122 | 161 | ti = proto_tree_add_item(tree, proto_rk512, tvb, 0, -1, ENC_NA); |
123 | 161 | rk512_tree = proto_item_add_subtree(ti, ett_rk512); |
124 | | |
125 | 161 | header_item = proto_tree_add_item_ret_uint(rk512_tree, hf_rk512_reply_header, tvb, offset, 4, ENC_BIG_ENDIAN, &tag); |
126 | 161 | offset += 4; |
127 | 161 | if (tag != 0) |
128 | 44 | expert_add_info(pinfo, header_item, &ei_rk512_reply_header); |
129 | | |
130 | 161 | proto_tree_add_item_ret_uint(rk512_tree, hf_rk512_data_block_type, tvb, offset, 2, ENC_BIG_ENDIAN, &block_type); |
131 | 161 | offset += 2; |
132 | | |
133 | 161 | col_set_str(pinfo->cinfo, COL_INFO, val_to_str_const(block_type, data_block_type_vals, "Unknown")); |
134 | | |
135 | 161 | switch (block_type) |
136 | 161 | { |
137 | 121 | case BLOCKNUM_CONTINUOUSDATA: |
138 | 121 | start_offset = offset; |
139 | 121 | continous_data_tree = proto_tree_add_subtree(rk512_tree, tvb, offset, -1, ett_rk512_continuous_data, &continous_data_item, "Continuous Data"); |
140 | | |
141 | 121 | proto_tree_add_item_ret_uint(continous_data_tree, hf_rk512_size, tvb, offset, 2, ENC_BIG_ENDIAN, &size); |
142 | 121 | size *= 2; //size is in words |
143 | 121 | offset += 2; |
144 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_coordination_flag, tvb, offset, 1, ENC_BIG_ENDIAN); |
145 | 121 | offset += 1; |
146 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_device_code, tvb, offset, 1, ENC_BIG_ENDIAN); |
147 | 121 | offset += 1; |
148 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_protocol_version, tvb, offset, 2, ENC_BIG_ENDIAN); |
149 | 121 | offset += 2; |
150 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_status, tvb, offset, 2, ENC_BIG_ENDIAN); |
151 | 121 | offset += 2; |
152 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_scan_number, tvb, offset, 4, ENC_LITTLE_ENDIAN); |
153 | 121 | offset += 4; |
154 | 121 | proto_tree_add_item(continous_data_tree, hf_rk512_telegram_number, tvb, offset, 2, ENC_LITTLE_ENDIAN); |
155 | 121 | offset += 2; |
156 | 121 | data_item = proto_tree_add_item_ret_uint(continous_data_tree, hf_rk512_data_type, tvb, offset, 2, ENC_LITTLE_ENDIAN, &data_type); |
157 | 121 | offset += 2; |
158 | | |
159 | 121 | switch (data_type) |
160 | 121 | { |
161 | 13 | case MEASUREMENT_DATA: |
162 | 13 | { |
163 | | //Yes, this is a horrible hack but works with the captures seen |
164 | 13 | if ((rk512_num_measurements_pts == 381) && (size == 784)) |
165 | 0 | size = 780; |
166 | | |
167 | 13 | uint8_t* crc_data; |
168 | 13 | proto_tree_add_item_ret_uint(continous_data_tree, hf_rk512_measurement_data_type, tvb, offset, 2, ENC_LITTLE_ENDIAN, &sub_data_type); |
169 | 13 | offset += 2; |
170 | 13 | measurement_tree = proto_tree_add_subtree(continous_data_tree, tvb, offset, rk512_num_measurements_pts * 2, ett_rk512_measurement_data, NULL, "Measurement Data"); |
171 | 1.42k | for (unsigned i = 0; i < rk512_num_measurements_pts; i++) |
172 | 1.41k | { |
173 | 1.41k | proto_tree_add_bitmask(measurement_tree, tvb, offset, hf_rk512_measurement_data, ett_rk512_measurement_data_value, rk512_measurement_data_fields, ENC_BIG_ENDIAN); |
174 | 1.41k | offset += 2; |
175 | 1.41k | } |
176 | | |
177 | | //Create data to compute the CRC |
178 | 13 | crc_data = (uint8_t*)wmem_alloc(pinfo->pool, size + 2); |
179 | | //start with a word with value 0 |
180 | 13 | crc_data[0] = 0; |
181 | 13 | crc_data[1] = 0; |
182 | | //copy the rest of the packet |
183 | 13 | tvb_memcpy(tvb, &crc_data[2], start_offset, size); |
184 | | |
185 | 13 | proto_tree_add_checksum(rk512_tree, tvb, offset, |
186 | 13 | hf_rk512_checksum, hf_rk512_checksum_status, &ei_rk512_checksum, pinfo, |
187 | 13 | crc16_x25_ccitt_seed(crc_data, size + 2, 0xFFFF), ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); |
188 | 13 | offset += 2; |
189 | 13 | break; |
190 | 0 | } |
191 | 0 | case REFLECTOR_DATA: |
192 | 0 | break; |
193 | 5 | default: |
194 | 5 | expert_add_info(pinfo, data_item, &ei_rk512_data_type); |
195 | 5 | break; |
196 | 121 | } |
197 | | |
198 | 5 | proto_item_set_len(continous_data_item, offset - start_offset); |
199 | 5 | break; |
200 | 0 | case BLOCKNUM_SCID: |
201 | 0 | break; |
202 | 6 | case BLOCKNUM_TOKEN: |
203 | 6 | break; |
204 | 0 | case BLOCKNUM_DATAMODE: |
205 | 0 | break; |
206 | 161 | } |
207 | | |
208 | 45 | return tvb_captured_length(tvb); |
209 | 161 | } |
210 | | |
211 | | static int |
212 | | dissect_rk512(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) |
213 | 24 | { |
214 | 24 | tcp_dissect_pdus(tvb, pinfo, tree, true, 8, get_rk512_pdu_len, dissect_rk512_pdu, data); |
215 | 24 | return tvb_captured_length(tvb); |
216 | 24 | } |
217 | | |
218 | | static bool |
219 | | dissect_rk512_heur(tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data _U_) |
220 | 2.20k | { |
221 | 2.20k | unsigned signature_start, offset; |
222 | 2.20k | uint16_t block_type; |
223 | 2.20k | tvbuff_t* rk512_tvb; |
224 | | |
225 | 2.20k | if (!tvb_find_tvb_remaining(tvb, tvb_header_signature, 0, &signature_start)) { |
226 | 856 | return false; |
227 | 856 | } |
228 | | |
229 | | //keep track of the offset for verification of upcoming fields |
230 | 1.34k | offset = signature_start + RK512_HEADER_SIZE; |
231 | | |
232 | | //Make sure there are enough bytes for the rest of the field checks |
233 | 1.34k | if (tvb_captured_length_remaining(tvb, offset) < 2) |
234 | 32 | return false; |
235 | | |
236 | | //Make sure it's supported block type |
237 | 1.31k | block_type = tvb_get_ntohs(tvb, offset); |
238 | 1.31k | if (try_val_to_str(block_type, data_block_type_vals) == NULL) |
239 | 560 | return false; |
240 | | |
241 | 755 | offset += 2; |
242 | 755 | if (block_type == BLOCKNUM_CONTINUOUSDATA) |
243 | 749 | { |
244 | 749 | uint16_t datatype; |
245 | 749 | if (tvb_captured_length_remaining(tvb, offset) < 18) |
246 | 169 | return false; |
247 | | |
248 | 580 | datatype = tvb_get_ntohs(tvb, offset+14); |
249 | 580 | if (try_val_to_str(datatype, datatype_vals) == NULL) |
250 | 562 | return false; |
251 | 580 | } |
252 | | |
253 | 24 | rk512_tvb = tvb_new_subset_remaining(tvb, signature_start); |
254 | 24 | dissect_rk512(rk512_tvb, pinfo, tree, data); |
255 | 24 | return true; |
256 | 755 | } |
257 | | |
258 | | static void |
259 | | rk512_shutdown(void) |
260 | 0 | { |
261 | 0 | tvb_free(tvb_header_signature); |
262 | 0 | } |
263 | | |
264 | | static void |
265 | | rk512_fmt_version( char *result, uint32_t revision ) |
266 | 0 | { |
267 | 0 | snprintf( result, ITEM_LABEL_LENGTH, "%d.%d", |
268 | 0 | (uint8_t)(revision & 0xFF), (uint8_t)(( revision & 0xFF00 ) >> 8)); |
269 | 0 | } |
270 | | |
271 | | void |
272 | | proto_register_rk512(void) |
273 | 14 | { |
274 | 14 | expert_module_t* expert_rk512; |
275 | | |
276 | 14 | static hf_register_info hf[] = { |
277 | | //{ &hf_rk512_garbage_data, |
278 | | // { "Garbage Data", "rk512.garbage_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
279 | 14 | { &hf_rk512_reply_header, |
280 | 14 | { "Reply Header", "rk512.reply_header", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
281 | 14 | { &hf_rk512_data_block_type, |
282 | 14 | { "Data Block Type", "rk512.data_block_type", FT_UINT16, BASE_DEC, VALS(data_block_type_vals), 0x0, NULL, HFILL } }, |
283 | 14 | { &hf_rk512_size, |
284 | 14 | { "Message Size", "rk512.size", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
285 | 14 | { &hf_rk512_coordination_flag, |
286 | 14 | { "Coordination Flag", "rk512.coordination_flag", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
287 | 14 | { &hf_rk512_device_code, |
288 | 14 | { "Device Code", "rk512.device_code", FT_UINT8, BASE_HEX, VALS(device_code_vals), 0x0, NULL, HFILL } }, |
289 | 14 | { &hf_rk512_protocol_version, |
290 | 14 | { "Protocol Version", "rk512.protocol_version.version", FT_UINT16, BASE_CUSTOM, CF_FUNC(rk512_fmt_version), 0x0, NULL, HFILL } }, |
291 | 14 | { &hf_rk512_status, |
292 | 14 | { "Status", "rk512.status", FT_UINT16, BASE_HEX, VALS(status_vals), 0x0, NULL, HFILL } }, |
293 | 14 | { &hf_rk512_scan_number, |
294 | 14 | { "Scan number", "rk512.scan_number", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
295 | 14 | { &hf_rk512_telegram_number, |
296 | 14 | { "Telegram number", "rk512.telegram_number", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
297 | 14 | { &hf_rk512_data_type, |
298 | 14 | { "Data type", "rk512.data_type", FT_UINT16, BASE_HEX, VALS(datatype_vals), 0x0, NULL, HFILL } }, |
299 | 14 | { &hf_rk512_measurement_data_type, |
300 | 14 | { "Measurement datatype", "rk512.measurement.data_type", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
301 | 14 | { &hf_rk512_measurement_data, |
302 | 14 | { "Measurement data", "rk512.measurement.data", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
303 | 14 | { &hf_rk512_measurement_data_distance, |
304 | 14 | { "Distance", "rk512.measurement.data.distance", FT_UINT16, BASE_DEC, NULL, 0x1FFF, NULL, HFILL } }, |
305 | 14 | { &hf_rk512_measurement_data_flags, |
306 | 14 | { "Flags", "rk512.measurement.data.flags", FT_UINT16, BASE_HEX, NULL, 0xE000, NULL, HFILL } }, |
307 | 14 | { &hf_rk512_checksum, |
308 | 14 | { "Checksum", "rk512.checksum", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
309 | 14 | { &hf_rk512_checksum_status, |
310 | 14 | { "CRC Status", "rk512.checksum_status", FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0, NULL, HFILL } }, |
311 | | |
312 | 14 | }; |
313 | | |
314 | 14 | static int *ett[] = { |
315 | 14 | &ett_rk512, |
316 | 14 | &ett_rk512_continuous_data, |
317 | 14 | &ett_rk512_measurement_data, |
318 | 14 | &ett_rk512_measurement_data_value |
319 | 14 | }; |
320 | | |
321 | 14 | static ei_register_info ei[] = { |
322 | 14 | { &ei_rk512_reply_header, { "rk512.reply_header.not_zero", PI_PROTOCOL, PI_WARN, "Reply header not zero", EXPFILL }}, |
323 | 14 | { &ei_rk512_data_type, { "rk512.data_type.unknown", PI_PROTOCOL, PI_WARN, "Unknown data type", EXPFILL }}, |
324 | 14 | { &ei_rk512_checksum, { "rk512.checksum.incorrect", PI_CHECKSUM, PI_WARN, "Checksum incorrect", EXPFILL }}, |
325 | 14 | }; |
326 | | |
327 | | |
328 | 14 | proto_rk512 = proto_register_protocol("SICK RK512", "RK512", "rk512"); |
329 | 14 | proto_register_field_array(proto_rk512, hf, array_length(hf)); |
330 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
331 | 14 | expert_rk512 = expert_register_protocol(proto_rk512); |
332 | 14 | expert_register_field_array(expert_rk512, ei, array_length(ei)); |
333 | | |
334 | 14 | module_t* rk512_module = prefs_register_protocol(proto_rk512, NULL); |
335 | 14 | prefs_register_uint_preference(rk512_module, "num_measurement_pts", |
336 | 14 | "Number of measurement points", |
337 | 14 | "Number of measurement points within the packet", |
338 | 14 | 10, &rk512_num_measurements_pts); |
339 | | |
340 | 14 | tvb_header_signature = tvb_new_real_data(HEADER_SEQUENCE, sizeof(HEADER_SEQUENCE), sizeof(HEADER_SEQUENCE)); |
341 | | |
342 | 14 | register_shutdown_routine(rk512_shutdown); |
343 | 14 | } |
344 | | |
345 | | void |
346 | | proto_reg_handoff_rk512(void) |
347 | 14 | { |
348 | 14 | dissector_handle_t rk512_handle; |
349 | | |
350 | 14 | rk512_handle = create_dissector_handle(dissect_rk512, proto_rk512); |
351 | 14 | dissector_add_for_decode_as("tcp.port", rk512_handle); |
352 | 14 | heur_dissector_add("tcp", dissect_rk512_heur, "RK512 over TCP", "rk512_tcp", proto_rk512, HEURISTIC_ENABLE); |
353 | 14 | } |
354 | | |
355 | | /* |
356 | | * Editor modelines - http://www.wireshark.org/tools/modelines.html |
357 | | * |
358 | | * Local variables: |
359 | | * c-basic-offset: 4 |
360 | | * tab-width: 8 |
361 | | * indent-tabs-mode: t |
362 | | * End: |
363 | | * |
364 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
365 | | * :indentSize=4:tabSize=8:noTabs=false: |
366 | | */ |