3 * By Dr. Lars Voelker <lars.voelker@technica-engineering.de>
4 * Copyright 2013-2019 BMW Group, Dr. Lars Voelker
5 * Copyright 2020-2023 Technica Engineering, Dr. Lars Voelker
6 * Copyright 2023-2023 BMW Group, Hermann Leinsle
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * SPDX-License-Identifier: GPL-2.0-or-later
17 #include <epan/prefs.h>
19 #include "packet-tcp.h"
20 #include "packet-udp.h"
21 #include "packet-hsfz.h"
24 #define HSFZ_HDR_LEN 6
26 #define HSFZ_NAME "HSFZ"
27 #define HSFZ_NAME_LONG "High Speed Fahrzeugzugang"
28 #define HSFZ_NAME_FILTER "hsfz"
31 dissector_handle_t hsfz_handle_tcp
;
32 dissector_handle_t hsfz_handle_udp
;
33 dissector_handle_t uds_handle
;
35 void proto_register_hsfz(void);
36 void proto_reg_handoff_hsfz(void);
38 static int proto_hsfz
;
40 /*** header fields ***/
41 static int hf_hsfz_length
;
42 static int hf_hsfz_ctrlword
;
43 static int hf_hsfz_source_address
;
44 static int hf_hsfz_target_address
;
45 static int hf_hsfz_address
;
46 static int hf_hsfz_ident_string
;
47 static int hf_hsfz_data
;
49 /*** protocol tree items ***/
53 #define HSFZ_CTRLWORD_DIAGNOSTIC_REQ_RES 0x0001
54 #define HSFZ_CTRLWORD_ACKNOWLEDGE_TRANSFER 0x0002
55 #define HSFZ_CTRLWORD_TERMINAL15 0x0010
56 #define HSFZ_CTRLWORD_VEHICLE_IDENT_DATA 0x0011
57 #define HSFZ_CTRLWORD_ALIVE_CHECK 0x0012
58 #define HSFZ_CTRLWORD_STATUS_DATA_INQUIRY 0x0013
59 #define HSFZ_CTRLWORD_INCORRECT_TESTER_ADDRESS 0x0040
60 #define HSFZ_CTRLWORD_INCORRECT_CONTROL_WORD 0x0041
61 #define HSFZ_CTRLWORD_INCORRECT_FORMAT 0x0042
62 #define HSFZ_CTRLWORD_INCORRECT_DEST_ADDRESS 0x0043
63 #define HSFZ_CTRLWORD_MESSAGE_TOO_LARGE 0x0044
64 #define HSFZ_CTRLWORD_DIAG_APP_NOT_READY 0x0045
65 #define HSFZ_CTRLWORD_OUT_OF_MEMORY 0x00FF
67 static const value_string hsfz_ctrlwords
[] = {
68 {HSFZ_CTRLWORD_DIAGNOSTIC_REQ_RES
, "Request or Response"},
69 {HSFZ_CTRLWORD_ACKNOWLEDGE_TRANSFER
, "Acknowledgment"},
70 {HSFZ_CTRLWORD_TERMINAL15
, "Terminal 15 Control Message"},
71 {HSFZ_CTRLWORD_VEHICLE_IDENT_DATA
, "Vehicle Identification Data"},
72 {HSFZ_CTRLWORD_ALIVE_CHECK
, "Alive check"},
73 {HSFZ_CTRLWORD_STATUS_DATA_INQUIRY
, "Status data inquiry"},
74 {HSFZ_CTRLWORD_INCORRECT_TESTER_ADDRESS
, "Incorrect tester address"},
75 {HSFZ_CTRLWORD_INCORRECT_CONTROL_WORD
, "Incorrect control word"},
76 {HSFZ_CTRLWORD_INCORRECT_FORMAT
, "Incorrect format"},
77 {HSFZ_CTRLWORD_INCORRECT_DEST_ADDRESS
, "Incorrect destination address"},
78 {HSFZ_CTRLWORD_MESSAGE_TOO_LARGE
, "Message too large"},
79 {HSFZ_CTRLWORD_DIAG_APP_NOT_READY
, "Diagnostic application not ready"},
80 {HSFZ_CTRLWORD_OUT_OF_MEMORY
, "Out of memory"},
85 /**********************************
86 ********* Configuration **********
87 **********************************/
88 typedef struct _udf_one_id_string
{
91 } udf_one_id_string_t
;
93 /*** Hash Tables for lookup data ***/
94 static GHashTable
*ht_diag_addr
;
96 static bool hsfz_check_header
;
97 static bool hsfz_show_uds_in_ack
;
99 static udf_one_id_string_t
*udf_diag_addr
;
100 static unsigned udf_diag_addr_num
;
103 udf_copy_one_id_string_cb(void* n
, const void* o
, size_t size _U_
) {
104 udf_one_id_string_t
*new_rec
= (udf_one_id_string_t
*)n
;
105 const udf_one_id_string_t
*old_rec
= (const udf_one_id_string_t
*)o
;
108 new_rec
->name
= g_strdup(old_rec
->name
);
110 new_rec
->name
= NULL
;
113 new_rec
->id
= old_rec
->id
;
118 udf_free_one_id_string_cb(void *r
) {
119 udf_one_id_string_t
*rec
= (udf_one_id_string_t
*)r
;
120 if (rec
->name
) g_free(rec
->name
);
124 udf_free_one_id_string_data(void *data _U_
) {
125 /* nothing to free here since we did not malloc data in udf_post_update_one_id_string_template_cb */
129 udf_update_diag_addr_cb(void *r
, char **err
) {
130 udf_one_id_string_t
*rec
= (udf_one_id_string_t
*)r
;
132 if (rec
->id
> 0xff) {
133 *err
= g_strdup_printf("HSFZ only supports 8 bit diagnostic addresses (diag_addr: %i name: %s)", rec
->id
, rec
->name
);
134 return (*err
== NULL
);
137 if (rec
->name
== NULL
|| rec
->name
[0] == 0) {
138 *err
= g_strdup("ECU Name cannot be empty");
139 return (*err
== NULL
);
143 return (*err
== NULL
);
146 UAT_HEX_CB_DEF(udf_diag_addr
, id
, udf_one_id_string_t
)
147 UAT_CSTRING_CB_DEF(udf_diag_addr
, name
, udf_one_id_string_t
)
150 udf_free_key(void *key
) {
151 wmem_free(wmem_epan_scope(), key
);
155 udf_post_update_one_id_string_template_cb(udf_one_id_string_t
*udf_data
, unsigned udf_data_num
, GHashTable
*ht
) {
160 if (udf_data_num
>0) {
161 for (i
= 0; i
< udf_data_num
; i
++) {
162 key
= wmem_new(wmem_epan_scope(), int);
163 tmp
= udf_data
[i
].id
;
166 g_hash_table_insert(ht
, key
, udf_data
[i
].name
);
172 udf_post_update_diag_addr_cb(void) {
174 g_hash_table_destroy(ht_diag_addr
);
178 ht_diag_addr
= g_hash_table_new_full(g_int_hash
, g_int_equal
, &udf_free_key
, &udf_free_one_id_string_data
);
179 udf_post_update_one_id_string_template_cb(udf_diag_addr
, udf_diag_addr_num
, ht_diag_addr
);
183 get_name_from_ht_diag_addr(unsigned identifier
) {
184 unsigned key
= identifier
;
186 if (ht_diag_addr
== NULL
) {
190 return (char *)g_hash_table_lookup(ht_diag_addr
, &key
);
194 /**********************************
195 ****** The dissector itself ******
196 **********************************/
199 dissect_hsfz_address(tvbuff_t
*tvb
, packet_info
*pinfo _U_
, proto_tree
*tree
, int offset
, int hf_specific_address
) {
204 ti
= proto_tree_add_item_ret_uint(tree
, hf_specific_address
, tvb
, offset
, 1, ENC_NA
, &tmp
);
205 name
= get_name_from_ht_diag_addr((unsigned)tmp
);
207 proto_item_append_text(ti
, " (%s)", name
);
210 ti
= proto_tree_add_item(tree
, hf_hsfz_address
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
211 PROTO_ITEM_SET_HIDDEN(ti
);
217 dissect_hsfz_message(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
221 uint32_t real_length
= 0;
226 if (tvb_captured_length_remaining(tvb
, 0) < HSFZ_HDR_LEN
) {
229 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, HSFZ_NAME
);
231 uint32_t hsfz_length
= tvb_get_ntohl(tvb
, 0);
232 uint16_t hsfz_ctrlword
= tvb_get_ntohs(tvb
, 4);
233 const char *ctrlword_description
= val_to_str(hsfz_ctrlword
, hsfz_ctrlwords
, "Unknown 0x%04x");
235 const char *col_string
= col_get_text(pinfo
->cinfo
, COL_INFO
);
236 if (col_string
!=NULL
&& g_str_has_prefix(col_string
, (char *)&"HSFZ\0")) {
237 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " / %s %s", HSFZ_NAME
, ctrlword_description
);
239 col_add_fstr(pinfo
->cinfo
, COL_INFO
, "%s %s", HSFZ_NAME
, ctrlword_description
);
242 if (hsfz_ctrlword
== HSFZ_CTRLWORD_DIAGNOSTIC_REQ_RES
|| (hsfz_ctrlword
== HSFZ_CTRLWORD_ACKNOWLEDGE_TRANSFER
&& hsfz_show_uds_in_ack
)) {
243 real_length
= HSFZ_HDR_LEN
+ 2;
245 real_length
= HSFZ_HDR_LEN
+ hsfz_length
;
248 ti_root
= proto_tree_add_item(tree
, proto_hsfz
, tvb
, 0, real_length
, ENC_NA
);
249 proto_item_append_text(ti_root
, ", Length: %i, Control Word: 0x%04x (%s)", hsfz_length
, hsfz_ctrlword
, ctrlword_description
);
250 proto_tree
*hsfz_tree
= proto_item_add_subtree(ti_root
, ett_hsfz
);
252 proto_tree_add_item(hsfz_tree
, hf_hsfz_length
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
255 proto_tree_add_item(hsfz_tree
, hf_hsfz_ctrlword
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
258 switch (hsfz_ctrlword
) {
259 case HSFZ_CTRLWORD_DIAGNOSTIC_REQ_RES
:
260 case HSFZ_CTRLWORD_ACKNOWLEDGE_TRANSFER
:
261 source_addr
= dissect_hsfz_address(tvb
, pinfo
, hsfz_tree
, offset
, hf_hsfz_source_address
);
264 target_addr
= dissect_hsfz_address(tvb
, pinfo
, hsfz_tree
, offset
, hf_hsfz_target_address
);
267 if ( (hsfz_ctrlword
!= HSFZ_CTRLWORD_ACKNOWLEDGE_TRANSFER
|| hsfz_show_uds_in_ack
) && uds_handle
!= 0) {
268 hsfz_info_t hsfz_info
;
269 hsfz_info
.source_address
= source_addr
;
270 hsfz_info
.target_address
= target_addr
;
272 tvbuff_t
*subtvb
= tvb_new_subset_length(tvb
, offset
, hsfz_length
- 2);
273 call_dissector_with_data(uds_handle
, subtvb
, pinfo
, tree
, &hsfz_info
);
275 proto_tree_add_item(hsfz_tree
, hf_hsfz_data
, tvb
, offset
, hsfz_length
- 2, ENC_NA
);
279 case HSFZ_CTRLWORD_VEHICLE_IDENT_DATA
:
280 if (hsfz_length
> 0) {
281 const uint8_t *ident_data
;
282 proto_tree_add_item_ret_string(hsfz_tree
, hf_hsfz_ident_string
, tvb
, offset
, hsfz_length
, ENC_ASCII
, pinfo
->pool
, &ident_data
);
283 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " (%s)", ident_data
);
287 case HSFZ_CTRLWORD_INCORRECT_DEST_ADDRESS
:
288 case HSFZ_CTRLWORD_OUT_OF_MEMORY
:
289 if (hsfz_ctrlword
== HSFZ_CTRLWORD_INCORRECT_DEST_ADDRESS
|| hsfz_length
>= 2) {
290 dissect_hsfz_address(tvb
, pinfo
, hsfz_tree
, offset
, hf_hsfz_source_address
);
293 dissect_hsfz_address(tvb
, pinfo
, hsfz_tree
, offset
, hf_hsfz_target_address
);
298 if (hsfz_length
> 0) {
299 proto_tree_add_item(hsfz_tree
, hf_hsfz_data
, tvb
, offset
, hsfz_length
, ENC_NA
);
304 return HSFZ_HDR_LEN
+ hsfz_length
;
308 get_hsfz_message_len(packet_info
*pinfo _U_
, tvbuff_t
*tvb
, int offset
, void* data _U_
) {
309 /* The length [uint32] does not include the header itself */
310 uint32_t length
= tvb_get_ntohl(tvb
, offset
);
311 uint16_t ctrlwd
= tvb_get_ntohs(tvb
, offset
+ 4);
313 /* if heuristic check active: */
314 if (hsfz_check_header
&& (length
> 0x000fffff || ctrlwd
> 0x00ff )) {
318 return HSFZ_HDR_LEN
+ length
;
322 dissect_hsfz_tcp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
323 tcp_dissect_pdus(tvb
, pinfo
, tree
, true, HSFZ_HDR_LEN
, get_hsfz_message_len
, dissect_hsfz_message
, NULL
);
324 return tvb_captured_length(tvb
);
328 dissect_hsfz_udp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
329 return udp_dissect_pdus(tvb
, pinfo
, tree
, HSFZ_HDR_LEN
, NULL
, get_hsfz_message_len
, dissect_hsfz_message
, NULL
);
332 void proto_register_hsfz(void) {
333 module_t
*hsfz_module
;
334 uat_t
* udf_diag_addr_uat
;
337 static hf_register_info hf
[] = {
339 { "Length", "hsfz.length", FT_UINT32
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
341 { "Control Word", "hsfz.ctrlword", FT_UINT16
, BASE_HEX
, VALS(hsfz_ctrlwords
), 0x0, NULL
, HFILL
}},
342 { &hf_hsfz_source_address
,
343 { "Source Address", "hsfz.sourceaddr", FT_UINT8
, BASE_HEX
, NULL
, 0x0, NULL
, HFILL
}},
344 { &hf_hsfz_target_address
,
345 { "Target Address", "hsfz.targetaddr", FT_UINT8
, BASE_HEX
, NULL
, 0x0, NULL
, HFILL
}},
347 { "Address", "hsfz.address", FT_UINT8
, BASE_HEX
, NULL
, 0x0, NULL
, HFILL
} },
348 { &hf_hsfz_ident_string
,
349 { "Identification String", "hsfz.identification_string", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
} },
351 { "Data", "hsfz.data", FT_BYTES
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
} },
354 /* entries in the protocol tree */
355 static int *ett
[] = {
359 /* UATs for user_data fields */
360 static uat_field_t diag_addr_uat_fields
[] = {
361 UAT_FLD_HEX(udf_diag_addr
, id
, "Diagnostic Address", "Diagnostic Address of ECU (hex without leading 0x)"),
362 UAT_FLD_CSTRING(udf_diag_addr
, name
, "ECU Name", "Name of ECU (string)"),
366 proto_hsfz
= proto_register_protocol(HSFZ_NAME_LONG
, HSFZ_NAME
, HSFZ_NAME_FILTER
);
367 proto_register_field_array(proto_hsfz
, hf
, array_length(hf
));
368 proto_register_subtree_array(ett
, array_length(ett
));
370 /* Register preferences */
371 hsfz_module
= prefs_register_protocol(proto_hsfz
, NULL
);
373 prefs_register_bool_preference(hsfz_module
, "header_check_heuristic", "Find start of HSFZ header by checking validity",
374 "Should the HSFZ dissector check if a HSFZ header for validity (length and control word)?", &hsfz_check_header
);
376 prefs_register_bool_preference(hsfz_module
, "show_uds_in_ack", "Show UDS in HSFZ Ack",
377 "Should the shortened UDS in the HSFZ be dissected?", &hsfz_show_uds_in_ack
);
379 udf_diag_addr_uat
= uat_new("Diagnostic Addresses",
380 sizeof(udf_one_id_string_t
), /* record size */
381 "HSFZ_diagnostics_addresses", /* filename */
382 true, /* from_profile */
383 (void**)&udf_diag_addr
, /* data_ptr */
384 &udf_diag_addr_num
, /* numitems_ptr */
385 UAT_AFFECTS_DISSECTION
, /* specifies addresses */
387 udf_copy_one_id_string_cb
, /* copy callback */
388 udf_update_diag_addr_cb
, /* update callback */
389 udf_free_one_id_string_cb
, /* free callback */
390 udf_post_update_diag_addr_cb
, /* post update callback */
391 NULL
, /* reset callback */
392 diag_addr_uat_fields
/* UAT field definitions */
395 prefs_register_uat_preference(hsfz_module
, "_udf_diag_addr", "Diagnostic Addresses",
396 "A table to define names for diagnostic addresses", udf_diag_addr_uat
);
399 void proto_reg_handoff_hsfz(void) {
400 hsfz_handle_tcp
= register_dissector("hsfz_over_tcp", dissect_hsfz_tcp
, proto_hsfz
);
401 hsfz_handle_udp
= register_dissector("hsfz_over_udp", dissect_hsfz_udp
, proto_hsfz
);
403 dissector_add_uint_range_with_preference("tcp.port", "", hsfz_handle_tcp
);
404 dissector_add_uint_range_with_preference("udp.port", "", hsfz_handle_udp
);
406 uds_handle
= find_dissector("uds_over_hsfz");
410 * Editor modelines - http://www.wireshark.org/tools/modelines.html
415 * indent-tabs-mode: nil
418 * vi: set shiftwidth=4 tabstop=8 expandtab:
419 * :indentSize=4:tabSize=8:noTabs=true: