2 * Routines for Bluetooth Headset Profile (HSP)
4 * Copyright 2013, Michal Labedzki for Tieto Corporation
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include <epan/packet.h>
16 #include <epan/prefs.h>
17 #include <epan/expert.h>
18 #include <epan/strutil.h>
19 #include <epan/unit_strings.h>
21 #include "packet-btrfcomm.h"
22 #include "packet-btsdp.h"
24 static int proto_bthsp
;
26 static int hf_command
;
27 static int hf_parameters
;
28 static int hf_command_in
;
29 static int hf_unsolicited
;
32 static int hf_at_cmd_type
;
33 static int hf_at_command_line_prefix
;
34 static int hf_at_ignored
;
35 static int hf_parameter
;
36 static int hf_unknown_parameter
;
38 static int hf_fragment
;
39 static int hf_fragmented
;
44 static expert_field ei_non_mandatory_command
;
45 static expert_field ei_invalid_usage
;
46 static expert_field ei_unknown_parameter
;
47 static expert_field ei_vgm_gain
;
48 static expert_field ei_vgs_gain
;
49 static expert_field ei_ckpd
;
52 static int ett_bthsp_command
;
53 static int ett_bthsp_parameters
;
55 static dissector_handle_t bthsp_handle
;
57 static wmem_tree_t
*fragments
;
59 #define ROLE_UNKNOWN 0
63 #define TYPE_UNKNOWN 0x0000
64 #define TYPE_RESPONSE_ACK 0x0d0a
65 #define TYPE_RESPONSE 0x003a
66 #define TYPE_ACTION 0x003d
67 #define TYPE_ACTION_SIMPLY 0x000d
68 #define TYPE_READ 0x003f
69 #define TYPE_TEST 0x3d3f
71 static int hsp_role
= ROLE_UNKNOWN
;
73 enum reassemble_state_t
{
79 typedef struct _fragment_t
{
80 uint32_t interface_id
;
89 struct _fragment_t
*previous_fragment
;
91 unsigned reassemble_start_offset
;
92 unsigned reassemble_end_offset
;
93 enum reassemble_state_t reassemble_state
;
96 typedef struct _at_cmd_t
{
98 const char *long_name
;
100 bool (*check_command
)(int role
, uint16_t type
);
101 bool (*dissect_parameter
)(tvbuff_t
*tvb
, packet_info
*pinfo
,
102 proto_tree
*tree
, int offset
, int role
, uint16_t type
,
103 uint8_t *parameter_stream
, unsigned parameter_number
,
104 int parameter_length
, void **data
);
107 static const value_string role_vals
[] = {
108 { ROLE_UNKNOWN
, "Unknown" },
109 { ROLE_AG
, "AG - Audio Gate" },
110 { ROLE_HS
, "HS - Headset" },
114 static const value_string at_cmd_type_vals
[] = {
115 { 0x0d, "Action Command" },
116 { 0x3a, "Response" },
117 { 0x3d, "Action Command" },
118 { 0x3f, "Read Command" },
119 { 0x0d0a, "Response" },
120 { 0x3d3f, "Test Command" },
124 static const enum_val_t pref_hsp_role
[] = {
125 { "off", "Off", ROLE_UNKNOWN
},
126 { "ag", "Sent is AG, Rcvd is HS", ROLE_AG
},
127 { "hs", "Sent is HS, Rcvd is AG", ROLE_HS
},
131 static const unit_name_string units_slash15
= { "/15", NULL
};
133 void proto_register_bthsp(void);
134 void proto_reg_handoff_bthsp(void);
136 static uint32_t get_uint_parameter(uint8_t *parameter_stream
, int parameter_length
)
141 val
= (uint8_t *) wmem_alloc(wmem_packet_scope(), parameter_length
+ 1);
142 memcpy(val
, parameter_stream
, parameter_length
);
143 val
[parameter_length
] = '\0';
144 value
= (uint32_t) g_ascii_strtoull(val
, NULL
, 10);
149 static bool check_vgs(int role
, uint16_t type
) {
150 if (role
== ROLE_HS
&& type
== TYPE_ACTION
) return true;
151 if (role
== ROLE_AG
&& type
== TYPE_RESPONSE
) return true;
156 static bool check_vgm(int role
, uint16_t type
) {
157 if (role
== ROLE_HS
&& type
== TYPE_ACTION
) return true;
158 if (role
== ROLE_AG
&& type
== TYPE_RESPONSE
) return true;
163 static bool check_ckpd(int role
, uint16_t type
) {
164 if (role
== ROLE_HS
&& type
== TYPE_ACTION
) return true;
169 static bool check_only_ag_role(int role
, uint16_t type
) {
170 if (role
== ROLE_AG
&& type
== TYPE_RESPONSE_ACK
) return true;
176 dissect_vgs_parameter(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
177 int offset
, int role
, uint16_t type
, uint8_t *parameter_stream
,
178 unsigned parameter_number
, int parameter_length
, void **data _U_
)
183 if (!check_vgs(role
, type
)) return false;
185 if (parameter_number
> 0) return false;
187 value
= get_uint_parameter(parameter_stream
, parameter_length
);
189 pitem
= proto_tree_add_uint(tree
, hf_vgs
, tvb
, offset
, parameter_length
, value
);
192 expert_add_info(pinfo
, pitem
, &ei_vgs_gain
);
199 dissect_vgm_parameter(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
200 int offset
, int role
, uint16_t type
, uint8_t *parameter_stream
,
201 unsigned parameter_number
, int parameter_length
, void **data _U_
)
206 if (!check_vgm(role
, type
)) return false;
208 if (parameter_number
> 0) return false;
210 value
= get_uint_parameter(parameter_stream
, parameter_length
);
212 pitem
= proto_tree_add_uint(tree
, hf_vgm
, tvb
, offset
, parameter_length
, value
);
215 expert_add_info(pinfo
, pitem
, &ei_vgm_gain
);
222 dissect_ckpd_parameter(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
223 int offset
, int role
, uint16_t type
, uint8_t *parameter_stream
,
224 unsigned parameter_number
, int parameter_length
, void **data _U_
)
229 if (!check_ckpd(role
, type
)) return false;
232 if (parameter_number
> 0) return false;
234 value
= get_uint_parameter(parameter_stream
, parameter_length
);
236 pitem
= proto_tree_add_uint(tree
, hf_ckpd
, tvb
, offset
, parameter_length
, value
);
239 expert_add_info(pinfo
, pitem
, &ei_ckpd
);
246 dissect_no_parameter(tvbuff_t
*tvb _U_
, packet_info
*pinfo _U_
, proto_tree
*tree _U_
,
247 int offset _U_
, int role _U_
, uint16_t type _U_
, uint8_t *parameter_stream _U_
,
248 unsigned parameter_number _U_
, int parameter_length _U_
, void **data _U_
)
253 static const at_cmd_t at_cmds
[] = {
254 { "+VGS", "Gain of Speaker", check_vgs
, dissect_vgs_parameter
},
255 { "+VGM", "Gain of Microphone", check_vgm
, dissect_vgm_parameter
},
256 { "+CKPD", "Control Keypad", check_ckpd
, dissect_ckpd_parameter
},
257 { "ERROR", "ERROR", check_only_ag_role
, dissect_no_parameter
},
258 { "RING", "Incoming Call Indication", check_only_ag_role
, dissect_no_parameter
},
259 { "OK", "OK", check_only_ag_role
, dissect_no_parameter
},
260 { NULL
, NULL
, NULL
, NULL
}
265 dissect_at_command(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
266 int offset
, uint32_t role
, int command_number
)
269 proto_tree
*command_item
;
270 proto_item
*command_tree
;
271 proto_tree
*parameters_item
= NULL
;
272 proto_item
*parameters_tree
= NULL
;
273 char *col_str
= NULL
;
275 char *at_command
= NULL
;
277 unsigned i_char_fix
= 0;
279 const at_cmd_t
*i_at_cmd
;
280 int parameter_length
;
281 unsigned parameter_number
= 0;
282 int first_parameter_offset
= offset
;
283 int last_parameter_offset
= offset
;
284 uint16_t type
= TYPE_UNKNOWN
;
290 length
= tvb_reported_length_remaining(tvb
, offset
);
292 return tvb_reported_length(tvb
);
294 if (!command_number
) {
295 proto_tree_add_item(tree
, hf_data
, tvb
, offset
, length
, ENC_NA
| ENC_ASCII
);
296 col_str
= (char *) wmem_alloc(pinfo
->pool
, length
+ 1);
297 tvb_memcpy(tvb
, col_str
, offset
, length
);
298 col_str
[length
] = '\0';
301 at_stream
= (char *) wmem_alloc(pinfo
->pool
, length
+ 1);
302 tvb_memcpy(tvb
, at_stream
, offset
, length
);
303 at_stream
[length
] = '\0';
304 while (at_stream
[i_char
]) {
305 at_stream
[i_char
] = g_ascii_toupper(at_stream
[i_char
]);
306 if (!command_number
) {
307 col_str
[i_char
] = g_ascii_toupper(col_str
[i_char
]);
308 if (!g_ascii_isgraph(col_str
[i_char
])) col_str
[i_char
] = ' ';
313 command_item
= proto_tree_add_none_format(tree
, hf_command
, tvb
,
314 offset
, 0, "Command %u", command_number
);
315 command_tree
= proto_item_add_subtree(command_item
, ett_bthsp_command
);
317 if (!command_number
) col_append_str(pinfo
->cinfo
, COL_INFO
, col_str
);
319 if (role
== ROLE_HS
) {
320 if (command_number
) {
321 at_command
= at_stream
;
324 at_command
= g_strstr_len(at_stream
, length
, "AT");
327 i_char
= (int) (at_command
- at_stream
);
330 proto_tree_add_item(command_tree
, hf_at_ignored
, tvb
, offset
,
331 i_char
, ENC_NA
| ENC_ASCII
);
335 proto_tree_add_item(command_tree
, hf_at_command_line_prefix
,
336 tvb
, offset
, 2, ENC_NA
| ENC_ASCII
);
339 at_command
= at_stream
;
341 at_command
+= i_char
;
343 i_char_fix
+= i_char
;
348 at_command
= at_stream
;
350 while (i_char
<= length
&&
351 (at_command
[i_char
] == '\r' || at_command
[i_char
] == '\n' ||
352 at_command
[i_char
] == ' ' || at_command
[i_char
] == '\t')) {
353 /* ignore white characters */
358 at_command
+= i_char
;
360 i_char_fix
+= i_char
;
366 while (i_char
< length
&&
367 (at_command
[i_char
] != '\r' && at_command
[i_char
] != '=' &&
368 at_command
[i_char
] != ';' && at_command
[i_char
] != '?' &&
369 at_command
[i_char
] != ':')) {
374 if (at_command
[0] == '\r') {
375 pitem
= proto_tree_add_item(command_tree
, hf_at_cmd
, tvb
, offset
- 2,
376 2, ENC_NA
| ENC_ASCII
);
380 while (i_at_cmd
->name
) {
381 if (g_str_has_prefix(&at_command
[0], i_at_cmd
->name
)) {
382 pitem
= proto_tree_add_item(command_tree
, hf_at_cmd
, tvb
, offset
,
383 (int) strlen(i_at_cmd
->name
), ENC_NA
| ENC_ASCII
);
384 proto_item_append_text(pitem
, " (%s)", i_at_cmd
->long_name
);
391 pitem
= proto_tree_add_item(command_tree
, hf_at_cmd
, tvb
, offset
,
392 i_char
, ENC_NA
| ENC_ASCII
);
397 if (i_at_cmd
&& i_at_cmd
->name
== NULL
) {
400 name
= format_text(pinfo
->pool
, at_command
, i_char
+ 1);
401 proto_item_append_text(command_item
, ": %s (Unknown)", name
);
402 proto_item_append_text(pitem
, " (Unknown - Non-Standard HSP Command)");
403 expert_add_info(pinfo
, pitem
, &ei_non_mandatory_command
);
404 } else if (i_at_cmd
== NULL
) {
405 proto_item_append_text(command_item
, ": AT");
407 proto_item_append_text(command_item
, ": %s", i_at_cmd
->name
);
412 if (i_at_cmd
&& g_strcmp0(i_at_cmd
->name
, "D")) {
413 if (length
>= 2 && at_command
[i_char
] == '=' && at_command
[i_char
+ 1] == '?') {
414 type
= at_command
[i_char
] << 8 | at_command
[i_char
+ 1];
415 proto_tree_add_uint(command_tree
, hf_at_cmd_type
, tvb
, offset
, 2, type
);
418 } else if (role
== ROLE_AG
&& length
>= 2 && at_command
[i_char
] == '\r' && at_command
[i_char
+ 1] == '\n') {
419 type
= at_command
[i_char
] << 8 | at_command
[i_char
+ 1];
420 proto_tree_add_uint(command_tree
, hf_at_cmd_type
, tvb
, offset
, 2, type
);
423 } else if (length
>= 1 && (at_command
[i_char
] == '=' ||
424 at_command
[i_char
] == '\r' ||
425 at_command
[i_char
] == ':' ||
426 at_command
[i_char
] == '?')) {
427 type
= at_command
[i_char
];
428 proto_tree_add_uint(command_tree
, hf_at_cmd_type
, tvb
, offset
, 1, type
);
434 if (i_at_cmd
&& i_at_cmd
->check_command
&& !i_at_cmd
->check_command(role
, type
)) {
435 expert_add_info(pinfo
, command_item
, &ei_invalid_usage
);
438 parameters_item
= proto_tree_add_none_format(command_tree
, hf_parameters
, tvb
,
439 offset
, 0, "Parameters");
440 parameters_tree
= proto_item_add_subtree(parameters_item
, ett_bthsp_parameters
);
444 while (i_char
< length
) {
446 while (at_command
[i_char
] == ' ' || at_command
[i_char
] == '\t') {
451 parameter_length
= 0;
456 if (at_command
[i_char
+ parameter_length
] != '\r') {
457 while (i_char
+ parameter_length
< length
&&
458 at_command
[i_char
+ parameter_length
] != '\r') {
460 if (at_command
[i_char
+ parameter_length
] == ';') {
465 if (at_command
[i_char
+ parameter_length
] == '"') {
466 quotation
= quotation
? false : true;
469 if (quotation
== true) {
470 parameter_length
+= 1;
474 if (at_command
[i_char
+ parameter_length
] == '(') {
477 if (at_command
[i_char
+ parameter_length
] == ')') {
481 if (brackets
== 0 && at_command
[i_char
+ parameter_length
] == ',') {
485 parameter_length
+= 1;
488 /* TODO: Save bthsp.at_cmd, bthsp.at_cmd.type, frame_time and frame_num here in
490 if (role == ROLE_HS && pinfo->fd->visited == 0) {
492 at_cmd_db = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
506 first_response_in (if 0 - no response)
509 interface_id = interface_id;
510 adapter_id = adapter_id;
513 frame_number = pinfo->num;
517 key[0].key = &interface_id;
519 key[1].key = &adapter_id;
521 key[2].key = &chandle;
525 key[4].key = &frame_number;
529 cmd = wmem_new(wmem_file_scope(), at_cmd_entry_t);
530 cmd->interface_id = interface_id;
531 cmd->adapter_id = adapter_id;
532 cmd->chandle = chandle;
535 cmd->frame_number = pinfo->num;
536 cmd->status = STATUS_NO_RESPONSE;
537 cmd->time = pinfo->abs_ts;
540 cmd->first_response_in = 0;
542 wmem_tree_insert32_array(at_cmd_db, key, cmd);
547 first_parameter_offset
= offset
;
548 if (type
== TYPE_ACTION
|| type
== TYPE_RESPONSE
) {
549 if (i_at_cmd
&& (i_at_cmd
->dissect_parameter
!= NULL
&&
550 !i_at_cmd
->dissect_parameter(tvb
, pinfo
, parameters_tree
, offset
, role
,
551 type
, &at_command
[i_char
], parameter_number
, parameter_length
, &data
) )) {
552 pitem
= proto_tree_add_item(parameters_tree
,
553 hf_unknown_parameter
, tvb
, offset
,
554 parameter_length
, ENC_NA
| ENC_ASCII
);
555 expert_add_info(pinfo
, pitem
, &ei_unknown_parameter
);
556 } else if (i_at_cmd
&& i_at_cmd
->dissect_parameter
== NULL
) {
557 proto_tree_add_item(parameters_tree
, hf_parameter
, tvb
, offset
,
558 parameter_length
, ENC_NA
| ENC_ASCII
);
563 if (type
!= TYPE_ACTION_SIMPLY
&& type
!= TYPE_RESPONSE_ACK
&& type
!= TYPE_TEST
&& type
!= TYPE_READ
)
564 parameter_number
+= 1;
565 i_char
+= parameter_length
;
566 offset
+= parameter_length
;
567 last_parameter_offset
= offset
;
569 if (role
== ROLE_AG
&&
570 i_char
+ 1 <= length
&&
571 at_command
[i_char
] == '\r' &&
572 at_command
[i_char
+ 1] == '\n') {
576 } else if (at_command
[i_char
] == ',' ||
577 at_command
[i_char
] == '\r' ||
578 at_command
[i_char
] == ';') {
586 i_char
+= i_char_fix
;
587 proto_item_set_len(command_item
, i_char
);
589 length
= tvb_reported_length_remaining(tvb
, offset
);
592 proto_item_set_len(command_item
, length
);
596 if (parameter_number
> 0 && last_parameter_offset
- first_parameter_offset
> 0)
597 proto_item_set_len(parameters_item
, last_parameter_offset
- first_parameter_offset
);
599 proto_item_append_text(parameters_item
, ": No");
601 if (role
== ROLE_AG
) {
602 unsigned command_frame_number
= 0;
604 if (command_frame_number
) {
605 pitem
= proto_tree_add_uint(command_tree
, hf_command_in
, tvb
, offset
,
606 0, command_frame_number
);
607 proto_item_set_generated(pitem
);
609 pitem
= proto_tree_add_item(command_tree
, hf_unsolicited
, tvb
, offset
, 0, ENC_NA
);
610 proto_item_set_generated(pitem
);
618 dissect_bthsp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
620 proto_item
*main_item
;
621 proto_tree
*main_tree
;
624 uint32_t role
= ROLE_UNKNOWN
;
625 wmem_tree_key_t key
[10];
626 uint32_t interface_id
;
630 uint32_t frame_number
;
632 uint32_t bd_addr_oui
;
634 fragment_t
*fragment
;
635 fragment_t
*previous_fragment
;
636 fragment_t
*i_fragment
;
641 tvbuff_t
*reassembled_tvb
= NULL
;
642 unsigned reassemble_start_offset
= 0;
643 unsigned reassemble_end_offset
= 0;
646 previous_proto
= (GPOINTER_TO_INT(wmem_list_frame_data(wmem_list_frame_prev(wmem_list_tail(pinfo
->layers
)))));
647 if (data
&& previous_proto
== proto_btrfcomm
) {
648 btrfcomm_data_t
*rfcomm_data
;
650 rfcomm_data
= (btrfcomm_data_t
*) data
;
652 interface_id
= rfcomm_data
->interface_id
;
653 adapter_id
= rfcomm_data
->adapter_id
;
654 chandle
= rfcomm_data
->chandle
;
655 dlci
= rfcomm_data
->dlci
;
656 direction
= (rfcomm_data
->is_local_psm
) ? P2P_DIR_SENT
: P2P_DIR_RECV
;
658 if (direction
== P2P_DIR_RECV
) {
659 bd_addr_oui
= rfcomm_data
->remote_bd_addr_oui
;
660 bd_addr_id
= rfcomm_data
->remote_bd_addr_id
;
666 interface_id
= HCI_INTERFACE_DEFAULT
;
667 adapter_id
= HCI_ADAPTER_DEFAULT
;
670 direction
= P2P_DIR_UNKNOWN
;
676 main_item
= proto_tree_add_item(tree
, proto_bthsp
, tvb
, 0, tvb_captured_length(tvb
), ENC_NA
);
677 main_tree
= proto_item_add_subtree(main_item
, ett_bthsp
);
679 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "HSP");
681 switch (pinfo
->p2p_dir
) {
683 col_set_str(pinfo
->cinfo
, COL_INFO
, "Sent ");
686 col_set_str(pinfo
->cinfo
, COL_INFO
, "Rcvd ");
689 col_set_str(pinfo
->cinfo
, COL_INFO
, "UnknownDirection ");
693 if ((hsp_role
== ROLE_AG
&& pinfo
->p2p_dir
== P2P_DIR_SENT
) ||
694 (hsp_role
== ROLE_HS
&& pinfo
->p2p_dir
== P2P_DIR_RECV
)) {
696 } else if (hsp_role
!= ROLE_UNKNOWN
) {
700 if (role
== ROLE_UNKNOWN
) {
702 uint32_t service_type
;
703 uint32_t service_channel
;
704 service_info_t
*service_info
;
706 sdp_psm
= SDP_PSM_DEFAULT
;
708 service_type
= BTSDP_RFCOMM_PROTOCOL_UUID
;
709 service_channel
= dlci
>> 1;
710 frame_number
= pinfo
->num
;
713 key
[0].key
= &interface_id
;
715 key
[1].key
= &adapter_id
;
717 key
[2].key
= &sdp_psm
;
719 key
[3].key
= &direction
;
721 key
[4].key
= &bd_addr_oui
;
723 key
[5].key
= &bd_addr_id
;
725 key
[6].key
= &service_type
;
727 key
[7].key
= &service_channel
;
729 key
[8].key
= &frame_number
;
733 service_info
= btsdp_get_service_info(key
);
734 if (service_info
&& service_info
->interface_id
== interface_id
&&
735 service_info
->adapter_id
== adapter_id
&&
736 service_info
->sdp_psm
== SDP_PSM_DEFAULT
&&
737 ((service_info
->direction
== P2P_DIR_RECV
&&
738 service_info
->bd_addr_oui
== bd_addr_oui
&&
739 service_info
->bd_addr_id
== bd_addr_id
) ||
740 (service_info
->direction
!= P2P_DIR_RECV
&&
741 service_info
->bd_addr_oui
== 0 &&
742 service_info
->bd_addr_id
== 0)) &&
743 service_info
->type
== BTSDP_RFCOMM_PROTOCOL_UUID
&&
744 service_info
->channel
== (dlci
>> 1)) {
745 if ((service_info
->uuid
.bt_uuid
== BTSDP_HSP_GW_SERVICE_UUID
&& service_info
->direction
== P2P_DIR_RECV
&& pinfo
->p2p_dir
== P2P_DIR_SENT
) ||
746 (service_info
->uuid
.bt_uuid
== BTSDP_HSP_GW_SERVICE_UUID
&& service_info
->direction
== P2P_DIR_SENT
&& pinfo
->p2p_dir
== P2P_DIR_RECV
) ||
747 ((service_info
->uuid
.bt_uuid
== BTSDP_HSP_SERVICE_UUID
|| service_info
->uuid
.bt_uuid
== BTSDP_HSP_HS_SERVICE_UUID
) && service_info
->direction
== P2P_DIR_RECV
&& pinfo
->p2p_dir
== P2P_DIR_RECV
) ||
748 ((service_info
->uuid
.bt_uuid
== BTSDP_HSP_SERVICE_UUID
|| service_info
->uuid
.bt_uuid
== BTSDP_HSP_HS_SERVICE_UUID
) && service_info
->direction
== P2P_DIR_SENT
&& pinfo
->p2p_dir
== P2P_DIR_SENT
)) {
756 pitem
= proto_tree_add_uint(main_tree
, hf_role
, tvb
, 0, 0, role
);
757 proto_item_set_generated(pitem
);
759 if (role
== ROLE_UNKNOWN
) {
760 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "Data: %s",
761 tvb_format_text(pinfo
->pool
, tvb
, 0, tvb_reported_length(tvb
)));
762 proto_tree_add_item(main_tree
, hf_data
, tvb
, 0, tvb_captured_length(tvb
), ENC_NA
| ENC_ASCII
);
763 return tvb_reported_length(tvb
);
767 if (!pinfo
->fd
->visited
) {
768 frame_number
= pinfo
->num
- 1;
771 key
[0].key
= &interface_id
;
773 key
[1].key
= &adapter_id
;
775 key
[2].key
= &chandle
;
781 key
[5].key
= &frame_number
;
785 previous_fragment
= (fragment_t
*) wmem_tree_lookup32_array_le(fragments
, key
);
786 if (!(previous_fragment
&& previous_fragment
->interface_id
== interface_id
&&
787 previous_fragment
->adapter_id
== adapter_id
&&
788 previous_fragment
->chandle
== chandle
&&
789 previous_fragment
->dlci
== dlci
&&
790 previous_fragment
->role
== role
&&
791 previous_fragment
->reassemble_state
!= REASSEMBLE_DONE
)) {
792 previous_fragment
= NULL
;
795 frame_number
= pinfo
->num
;
798 key
[0].key
= &interface_id
;
800 key
[1].key
= &adapter_id
;
802 key
[2].key
= &chandle
;
808 key
[5].key
= &frame_number
;
812 fragment
= wmem_new(wmem_file_scope(), fragment_t
);
813 fragment
->interface_id
= interface_id
;
814 fragment
->adapter_id
= adapter_id
;
815 fragment
->chandle
= chandle
;
816 fragment
->dlci
= dlci
;
817 fragment
->role
= role
;
818 fragment
->idx
= previous_fragment
? previous_fragment
->idx
+ previous_fragment
->length
: 0;
819 fragment
->reassemble_state
= REASSEMBLE_FRAGMENT
;
820 fragment
->length
= tvb_reported_length(tvb
);
821 fragment
->data
= (uint8_t *) wmem_alloc(wmem_file_scope(), fragment
->length
);
822 fragment
->previous_fragment
= previous_fragment
;
823 tvb_memcpy(tvb
, fragment
->data
, offset
, fragment
->length
);
825 wmem_tree_insert32_array(fragments
, key
, fragment
);
827 /* Detect reassemble end character: \r for HS or \n for AG */
828 length
= tvb_reported_length(tvb
);
829 at_stream
= tvb_get_string_enc(pinfo
->pool
, tvb
, 0, length
, ENC_ASCII
);
831 reassemble_start_offset
= 0;
833 for (i_length
= 0; i_length
< length
; i_length
+= 1) {
834 if (!((role
== ROLE_HS
&& at_stream
[i_length
] == '\r') ||
835 (role
== ROLE_AG
&& at_stream
[i_length
] == '\n'))) {
839 if (role
== ROLE_HS
&& at_stream
[i_length
] == '\r') {
840 reassemble_start_offset
= i_length
+ 1;
841 if (reassemble_end_offset
== 0) reassemble_end_offset
= i_length
+ 1;
844 if (role
== ROLE_AG
&& at_stream
[i_length
] == '\n') {
845 reassemble_start_offset
= i_length
+ 1;
848 frame_number
= pinfo
->num
;
851 key
[0].key
= &interface_id
;
853 key
[1].key
= &adapter_id
;
855 key
[2].key
= &chandle
;
861 key
[5].key
= &frame_number
;
865 fragment
= (fragment_t
*) wmem_tree_lookup32_array_le(fragments
, key
);
866 if (fragment
&& fragment
->interface_id
== interface_id
&&
867 fragment
->adapter_id
== adapter_id
&&
868 fragment
->chandle
== chandle
&&
869 fragment
->dlci
== dlci
&&
870 fragment
->role
== role
) {
871 i_fragment
= fragment
;
872 while (i_fragment
&& i_fragment
->idx
> 0) {
873 i_fragment
= i_fragment
->previous_fragment
;
876 if (i_length
+ 1 == length
&&
878 at_stream
[i_length
] == '\r') {
879 fragment
->reassemble_state
= REASSEMBLE_DONE
;
880 } else if (i_length
+ 1 == length
&&
883 at_stream
[i_length
] == '\n' &&
884 at_stream
[i_length
- 1] == '\r' &&
885 at_stream
[0] == '\r' &&
886 at_stream
[1] == '\n') {
887 fragment
->reassemble_state
= REASSEMBLE_DONE
;
888 } else if (i_length
+ 1 == length
&&
891 at_stream
[i_length
] == '\n' &&
892 at_stream
[i_length
- 1] == '\r' &&
894 i_fragment
->reassemble_state
== REASSEMBLE_FRAGMENT
&&
895 i_fragment
->length
>= 2 &&
896 i_fragment
->data
[0] == '\r' &&
897 i_fragment
->data
[1] == '\n') {
898 fragment
->reassemble_state
= REASSEMBLE_DONE
;
899 } else if (role
== ROLE_HS
) {
900 /* XXX: Temporary disable reassembling of partial message, it seems to be broken */
901 /* fragment->reassemble_state = REASSEMBLE_PARTIALLY;*/
903 fragment
->reassemble_start_offset
= reassemble_start_offset
;
904 fragment
->reassemble_end_offset
= reassemble_end_offset
;
909 /* recover reassembled payload */
910 frame_number
= pinfo
->num
;
913 key
[0].key
= &interface_id
;
915 key
[1].key
= &adapter_id
;
917 key
[2].key
= &chandle
;
923 key
[5].key
= &frame_number
;
927 fragment
= (fragment_t
*) wmem_tree_lookup32_array_le(fragments
, key
);
928 if (fragment
&& fragment
->interface_id
== interface_id
&&
929 fragment
->adapter_id
== adapter_id
&&
930 fragment
->chandle
== chandle
&&
931 fragment
->dlci
== dlci
&&
932 fragment
->role
== role
&&
933 fragment
->reassemble_state
!= REASSEMBLE_FRAGMENT
) {
935 unsigned i_data_offset
;
937 i_data_offset
= fragment
->idx
+ fragment
->length
;
938 at_data
= (uint8_t *) wmem_alloc(pinfo
->pool
, fragment
->idx
+ fragment
->length
);
940 i_fragment
= fragment
;
942 if (i_fragment
&& i_fragment
->reassemble_state
== REASSEMBLE_PARTIALLY
) {
943 i_data_offset
-= i_fragment
->reassemble_end_offset
;
944 memcpy(at_data
+ i_data_offset
, i_fragment
->data
, i_fragment
->reassemble_end_offset
);
945 i_fragment
= i_fragment
->previous_fragment
;
949 while (i_fragment
&& i_fragment
->idx
> 0) {
950 i_data_offset
-= i_fragment
->length
;
951 memcpy(at_data
+ i_data_offset
, i_fragment
->data
, i_fragment
->length
);
952 i_fragment
= i_fragment
->previous_fragment
;
955 if (i_fragment
&& i_fragment
->reassemble_state
== REASSEMBLE_PARTIALLY
) {
956 i_data_offset
-= (i_fragment
->length
- i_fragment
->reassemble_start_offset
);
957 memcpy(at_data
+ i_data_offset
, i_fragment
->data
+ i_fragment
->reassemble_start_offset
,
958 i_fragment
->length
- i_fragment
->reassemble_start_offset
);
959 } else if (i_fragment
) {
960 i_data_offset
-= i_fragment
->length
;
961 memcpy(at_data
+ i_data_offset
, i_fragment
->data
, i_fragment
->length
);
965 if (fragment
->idx
> 0 && fragment
->length
> 0) {
966 proto_tree_add_item(main_tree
, hf_fragment
, tvb
, offset
,
967 tvb_captured_length_remaining(tvb
, offset
), ENC_ASCII
| ENC_NA
);
968 reassembled_tvb
= tvb_new_child_real_data(tvb
, at_data
,
969 fragment
->idx
+ fragment
->length
, fragment
->idx
+ fragment
->length
);
970 add_new_data_source(pinfo
, reassembled_tvb
, "Reassembled HSP");
974 if (reassembled_tvb
) {
975 unsigned reassembled_offset
= 0;
977 while (tvb_reported_length(reassembled_tvb
) > reassembled_offset
) {
978 reassembled_offset
= dissect_at_command(reassembled_tvb
,
979 pinfo
, main_tree
, reassembled_offset
, role
, command_number
);
982 offset
= tvb_captured_length(tvb
);
984 while (tvb_reported_length(tvb
) > (unsigned) offset
) {
985 offset
= dissect_at_command(tvb
, pinfo
, main_tree
, offset
, role
, command_number
);
990 pitem
= proto_tree_add_item(main_tree
, hf_fragmented
, tvb
, 0, 0, ENC_NA
);
991 proto_item_set_generated(pitem
);
993 proto_tree_add_item_ret_display_string(main_tree
, hf_fragment
, tvb
, offset
, -1, ENC_ASCII
, pinfo
->pool
, &display_str
);
994 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "Fragment: %s", display_str
);
995 offset
= tvb_captured_length(tvb
);
1002 proto_register_bthsp(void)
1005 expert_module_t
*expert_bthsp
;
1007 static hf_register_info hf
[] = {
1009 { "Command", "bthsp.command",
1010 FT_NONE
, BASE_NONE
, NULL
, 0,
1014 { "Parameters", "bthsp.parameters",
1015 FT_NONE
, BASE_NONE
, NULL
, 0,
1019 { "Command frame number in", "bthsp.command_in",
1020 FT_FRAMENUM
, BASE_NONE
, NULL
, 0,
1024 { "Unsolicited", "bthsp.unsolicited",
1025 FT_NONE
, BASE_NONE
, NULL
, 0,
1029 { "AT Stream", "bthsp.data",
1030 FT_STRING
, BASE_NONE
, NULL
, 0,
1034 { "Fragment", "bthsp.fragment",
1035 FT_STRING
, BASE_NONE
, NULL
, 0,
1039 { "Fragmented", "bthsp.fragmented",
1040 FT_NONE
, BASE_NONE
, NULL
, 0,
1044 { "Ignored", "bthsp.ignored",
1045 FT_BYTES
, BASE_NONE
, NULL
, 0,
1049 { "Command", "bthsp.at_cmd",
1050 FT_STRING
, BASE_NONE
, NULL
, 0,
1054 { "Type", "bthsp.at_cmd.type",
1055 FT_UINT16
, BASE_HEX
, VALS(at_cmd_type_vals
), 0,
1058 { &hf_at_command_line_prefix
,
1059 { "Command Line Prefix", "bthsp.command_line_prefix",
1060 FT_STRING
, BASE_NONE
, NULL
, 0,
1064 { "Parameter", "bthsp.parameter",
1065 FT_STRING
, BASE_NONE
, NULL
, 0,
1068 { &hf_unknown_parameter
,
1069 { "Unknown Parameter", "bthsp.unknown_parameter",
1070 FT_STRING
, BASE_NONE
, NULL
, 0,
1074 { "Role", "bthsp.role",
1075 FT_UINT8
, BASE_DEC
, VALS(role_vals
), 0,
1079 { "Gain", "bthsp.vgs",
1080 FT_UINT8
, BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_slash15
), 0,
1084 { "Gain", "bthsp.vgm",
1085 FT_UINT8
, BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_slash15
), 0,
1089 { "Key", "bthsp.ckpd",
1090 FT_UINT8
, BASE_DEC
, NULL
, 0,
1095 static ei_register_info ei
[] = {
1096 { &ei_non_mandatory_command
, { "bthsp.expert.non_mandatory_command", PI_PROTOCOL
, PI_NOTE
, "Non-mandatory command in HSP", EXPFILL
}},
1097 { &ei_invalid_usage
, { "bthsp.expert.invalid_usage", PI_PROTOCOL
, PI_WARN
, "Non mandatory type or command in this role", EXPFILL
}},
1098 { &ei_unknown_parameter
, { "bthsp.expert.unknown_parameter", PI_PROTOCOL
, PI_WARN
, "Unknown parameter", EXPFILL
}},
1099 { &ei_vgm_gain
, { "bthsp.expert.vgm", PI_PROTOCOL
, PI_WARN
, "Gain of microphone exceeds range 0-15", EXPFILL
}},
1100 { &ei_vgs_gain
, { "bthsp.expert.vgs", PI_PROTOCOL
, PI_WARN
, "Gain of speaker exceeds range 0-15", EXPFILL
}},
1101 { &ei_ckpd
, { "bthsp.expert.ckpd", PI_PROTOCOL
, PI_WARN
, "Only key 200 is covered in HSP", EXPFILL
}} };
1103 static int *ett
[] = {
1106 &ett_bthsp_parameters
1109 fragments
= wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
1111 proto_bthsp
= proto_register_protocol("Bluetooth HSP Profile", "BT HSP", "bthsp");
1112 bthsp_handle
= register_dissector("bthsp", dissect_bthsp
, proto_bthsp
);
1114 proto_register_field_array(proto_bthsp
, hf
, array_length(hf
));
1115 proto_register_subtree_array(ett
, array_length(ett
));
1117 module
= prefs_register_protocol_subtree("Bluetooth", proto_bthsp
, NULL
);
1118 prefs_register_static_text_preference(module
, "hsp.version",
1119 "Bluetooth Profile HSP version: 1.2",
1120 "Version of profile supported by this dissector.");
1122 prefs_register_enum_preference(module
, "hsp.hsp_role",
1123 "Force treat packets as AG or HS role",
1124 "Force treat packets as AG or HS role",
1125 &hsp_role
, pref_hsp_role
, true);
1127 expert_bthsp
= expert_register_protocol(proto_bthsp
);
1128 expert_register_field_array(expert_bthsp
, ei
, array_length(ei
));
1132 proto_reg_handoff_bthsp(void)
1134 dissector_add_string("bluetooth.uuid", "1108", bthsp_handle
);
1135 dissector_add_string("bluetooth.uuid", "1112", bthsp_handle
);
1136 dissector_add_string("bluetooth.uuid", "1131", bthsp_handle
);
1138 dissector_add_for_decode_as("btrfcomm.dlci", bthsp_handle
);
1142 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1147 * indent-tabs-mode: nil
1150 * vi: set shiftwidth=4 tabstop=8 expandtab:
1151 * :indentSize=4:tabSize=8:noTabs=true: