diff options
author | Dr. Lars Völker <lars.voelker@technica-engineering.de> | 2022-01-04 15:49:39 +0100 |
---|---|---|
committer | A Wireshark GitLab Utility <gerald+gitlab-utility@wireshark.org> | 2022-01-05 06:06:33 +0000 |
commit | 382fe23aa8129d8f2ad2fb3ed484946625e70a50 (patch) | |
tree | 26a2918f18bc61b5121ea659b2db00b175c02f79 /epan/dissectors | |
parent | 27ccf26b4bdacefbd25a185721f88e07677b95e5 (diff) |
UDS: add subdissector support
Adding subdissector support to UDS and allow Signal PDUs for it.
This patch supports:
- ReadDataByIdentifier (RDBI) Reply
- WriteDataByIdentifier (WDBI) Request
- RoutineControl (RC) Request
- RoutineControl (RC) Reply
Diffstat (limited to 'epan/dissectors')
-rw-r--r-- | epan/dissectors/CMakeLists.txt | 1 | ||||
-rw-r--r-- | epan/dissectors/packet-signal-pdu.c | 175 | ||||
-rw-r--r-- | epan/dissectors/packet-uds.c | 61 | ||||
-rw-r--r-- | epan/dissectors/packet-uds.h | 63 |
4 files changed, 269 insertions, 31 deletions
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 817575f218..94698143cd 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -601,6 +601,7 @@ set(DISSECTOR_PUBLIC_HEADERS packet-uavcan-dsdl.h packet-ubertooth.h packet-udp.h + packet-uds.h packet-umts_fp.h packet-umts_mac.h packet-umts_rlc.h diff --git a/epan/dissectors/packet-signal-pdu.c b/epan/dissectors/packet-signal-pdu.c index 277e6827c3..4aa8fa26e5 100644 --- a/epan/dissectors/packet-signal-pdu.c +++ b/epan/dissectors/packet-signal-pdu.c @@ -32,6 +32,7 @@ #include <packet-lin.h> #include <packet-autosar-ipdu-multiplexer.h> #include <packet-dlt.h> +#include <packet-uds.h> /* @@ -62,6 +63,7 @@ #define DATAFILE_SPDU_PDU_TRANSPORT_MAPPING "Signal_PDU_Binding_PDU_Transport" #define DATAFILE_SPDU_IPDUM_MAPPING "Signal_PDU_Binding_AUTOSAR_IPduM" #define DATAFILE_SPDU_DLT_MAPPING "Signal_PDU_Binding_DLT" +#define DATAFILE_SPDU_UDS_MAPPING "Signal_PDU_Binding_UDS" /* ID wireshark identifies the dissector by */ static int proto_signal_pdu = -1; @@ -99,6 +101,7 @@ static GHashTable *data_spdu_lin_mappings = NULL; static GHashTable *data_spdu_pdu_transport_mappings = NULL; static GHashTable *data_spdu_ipdum_mappings = NULL; static GHashTable *data_spdu_dlt_mappings = NULL; +static GHashTable *data_spdu_uds_mappings = NULL; static hf_register_info *dynamic_hf = NULL; static guint dynamic_hf_size = 0; @@ -259,6 +262,7 @@ typedef struct _spdu_ipdum_mapping { } spdu_ipdum_mapping_t; typedef spdu_ipdum_mapping_t spdu_ipdum_mapping_uat_t; + typedef struct _spdu_dlt_mapping { gchar *ecu_id; guint32 dlt_message_id; @@ -266,6 +270,17 @@ typedef struct _spdu_dlt_mapping { } spdu_dlt_mapping_t; typedef spdu_dlt_mapping_t spdu_dlt_mapping_uat_t; + +typedef struct _spdu_uds_mapping { + guint32 uds_address; + guint32 service; + gboolean reply; + guint32 id; + guint32 message_id; +} spdu_uds_mapping_t; +typedef spdu_uds_mapping_t spdu_uds_mapping_uat_t; + + static generic_one_id_string_t *spdu_message_ident = NULL; static guint spdu_message_ident_num = 0; @@ -296,6 +311,10 @@ static guint spdu_ipdum_mapping_num = 0; static spdu_dlt_mapping_t *spdu_dlt_mapping = NULL; static guint spdu_dlt_mapping_num = 0; +static spdu_uds_mapping_t *spdu_uds_mapping = NULL; +static guint spdu_uds_mapping_num = 0; + + void proto_register_signal_pdu(void); void proto_reg_handoff_signal_pdu(void); @@ -1582,7 +1601,7 @@ update_spdu_dlt_mapping(void *r, char **err) { spdu_dlt_mapping_uat_t *rec = (spdu_dlt_mapping_uat_t *)r; if (rec->ecu_id != NULL && strlen(rec->ecu_id) > 4) { - *err = ws_strdup_printf("ECU ID can only be up to 4 characters long!"); + *err = ws_strdup_printf("ECU ID can only be up to 4 characters long!"); return FALSE; } @@ -1628,6 +1647,112 @@ get_dlt_mapping(guint32 pdu_id, const gchar *ecu_id) { return (spdu_dlt_mapping_uat_t *)g_hash_table_lookup(data_spdu_dlt_mappings, &key); } +/* UAT: UDS Mapping */ +UAT_HEX_CB_DEF(spdu_uds_mapping, uds_address, spdu_uds_mapping_uat_t) +UAT_HEX_CB_DEF(spdu_uds_mapping, service, spdu_uds_mapping_uat_t) +UAT_BOOL_CB_DEF(spdu_uds_mapping, reply, spdu_uds_mapping_uat_t) +UAT_HEX_CB_DEF(spdu_uds_mapping, id, spdu_uds_mapping_uat_t) +UAT_HEX_CB_DEF(spdu_uds_mapping, message_id, spdu_uds_mapping_uat_t) + +static void * +copy_spdu_uds_mapping_cb(void *n, const void *o, size_t size _U_) { + spdu_uds_mapping_uat_t *new_rec = (spdu_uds_mapping_uat_t *)n; + const spdu_uds_mapping_uat_t *old_rec = (const spdu_uds_mapping_uat_t *)o; + + new_rec->uds_address = old_rec->uds_address; + new_rec->service = old_rec->service; + new_rec->reply = old_rec->reply; + new_rec->id = old_rec->id; + new_rec->message_id = old_rec->message_id; + + return new_rec; +} + +static gboolean +update_spdu_uds_mapping(void *r, char **err) { + spdu_uds_mapping_uat_t *rec = (spdu_uds_mapping_uat_t *)r; + + if (rec->id > 0xffff) { + *err = g_strdup_printf("UDS IDs are only uint16!"); + return FALSE; + } + + if (rec->service > 0xff) { + *err = g_strdup_printf("UDS Services are only uint8!"); + return FALSE; + } + + return TRUE; +} + +static void +post_update_spdu_uds_mapping_cb(void) { + /* destroy old hash table, if it exists */ + if (data_spdu_uds_mappings) { + g_hash_table_destroy(data_spdu_uds_mappings); + data_spdu_uds_mappings = NULL; + } + + /* we don't need to free the data as long as we don't alloc it first */ + data_spdu_uds_mappings = g_hash_table_new_full(g_int64_hash, g_int64_equal, &spdu_payload_free_key, NULL); + + if (data_spdu_uds_mappings == NULL || spdu_uds_mapping == NULL) { + return; + } + + if (spdu_uds_mapping_num > 0) { + guint i; + guint32 sid; + for (i = 0; i < spdu_uds_mapping_num; i++) { + gint64 *key = wmem_new(wmem_epan_scope(), gint64); + if (spdu_uds_mapping[i].reply) { + sid = (0xff & spdu_uds_mapping[i].service) | UDS_REPLY_MASK; + } else { + sid = (0xff & spdu_uds_mapping[i].service); + } + + *key = (guint64)(spdu_uds_mapping[i].uds_address) | ((guint64)(0xffff & spdu_uds_mapping[i].id) << 32) | ((guint64)sid << 48); + g_hash_table_insert(data_spdu_uds_mappings, key, &spdu_uds_mapping[i]); + + /* Adding with 0xffffffff (ANY) as address too */ + key = wmem_new(wmem_epan_scope(), gint64); + *key = (guint64)(0xffffffff) | ((guint64)(0xffff & spdu_uds_mapping[i].id) << 32) | ((guint64)sid << 48); + g_hash_table_insert(data_spdu_uds_mappings, key, &spdu_uds_mapping[i]); + } + } +} + +static spdu_uds_mapping_uat_t * +get_uds_mapping(uds_info_t *uds_info) { + guint32 sid; + + DISSECTOR_ASSERT(uds_info); + if (data_spdu_uds_mappings == NULL) { + return NULL; + } + + gint64 *key = wmem_new(wmem_epan_scope(), gint64); + if (uds_info->reply) { + sid = (0xff & uds_info->service) | UDS_REPLY_MASK; + } else { + sid = (0xff & uds_info->service); + } + *key = (guint64)(uds_info->uds_address) | ((guint64)(0xffff & uds_info->id) << 32) | ((guint64)sid << 48); + + spdu_uds_mapping_uat_t *tmp = (spdu_uds_mapping_uat_t *)g_hash_table_lookup(data_spdu_uds_mappings, key); + + /* if we cannot find it for the Address, lets look at MAXUINT32 */ + if (tmp == NULL) { + *key = (guint64)(G_MAXUINT32) | ((guint64)(0xffff & uds_info->id) << 32) | ((guint64)sid << 48); + + tmp = (spdu_uds_mapping_uat_t *)g_hash_table_lookup(data_spdu_uds_mappings, key); + } + + wmem_free(wmem_epan_scope(), key); + + return tmp; +} + /************************************** ******** Expert Infos ******** **************************************/ @@ -2058,7 +2183,7 @@ dissect_spdu_message_lin(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, vo static int dissect_spdu_message_pdu_transport(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { - pdu_transport_info_t *pdu_info = (pdu_transport_info_t*)data; + pdu_transport_info_t *pdu_info = (pdu_transport_info_t *)data; DISSECTOR_ASSERT(pdu_info); spdu_pdu_transport_mapping_t *pdu_transport_mapping = get_pdu_transport_mapping(pdu_info->id); @@ -2072,7 +2197,7 @@ dissect_spdu_message_pdu_transport(tvbuff_t *tvb, packet_info *pinfo, proto_tree static int dissect_spdu_message_ipdum(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { - autosar_ipdu_multiplexer_info_t *pdu_info = (autosar_ipdu_multiplexer_info_t*)data; + autosar_ipdu_multiplexer_info_t *pdu_info = (autosar_ipdu_multiplexer_info_t *)data; DISSECTOR_ASSERT(pdu_info); spdu_ipdum_mapping_uat_t *ipdum_mapping = get_ipdum_mapping(pdu_info->pdu_id); @@ -2098,6 +2223,19 @@ dissect_spdu_message_dlt_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tre return dissect_spdu_payload(tvb, pinfo, tree, dlt_mapping->message_id, TRUE); } +static gboolean +dissect_spdu_message_uds_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { + uds_info_t *uds_info = (uds_info_t *)data; + DISSECTOR_ASSERT(uds_info); + + spdu_uds_mapping_t *uds_mapping = get_uds_mapping(uds_info); + if (uds_mapping == NULL) { + return FALSE; + } + + return dissect_spdu_payload(tvb, pinfo, tree, uds_mapping->message_id, FALSE) != 0; +} + /************************************** ******** Register Dissector ******** **************************************/ @@ -2122,7 +2260,7 @@ proto_register_signal_pdu(void) { uat_t *spdu_pdu_transport_mapping_uat; uat_t *spdu_ipdum_mapping_uat; uat_t *spdu_dlt_mapping_uat; - + uat_t *spdu_uds_mapping_uat; /* data fields */ static hf_register_info hf[] = { @@ -2222,6 +2360,15 @@ proto_register_signal_pdu(void) { UAT_END_FIELDS }; + static uat_field_t spdu_uds_mapping_uat_fields[] = { + UAT_FLD_HEX(spdu_uds_mapping, uds_address, "ECU Address", "ECU Address (32bit hex without leading 0x, 0xffffffff means any)"), + UAT_FLD_HEX(spdu_uds_mapping, service, "UDS Service", "UDS Service (8bit hex without leading 0x)"), + UAT_FLD_BOOL(spdu_uds_mapping, reply, "Reply", "Reply [FALSE|TRUE]"), + UAT_FLD_HEX(spdu_uds_mapping, id, "ID", "ID (16bit hex without leading 0x)"), + UAT_FLD_HEX(spdu_uds_mapping, message_id, "Signal PDU ID", "ID of the Signal PDU (32bit hex without leading 0x)"), + UAT_END_FIELDS + }; + static ei_register_info ei[] = { { &ef_spdu_payload_truncated, {"signal_pdu.payload.expert_truncated", PI_MALFORMED, PI_ERROR, "Signal PDU: Truncated payload!", EXPFILL} }, @@ -2444,6 +2591,24 @@ proto_register_signal_pdu(void) { prefs_register_uat_preference(spdu_module, "_spdu_dlt_mapping", "DLT Mappings", "A table to map DLT non-verbose Payloads to Signal PDUs", spdu_dlt_mapping_uat); + + + spdu_uds_mapping_uat = uat_new("UDS", + sizeof(spdu_uds_mapping_uat_t), DATAFILE_SPDU_UDS_MAPPING, TRUE, + (void **)&spdu_uds_mapping, + &spdu_uds_mapping_num, + UAT_AFFECTS_DISSECTION, + NULL, /* help */ + copy_spdu_uds_mapping_cb, + update_spdu_uds_mapping, + NULL, + post_update_spdu_uds_mapping_cb, + NULL, /* reset */ + spdu_uds_mapping_uat_fields + ); + + prefs_register_uat_preference(spdu_module, "_spdu_uds_mapping", "UDS Mappings", + "A table to map UDS payloads to Signal PDUs", spdu_uds_mapping_uat); } void @@ -2469,6 +2634,8 @@ proto_reg_handoff_signal_pdu(void) { heur_dissector_add("dlt", dissect_spdu_message_dlt_heur, "Signal-PDU-Heuristic", "signal_pdu_dlt_heur", proto_signal_pdu, HEURISTIC_ENABLE); + heur_dissector_add("uds", dissect_spdu_message_uds_heur, "Signal-PDU-Heuristic", "signal_pdu_uds_heur", proto_signal_pdu, HEURISTIC_ENABLE); + initialized = TRUE; } } diff --git a/epan/dissectors/packet-uds.c b/epan/dissectors/packet-uds.c index ac9e96ad5a..13b38f6757 100644 --- a/epan/dissectors/packet-uds.c +++ b/epan/dissectors/packet-uds.c @@ -13,6 +13,7 @@ #include <epan/packet.h> #include <epan/uat.h> #include <wsutil/bits_ctz.h> +#include <epan/dissectors/packet-uds.h> #include <epan/dissectors/packet-doip.h> #include <epan/dissectors/packet-iso10681.h> #include <epan/dissectors/packet-iso15765.h> @@ -23,30 +24,6 @@ void proto_reg_handoff_uds(void); #define DATAFILE_UDS_ROUTINE_IDS "UDS_routine_identifiers" #define DATAFILE_UDS_DATA_IDS "UDS_data_identifiers" -#define UDS_SERVICES_DSC 0x10 -#define UDS_SERVICES_ER 0x11 -#define UDS_SERVICES_CDTCI 0x14 -#define UDS_SERVICES_RDTCI 0x19 -#define UDS_SERVICES_RDBI 0x22 -#define UDS_SERVICES_RMBA 0x23 -#define UDS_SERVICES_RSDBI 0x24 -#define UDS_SERVICES_SA 0x27 -#define UDS_SERVICES_CC 0x28 -#define UDS_SERVICES_RDBPI 0x2A -#define UDS_SERVICES_DDDI 0x2C -#define UDS_SERVICES_WDBI 0x2E -#define UDS_SERVICES_IOCBI 0x2F -#define UDS_SERVICES_RC 0x31 -#define UDS_SERVICES_RD 0x34 -#define UDS_SERVICES_RU 0x35 -#define UDS_SERVICES_TD 0x36 -#define UDS_SERVICES_RTE 0x37 -#define UDS_SERVICES_RFT 0x38 -#define UDS_SERVICES_WMBA 0x3D -#define UDS_SERVICES_TP 0x3E -#define UDS_SERVICES_ERR 0x3F -#define UDS_SERVICES_CDTCS 0x85 - #define UDS_RESPONSE_CODES_GR 0x10 #define UDS_RESPONSE_CODES_SNS 0x11 #define UDS_RESPONSE_CODES_SFNS 0x12 @@ -70,9 +47,6 @@ void proto_reg_handoff_uds(void); #define UDS_RESPONSE_CODES_SFNSIAS 0x7E #define UDS_RESPONSE_CODES_SNSIAS 0x7F - -#define UDS_SID_MASK 0xBF -#define UDS_REPLY_MASK 0x40 #define UDS_SID_OFFSET 0 #define UDS_SID_LEN 1 #define UDS_DATA_OFFSET 1 @@ -386,6 +360,9 @@ static dissector_handle_t uds_handle_doip; static dissector_handle_t uds_handle_iso10681; static dissector_handle_t uds_handle_iso15765; +/*** Subdissectors ***/ +static heur_dissector_list_t heur_subdissector_list; +static heur_dtbl_entry_t *heur_dtbl_entry; /*** Configuration ***/ typedef struct _generic_addr_id_string { @@ -609,6 +586,19 @@ tvb_get_guintX(tvbuff_t *tvb, const gint offset, const gint size, const guint en return 0; } +gboolean +call_heur_subdissector_uds(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint8 service, gboolean reply, guint32 id, guint32 uds_address) +{ + uds_info_t uds_info; + + uds_info.id = id; + uds_info.uds_address = uds_address; + uds_info.reply = reply; + uds_info.service = service; + + return dissector_try_heuristic(heur_subdissector_list, tvb, pinfo, tree, &heur_dtbl_entry, &uds_info); +} + static int dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint16 source_address, guint16 target_address, guint8 number_of_addresses_valid) { @@ -619,6 +609,7 @@ dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint1 const char *service_name; guint32 ecu_address; guint32 data_length = tvb_reported_length(tvb); + tvbuff_t *payload_tvb; col_set_str(pinfo->cinfo, COL_PROTOCOL, "UDS"); col_clear(pinfo->cinfo,COL_INFO); @@ -700,6 +691,10 @@ dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint1 proto_tree_add_item(subtree, hf_uds_rdbi_data_record, tvb, UDS_RDBI_DATA_RECORD_OFFSET, record_length, ENC_NA); + + payload_tvb = tvb_new_subset_length(tvb, UDS_RDBI_DATA_RECORD_OFFSET, record_length); + call_heur_subdissector_uds(payload_tvb, pinfo, tree, service, TRUE, data_identifier, ecu_address); + col_append_fstr(pinfo->cinfo, COL_INFO, " 0x%04x", data_identifier); infocol_append_data_name(pinfo, ecu_address, data_identifier); col_append_fstr(pinfo->cinfo, COL_INFO, " %s", @@ -761,6 +756,10 @@ dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint1 guint32 record_length = data_length - UDS_WDBI_DATA_RECORD_OFFSET; proto_tree_add_item(subtree, hf_uds_wdbi_data_record, tvb, UDS_WDBI_DATA_RECORD_OFFSET, record_length, ENC_NA); + + payload_tvb = tvb_new_subset_length(tvb, UDS_WDBI_DATA_RECORD_OFFSET, record_length); + call_heur_subdissector_uds(payload_tvb, pinfo, tree, service, FALSE, enum_val, ecu_address); + col_append_fstr(pinfo->cinfo, COL_INFO, " 0x%04x", enum_val); infocol_append_data_name(pinfo, ecu_address, enum_val); col_append_fstr(pinfo->cinfo, COL_INFO, " %s", @@ -820,6 +819,9 @@ dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint1 col_append_fstr(pinfo->cinfo, COL_INFO, " %s", tvb_bytes_to_str_punct(pinfo->pool, tvb, UDS_RC_STATUS_RECORD_OFFSET, status_record_len, ' ')); + + payload_tvb = tvb_new_subset_length(tvb, UDS_RC_STATUS_RECORD_OFFSET, status_record_len); + call_heur_subdissector_uds(payload_tvb, pinfo, tree, service, TRUE, identifier, ecu_address); } } } else { @@ -830,6 +832,9 @@ dissect_uds_internal(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint1 col_append_fstr(pinfo->cinfo, COL_INFO, " %s", tvb_bytes_to_str_punct(pinfo->pool, tvb, UDS_RC_OPTION_RECORD_OFFSET, option_record_len, ' ')); + + payload_tvb = tvb_new_subset_length(tvb, UDS_RC_OPTION_RECORD_OFFSET, option_record_len); + call_heur_subdissector_uds(payload_tvb, pinfo, tree, service, FALSE, identifier, ecu_address); } } break; @@ -1441,6 +1446,8 @@ proto_register_uds(void) prefs_register_uat_preference(uds_module, "_uds_data_id_list", "UDS Data Identifier List", "A table to define names of UDS Data Identifier", uds_data_ids_uat); + + heur_subdissector_list = register_heur_dissector_list("uds", proto_uds); } void diff --git a/epan/dissectors/packet-uds.h b/epan/dissectors/packet-uds.h new file mode 100644 index 0000000000..49910b2d79 --- /dev/null +++ b/epan/dissectors/packet-uds.h @@ -0,0 +1,63 @@ +/* packet-uds.h + * ISO 14229-2 ISO UDS + * By Dr. Lars Voelker <lars.voelker@technica-engineering.de> + * Copyright 2021-2021 Dr. Lars Voelker + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PACKET_UDS_H__ +#define __PACKET_UDS_H__ + +#define UDS_SID_MASK 0xBF +#define UDS_REPLY_MASK 0x40 + +#define UDS_SERVICES_DSC 0x10 +#define UDS_SERVICES_ER 0x11 +#define UDS_SERVICES_CDTCI 0x14 +#define UDS_SERVICES_RDTCI 0x19 +#define UDS_SERVICES_RDBI 0x22 +#define UDS_SERVICES_RMBA 0x23 +#define UDS_SERVICES_RSDBI 0x24 +#define UDS_SERVICES_SA 0x27 +#define UDS_SERVICES_CC 0x28 +#define UDS_SERVICES_RDBPI 0x2A +#define UDS_SERVICES_DDDI 0x2C +#define UDS_SERVICES_WDBI 0x2E +#define UDS_SERVICES_IOCBI 0x2F +#define UDS_SERVICES_RC 0x31 +#define UDS_SERVICES_RD 0x34 +#define UDS_SERVICES_RU 0x35 +#define UDS_SERVICES_TD 0x36 +#define UDS_SERVICES_RTE 0x37 +#define UDS_SERVICES_RFT 0x38 +#define UDS_SERVICES_WMBA 0x3D +#define UDS_SERVICES_TP 0x3E +#define UDS_SERVICES_ERR 0x3F +#define UDS_SERVICES_CDTCS 0x85 + +typedef struct uds_info { + guint32 id; + guint32 uds_address; + gboolean reply; + guint8 service; +} uds_info_t; + +#endif /* __PACKET_UDS_H__ */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |