pbap: add Phone Book Access Client (PBAP) over General Object Exchange (GOEP)

create RFCOMM connection
send OBEX Connect and receive response
retrieve phone book
repeat GET request when response incomplete
start extracting goep_client.h
support CONNECT message
support GET message
add obex_iterator
use obex_iterator
goep_client adds connection_id if available
avoid accessing internal goep state from pbap
start extracting pbap_client.h
events: add meta events and data packet types for GOEP and PBAP
use events and data packet type for goep
use events and data packet type for pbap
remove private fields from GOEP_SUBEVENT_CONNECTION_OPENED
implement SetPhoneBook
return cid in connect operations, return errors if busy, return obex errors
limit OBEX packet size by bearer MTU
split into obex.h, goep_client.h, goep_client.c, pbap_client.h, pbab_client.c
fix state after pull phonebook
add console UI for testing
clean up code
provide packet_handler in create connection for pbap_client and goep_client
annotate headers for goep_client and pbap_client
This commit is contained in:
Matthias Ringwald 2017-03-19 22:32:10 +01:00
parent bfb465b059
commit f1b34e8dd9
13 changed files with 1821 additions and 11 deletions

View File

@ -112,6 +112,7 @@ EXAMPLES = \
hsp_hs_demo \
sm_pairing_peripheral \
sm_pairing_central \
pbap_client \
EXAMPLES_USING_LE = \
ancs_client_demo \
@ -158,6 +159,9 @@ sm_pairing_peripheral.h: sm_pairing_peripheral.gatt
sdp_rfcomm_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${PAN_OBJ} ${SDP_CLIENT} sdp_rfcomm_query.c
${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@
pbap_client: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} obex_iterator.c goep_client.c pbap_client.c pbap_client_demo.c
${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@
sdp_general_query: ${CORE_OBJ} ${COMMON_OBJ} ${CLASSIC_OBJ} ${SDP_CLIENT} sdp_general_query.c
${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@

246
example/pbap_client_demo.c Normal file
View File

@ -0,0 +1,246 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#include "btstack_config.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "btstack_run_loop.h"
#include "l2cap.h"
#include "rfcomm.h"
#include "btstack_event.h"
#include "classic/goep_client.h"
#include "classic/pbap_client.h"
#ifdef HAVE_POSIX_STDIN
#include <unistd.h>
#include "stdin_support.h"
#endif
#ifndef HAVE_POSIX_STDIN
static int step = 0;
#endif
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static bd_addr_t remote_addr;
// MBP2016 "F4-0F-24-3B-1B-E1"
// Nexus 7 "30-85-A9-54-2E-78"
static const char * remote_addr_string = "30-85-A9-54-2E-78";
static btstack_packet_callback_registration_t hci_event_callback_registration;
static uint16_t pbap_cid;
#ifdef HAVE_POSIX_STDIN
// Testig User Interface
static void show_usage(void){
bd_addr_t iut_address;
gap_local_bd_addr(iut_address);
printf("\n--- Bluetooth PBAP Client (HF) Test Console %s ---\n", bd_addr_to_str(iut_address));
printf("\n");
printf("a - establish PBAP connection to %s\n", bd_addr_to_str(remote_addr));
printf("b - set phonebook '/telecom/pb'\n");
printf("c - set phonebook '/SIM1/telecom/pb'\n");
printf("d - pull phonebook\n");
printf("e - disconnnect\n");
printf("---\n");
printf("Ctrl-c - exit\n");
printf("---\n");
}
static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
UNUSED(ds);
UNUSED(callback_type);
char cmd = btstack_stdin_read();
switch (cmd){
case 'a':
printf("[+] Connecting to %s...\n", bd_addr_to_str(remote_addr));
pbap_connect(&packet_handler, remote_addr, &pbap_cid);
break;
case 'b':
printf("[+] Set Phonebook 'telecom/pb'\n");
pbap_set_phonebook(pbap_cid, "telecom/pb");
break;
case 'c':
printf("[+] Set Phonebook 'SIM1/telecom/pb'\n");
pbap_set_phonebook(pbap_cid, "SIM1/telecom/pb");
break;
case 'd':
pbap_pull_phonebook(pbap_cid);
break;
case 'e':
pbap_disconnect(pbap_cid);
break;
default:
show_usage();
break;
}
}
// packet handler for interactive console
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
int i;
switch (packet_type){
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
// BTstack activated, get started
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){
show_usage();
}
break;
case HCI_EVENT_PBAP_META:
switch (hci_event_pbap_meta_get_subevent_code(packet)){
case PBAP_SUBEVENT_CONNECTION_OPENED:
printf("[+] Connected\n");
break;
case PBAP_SUBEVENT_CONNECTION_CLOSED:
printf("[+] Connection closed\n");
break;
case PBAP_SUBEVENT_OPERATION_COMPLETED:
printf("[+] Operation complete\n");
break;
default:
break;
}
break;
default:
break;
}
break;
case PBAP_DATA_PACKET:
for (i=0;i<size;i++){
printf("%c", packet[i]);
}
break;
default:
break;
}
}
#else
// packet handler for emdded system with fixed operation sequence
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
int i;
switch (packet_type){
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
// BTstack activated, get started
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){
printf("[+] Connect to Phone Book Server on %s\n", bd_addr_to_str(remote_addr));
pbap_connect(&packet_handler, remote_addr, &pbap_cid);
}
break;
case HCI_EVENT_PBAP_META:
switch (hci_event_pbap_meta_get_subevent_code(packet)){
case PBAP_SUBEVENT_CONNECTION_OPENED:
printf("[+] Connected\n");
printf("[+] Pull phonebook\n");
pbap_pull_phonebook(pbap_cid);
break;
case PBAP_SUBEVENT_CONNECTION_CLOSED:
printf("[+] Connection closed\n");
break;
case PBAP_SUBEVENT_OPERATION_COMPLETED:
printf("[+] Operation complete\n");
printf("[+] Pull Phonebook complete\n");
pbap_disconnect(pbap_cid);
break;
default:
break;
}
break;
default:
break;
}
break;
case PBAP_DATA_PACKET:
for (i=0;i<size;i++){
printf("%c", packet[i]);
}
break;
default:
break;
}
}
#endif
int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]){
(void)argc;
(void)argv;
sscanf_bd_addr(remote_addr_string, remote_addr);
// register for HCI events
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
// init L2CAP
l2cap_init();
// init RFCOM
rfcomm_init();
// init GOEP Client
goep_client_init();
// init PBAP Client
pbap_client_init();
#ifdef HAVE_POSIX_STDIN
btstack_stdin_setup(stdin_process);
#endif
// turn on!
hci_power_control(HCI_POWER_ON);
return 0;
}

View File

@ -99,7 +99,7 @@ static void sm_peripheral_setup(void){
*/
// LE Legacy Pairing, Just Works
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_YES_NO);
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
sm_set_authentication_requirements(0);
// LE Legacy Pairing, Passkey entry initiator enter, responder (us) displays

View File

@ -89,7 +89,7 @@ typedef uint8_t sm_key_t[16];
// Security Manager protocol data
#define SM_DATA_PACKET 0x09
// SDP query result
// SDP query result - only used by daemon
// format: type (8), record_id (16), attribute_id (16), attribute_length (16), attribute_value (max 1k)
#define SDP_CLIENT_PACKET 0x0a
@ -98,6 +98,12 @@ typedef uint8_t sm_key_t[16];
// Unicast Connectionless Data
#define UCD_DATA_PACKET 0x0c
// GOEP data
#define GOEP_DATA_PACKET 0x0d
// PBAP data
#define PBAP_DATA_PACKET 0x0e
// debug log messages
#define LOG_MESSAGE_PACKET 0xfc
@ -159,8 +165,6 @@ typedef uint8_t sm_key_t[16];
#define BNEP_CHANNEL_NOT_CONNECTED 0xA1
#define BNEP_DATA_LEN_EXCEEDS_MTU 0xA2
// DAEMON COMMANDS
#define OGF_BTSTACK 0x3d
@ -266,7 +270,11 @@ typedef uint8_t sm_key_t[16];
#define GATT_WRITE_CLIENT_CHARACTERISTIC_CONFIGURATION 0X81
#define GATT_GET_MTU 0x82
// OBEX ERRORS
#define OBEX_UNKNOWN_ERROR 0x90
#define OBEX_CONNECT_FAILED 0x91
#define OBEX_DISCONNECTED 0x92
#define OBEX_NOT_FOUND 0x93
// EVENTS
@ -922,6 +930,8 @@ typedef uint8_t sm_key_t[16];
#define HCI_EVENT_ANCS_META 0xEA
#define HCI_EVENT_AVDTP_META 0xEB
#define HCI_EVENT_AVRCP_META 0xEC
#define HCI_EVENT_GOEP_META 0xED
#define HCI_EVENT_PBAP_META 0xEE
// Potential other meta groups
// #define HCI_EVENT_BNEP_META 0xxx
@ -1470,6 +1480,55 @@ typedef uint8_t sm_key_t[16];
*/
#define AVRCP_SUBEVENT_PLAYER_APPLICATION_VALUE_RESPONSE 0x0F
/**
* @format 121BH1
* @param subevent_code
* @param goep_cid
* @param status
* @param bd_addr
* @param con_handle
* @param incoming
*/
#define GOEP_SUBEVENT_CONNECTION_OPENED 0x01
/**
* @format 12
* @param subevent_code
* @param goep_cid
*/
#define GOEP_SUBEVENT_CONNECTION_CLOSED 0x02
/**
* @format 12
* @param subevent_code
* @param goep_cid
*/
#define GOEP_SUBEVENT_CAN_SEND_NOW 0x03
/**
* @format 121BH1
* @param subevent_code
* @param pbap_cid
* @param status
* @param bd_addr
* @param con_handle
* @param incoming
*/
#define PBAP_SUBEVENT_CONNECTION_OPENED 0x01
/**
* @format 12
* @param subevent_code
* @param goep_cid
*/
#define PBAP_SUBEVENT_CONNECTION_CLOSED 0x02
/**
* @format 121
* @param subevent_code
* @param goep_cid
* @param status
*/
#define PBAP_SUBEVENT_OPERATION_COMPLETED 0x03
#endif

View File

@ -70,11 +70,35 @@ static inline uint8_t hci_event_packet_get_type(const uint8_t * event){
}
/***
* @brief Get subevent code for hsp event
* @brief Get subevent code for ancs event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_hsp_meta_get_subevent_code(const uint8_t * event){
static inline uint8_t hci_event_ancs_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
* @brief Get subevent code for avdtp event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_avdtp_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
* @brief Get subevent code for avrcp event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_avrcp_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
* @brief Get subevent code for goep event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_goep_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
@ -86,11 +110,19 @@ static inline uint8_t hci_event_hfp_meta_get_subevent_code(const uint8_t * event
return event[2];
}
/***
* @brief Get subevent code for ancs event
* @brief Get subevent code for hsp event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_ancs_meta_get_subevent_code(const uint8_t * event){
static inline uint8_t hci_event_hsp_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
* @brief Get subevent code for pbap event
* @param event packet
* @return subevent_code
*/
static inline uint8_t hci_event_pbap_meta_get_subevent_code(const uint8_t * event){
return event[2];
}
/***
@ -4754,6 +4786,147 @@ static inline uint8_t avrcp_subevent_player_application_value_response_get_statu
return event[5];
}
/**
* @brief Get field goep_cid from event GOEP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return goep_cid
* @note: btstack_type 2
*/
static inline uint16_t goep_subevent_connection_opened_get_goep_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field status from event GOEP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return status
* @note: btstack_type 1
*/
static inline uint8_t goep_subevent_connection_opened_get_status(const uint8_t * event){
return event[5];
}
/**
* @brief Get field bd_addr from event GOEP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @param Pointer to storage for bd_addr
* @note: btstack_type B
*/
static inline void goep_subevent_connection_opened_get_bd_addr(const uint8_t * event, bd_addr_t bd_addr){
reverse_bd_addr(&event[6], bd_addr);
}
/**
* @brief Get field con_handle from event GOEP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return con_handle
* @note: btstack_type H
*/
static inline hci_con_handle_t goep_subevent_connection_opened_get_con_handle(const uint8_t * event){
return little_endian_read_16(event, 12);
}
/**
* @brief Get field incoming from event GOEP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return incoming
* @note: btstack_type 1
*/
static inline uint8_t goep_subevent_connection_opened_get_incoming(const uint8_t * event){
return event[14];
}
/**
* @brief Get field goep_cid from event GOEP_SUBEVENT_CONNECTION_CLOSED
* @param event packet
* @return goep_cid
* @note: btstack_type 2
*/
static inline uint16_t goep_subevent_connection_closed_get_goep_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field goep_cid from event GOEP_SUBEVENT_CAN_SEND_NOW
* @param event packet
* @return goep_cid
* @note: btstack_type 2
*/
static inline uint16_t goep_subevent_can_send_now_get_goep_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field pbap_cid from event PBAP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return pbap_cid
* @note: btstack_type 2
*/
static inline uint16_t pbap_subevent_connection_opened_get_pbap_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field status from event PBAP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return status
* @note: btstack_type 1
*/
static inline uint8_t pbap_subevent_connection_opened_get_status(const uint8_t * event){
return event[5];
}
/**
* @brief Get field bd_addr from event PBAP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @param Pointer to storage for bd_addr
* @note: btstack_type B
*/
static inline void pbap_subevent_connection_opened_get_bd_addr(const uint8_t * event, bd_addr_t bd_addr){
reverse_bd_addr(&event[6], bd_addr);
}
/**
* @brief Get field con_handle from event PBAP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return con_handle
* @note: btstack_type H
*/
static inline hci_con_handle_t pbap_subevent_connection_opened_get_con_handle(const uint8_t * event){
return little_endian_read_16(event, 12);
}
/**
* @brief Get field incoming from event PBAP_SUBEVENT_CONNECTION_OPENED
* @param event packet
* @return incoming
* @note: btstack_type 1
*/
static inline uint8_t pbap_subevent_connection_opened_get_incoming(const uint8_t * event){
return event[14];
}
/**
* @brief Get field goep_cid from event PBAP_SUBEVENT_CONNECTION_CLOSED
* @param event packet
* @return goep_cid
* @note: btstack_type 2
*/
static inline uint16_t pbap_subevent_connection_closed_get_goep_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field goep_cid from event PBAP_SUBEVENT_OPERATION_COMPLETED
* @param event packet
* @return goep_cid
* @note: btstack_type 2
*/
static inline uint16_t pbap_subevent_operation_completed_get_goep_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field status from event PBAP_SUBEVENT_OPERATION_COMPLETED
* @param event packet
* @return status
* @note: btstack_type 1
*/
static inline uint8_t pbap_subevent_operation_completed_get_status(const uint8_t * event){
return event[5];
}
/* API_END */

328
src/classic/goep_client.c Normal file
View File

@ -0,0 +1,328 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#include "btstack_config.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "btstack_debug.h"
#include "hci_dump.h"
#include "bluetooth_sdp.h"
#include "btstack_event.h"
#include "classic/goep_client.h"
#include "classic/obex.h"
#include "classic/obex_iterator.h"
#include "classic/rfcomm.h"
#include "classic/sdp_client_rfcomm.h"
//------------------------------------------------------------------------------------------------------------
// goep_client.c
//
typedef enum {
GOEP_INIT,
GOEP_W4_SDP,
GOEP_W4_CONNECTION,
GOEP_CONNECTED,
} goep_state_t;
typedef struct {
uint16_t cid;
goep_state_t state;
bd_addr_t bd_addr;
hci_con_handle_t con_handle;
uint8_t incoming;
uint8_t bearer_l2cap;
uint16_t bearer_port; // l2cap: psm, rfcomm: channel nr
uint16_t bearer_cid;
uint16_t bearer_mtu;
uint8_t obex_opcode;
uint32_t obex_connection_id;
int obex_connection_id_set;
btstack_packet_handler_t client_handler;
} goep_client_t;
static goep_client_t _goep_client;
static goep_client_t * goep_client = &_goep_client;
static inline void goep_client_emit_connected_event(goep_client_t * context, uint8_t status){
uint8_t event[22];
int pos = 0;
event[pos++] = HCI_EVENT_GOEP_META;
pos++; // skip len
event[pos++] = GOEP_SUBEVENT_CONNECTION_OPENED;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[pos++] = status;
memcpy(&event[pos], context->bd_addr, 6);
pos += 6;
little_endian_store_16(event,pos,context->con_handle);
pos += 2;
event[pos++] = context->incoming;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static inline void goep_client_emit_connection_closed_event(goep_client_t * context){
uint8_t event[5];
int pos = 0;
event[pos++] = HCI_EVENT_GOEP_META;
pos++; // skip len
event[pos++] = GOEP_SUBEVENT_CONNECTION_CLOSED;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("goep_client_emit_connection_closed_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static inline void goep_client_emit_can_send_now_event(goep_client_t * context){
uint8_t event[5];
int pos = 0;
event[pos++] = HCI_EVENT_GOEP_META;
pos++; // skip len
event[pos++] = GOEP_SUBEVENT_CAN_SEND_NOW;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("goep_client_emit_can_send_now_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static void goep_client_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
uint8_t status;
switch (packet_type){
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case RFCOMM_EVENT_CHANNEL_OPENED:
status = rfcomm_event_channel_opened_get_status(packet);
if (status) {
log_info("goep_client: RFCOMM channel open failed, status %u", rfcomm_event_channel_opened_get_status(packet));
goep_client->state = GOEP_INIT;
} else {
goep_client->bearer_mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
log_info("goep_client: RFCOMM channel open succeeded. cid %u, max frame size %u", goep_client->bearer_cid, goep_client->bearer_mtu);
goep_client->state = GOEP_CONNECTED;
}
goep_client_emit_connected_event(goep_client, status);
return;
case RFCOMM_EVENT_CAN_SEND_NOW:
goep_client_emit_can_send_now_event(goep_client);
break;
case RFCOMM_CHANNEL_CLOSED:
goep_client->state = GOEP_INIT;
goep_client_emit_connection_closed_event(goep_client);
break;
default:
break;
}
break;
case RFCOMM_DATA_PACKET:
goep_client->client_handler(GOEP_DATA_PACKET, goep_client->cid, packet, size);
break;
default:
break;
}
}
static void goep_client_handle_query_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
switch (packet[0]){
case SDP_EVENT_QUERY_RFCOMM_SERVICE:
goep_client->bearer_port = sdp_event_query_rfcomm_service_get_rfcomm_channel(packet);
break;
case SDP_EVENT_QUERY_COMPLETE:
if (goep_client->bearer_port == 0){
log_info("Remote GOEP RFCOMM Server Channel not found");
goep_client->state = GOEP_INIT;
goep_client_emit_connected_event(goep_client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
break;
}
log_info("Remote GOEP RFCOMM Server Channel: %u", goep_client->bearer_port);
rfcomm_create_channel(&goep_client_packet_handler, goep_client->bd_addr, goep_client->bearer_port, &goep_client->bearer_cid);
break;
}
}
static void goep_client_packet_append(const uint8_t * data, uint16_t len){
uint8_t * buffer = rfcomm_get_outgoing_buffer();
uint16_t pos = big_endian_read_16(buffer, 1);
memcpy(&buffer[pos], data, len);
pos += len;
big_endian_store_16(buffer, 1, pos);
}
static void goep_client_packet_init(uint16_t goep_cid, uint8_t opcode){
UNUSED(goep_cid);
rfcomm_reserve_packet_buffer();
uint8_t * buffer = rfcomm_get_outgoing_buffer();
buffer[0] = opcode;
big_endian_store_16(buffer, 1, 3);
// store opcode for parsing of response
goep_client->obex_opcode = opcode;
}
static void goep_client_packet_add_connection_id(uint16_t goep_cid){
UNUSED(goep_cid);
// add connection_id header if set, must be first header if used
if (goep_client->obex_connection_id != OBEX_CONNECTION_ID_INVALID){
uint8_t header[5];
header[0] = OBEX_HEADER_CONNECTION_ID;
big_endian_store_32(header, 1, goep_client->obex_connection_id);
goep_client_packet_append(&header[0], sizeof(header));
}
}
void goep_client_init(void){
memset(goep_client, 0, sizeof(goep_client_t));
goep_client->state = GOEP_INIT;
goep_client->cid = 1;
goep_client->obex_connection_id = OBEX_CONNECTION_ID_INVALID;
}
uint8_t goep_client_create_connection(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t uuid, uint16_t * out_cid){
if (goep_client->state != GOEP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED;
goep_client->client_handler = handler;
goep_client->state = GOEP_W4_SDP;
memcpy(goep_client->bd_addr, addr, 6);
sdp_client_query_rfcomm_channel_and_name_for_uuid(&goep_client_handle_query_rfcomm_event, goep_client->bd_addr, uuid);
*out_cid = goep_client->cid;
return 0;
}
uint8_t goep_client_disconnect(uint16_t goep_cid){
UNUSED(goep_cid);
rfcomm_disconnect(goep_client->bearer_cid);
return 0;
}
void goep_client_set_connection_id(uint16_t goep_cid, uint32_t connection_id){
UNUSED(goep_cid);
goep_client->obex_connection_id = connection_id;
}
uint8_t goep_client_get_request_opcode(uint16_t goep_cid){
UNUSED(goep_cid);
return goep_client->obex_opcode;
}
void goep_client_request_can_send_now(uint16_t goep_cid){
UNUSED(goep_cid);
rfcomm_request_can_send_now_event(goep_client->bearer_cid);
}
void goep_client_create_connect_request(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length){
UNUSED(goep_cid);
goep_client_packet_init(goep_cid, OBEX_OPCODE_CONNECT);
uint8_t fields[4];
fields[0] = obex_version_number;
fields[1] = flags;
// workaround: limit OBEX packet len to RFCOMM MTU to avoid handling of fragemented packets
maximum_obex_packet_length = btstack_min(maximum_obex_packet_length, goep_client->bearer_mtu);
big_endian_store_16(fields, 2, maximum_obex_packet_length);
goep_client_packet_append(&fields[0], sizeof(fields));
}
void goep_client_create_get_request(uint16_t goep_cid){
UNUSED(goep_cid);
goep_client_packet_init(goep_cid, OBEX_OPCODE_GET | OBEX_OPCODE_FINAL_BIT_MASK);
goep_client_packet_add_connection_id(goep_cid);
}
void goep_client_create_set_path_request(uint16_t goep_cid, uint8_t flags){
UNUSED(goep_cid);
goep_client_packet_init(goep_cid, OBEX_OPCODE_SETPATH);
uint8_t fields[2];
fields[0] = flags;
fields[1] = 0; // reserved
goep_client_packet_append(&fields[0], sizeof(fields));
goep_client_packet_add_connection_id(goep_cid);
}
void goep_client_add_header_target(uint16_t goep_cid, uint16_t length, const uint8_t * target){
UNUSED(goep_cid);
uint8_t header[3];
header[0] = OBEX_HEADER_TARGET;
big_endian_store_16(header, 1, 1 + 2 + length);
goep_client_packet_append(&header[0], sizeof(header));
goep_client_packet_append(target, length);
}
void goep_client_add_header_name(uint16_t goep_cid, const char * name){
UNUSED(goep_cid);
int len_incl_zero = strlen(name) + 1;
uint8_t * buffer = rfcomm_get_outgoing_buffer();
uint16_t pos = big_endian_read_16(buffer, 1);
buffer[pos++] = OBEX_HEADER_NAME;
big_endian_store_16(buffer, pos, 1 + 2 + len_incl_zero*2);
pos += 2;
int i;
// @note name[len] == 0
for (i = 0 ; i < len_incl_zero ; i++){
buffer[pos++] = 0;
buffer[pos++] = *name++;
}
big_endian_store_16(buffer, 1, pos);
}
void goep_client_add_header_type(uint16_t goep_cid, const char * type){
UNUSED(goep_cid);
uint8_t header[3];
header[0] = OBEX_HEADER_TYPE;
int len_incl_zero = strlen(type) + 1;
big_endian_store_16(header, 1, 1 + 2 + len_incl_zero);
goep_client_packet_append(&header[0], sizeof(header));
goep_client_packet_append((const uint8_t*)type, len_incl_zero);
}
int goep_client_execute(uint16_t goep_cid){
UNUSED(goep_cid);
uint8_t * buffer = rfcomm_get_outgoing_buffer();
uint16_t pos = big_endian_read_16(buffer, 1);
return rfcomm_send_prepared(goep_client->bearer_cid, pos);
}

177
src/classic/goep_client.h Normal file
View File

@ -0,0 +1,177 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#ifndef __GOEP_CLIENT_H
#if defined __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "btstack_defines.h"
//------------------------------------------------------------------------------------------------------------
// goep_client.h
//
// Communicate with remote OBEX server - General Object Exchange
//
/* API_START */
/**
* Setup GOEP Client
*/
void goep_client_init(void);
/*
* @brief Create GOEP connection to a GEOP server with specified UUID on a remote deivce.
* @param handler
* @param addr
* @param uuid
* @param out_cid to use for further commands
* @result status
*/
uint8_t goep_client_create_connection(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t uuid, uint16_t * out_cid);
/**
* @brief Disconnects GOEP connection with given identifier.
* @param gope_cid
* @return status
*/
uint8_t goep_client_disconnect(uint16_t goep_cid);
/**
* @brief Request emission of GOEP_SUBEVENT_CAN_SEND_NOW as soon as possible
* @note GOEP_SUBEVENT_CAN_SEND_NOW might be emitted during call to this function
* so packet handler should be ready to handle it
* @param goep_cid
*/
void goep_client_request_can_send_now(uint16_t goep_cid);
/**
* @brief Get Opcode from last created request, needed for parsing of OBEX response packet
* @param gope_cid
* @return opcode
*/
uint8_t goep_client_get_request_opcode(uint16_t goep_cid);
/**
* @brief Set Connection ID used for newly created requests
* @param gope_cid
*/
void goep_client_set_connection_id(uint16_t goep_cid, uint32_t connection_id);
/**
* @brief Start Connect request
* @param gope_cid
* @param obex_version_number
* @param flags
* @param maximum_obex_packet_length
*/
void goep_client_create_connect_request(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length);
/**
* @brief Start Get request
* @param gope_cid
*/
void goep_client_create_get_request(uint16_t goep_cid);
/**
* @brief Start Set Path request
* @param gope_cid
*/
void goep_client_create_set_path_request(uint16_t goep_cid, uint8_t flags);
// not implemented yet
// void goep_client_create_put(uint16_t goep_cid);
/**
* @brief Add name header to current request
* @param goep_cid
* @param name
*/
void goep_client_add_header_name(uint16_t goep_cid, const char * name);
/**
* @brief Add target header to current request
* @param goep_cid
* @param target
*/
void goep_client_add_header_target(uint16_t goep_cid, uint16_t length, const uint8_t * target);
/**
* @brief Add type header to current request
* @param goep_cid
* @param type
*/
void goep_client_add_header_type(uint16_t goep_cid, const char * type);
/**
* @brief Add count header to current request
* @param goep_cid
* @param count
*/
void goep_client_add_header_count(uint16_t goep_cid, uint32_t count);
/**
* @brief Add application parameters header to current request
* @param goep_cid
* @param lenght of application parameters
* @param daa
*/
void goep_client_add_header_application_parameters(uint16_t goep_cid, uint16_t length, uint8_t * data);
// int goep_client_add_body_static(uint16_t goep_cid, uint32_t length, uint8_t * data);
// int goep_client_add_body_dynamic(uint16_t goep_cid, uint32_t length, void (*data_callback)(uint32_t offset, uint8_t * buffer, uint32_t len));
/**
* @brief Execute prepared request
* @param goep_cid
* @param daa
*/
int goep_client_execute(uint16_t goep_cid);
/* API_END */
#if defined __cplusplus
}
#endif
#endif

82
src/classic/obex.h Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#ifndef __OBEX_H
#define OBEX_OPCODE_CONNECT 0x80
#define OBEX_OPCODE_DISCONNECT 0x81
#define OBEX_OPCODE_PUT 0x02
#define OBEX_OPCODE_CLOSE 0x82
#define OBEX_OPCODE_GET 0x03
#define OBEX_OPCODE_SETPATH 0x85
#define OBEX_OPCODE_SESSION 0x87
#define OBEX_OPCODE_ABORT 0xFF
#define OBEX_RESP_SUCCESS 0xA0
#define OBEX_RESP_CONTINUE 0x90
#define OBEX_RESP_CANCELED 0xC1
#define OBEX_RESP_NOT_FOUND 0xC4
#define OBEX_RESP_REFUSED 0xC6
#define OBEX_HEADER_BODY 0x48
#define OBEX_HEADER_END_OF_BODY 0x49
#define OBEX_HEADER_COUNT 0xC0
#define OBEX_HEADER_NAME 0x01
#define OBEX_HEADER_TYPE 0x42
#define OBEX_HEADER_LENGTH 0xC3
#define OBEX_HEADER_TIME_ISO_8601 0x44
#define OBEX_HEADER_TIME_4_BYTE 0xC4
#define OBEX_HEADER_DESCRIPTION 0x05
#define OBEX_HEADER_TARGET 0x46
#define OBEX_HEADER_HTTP 0x47
#define OBEX_HEADER_WHO 0x4A
#define OBEX_HEADER_OBJECT_CLASS 0x4F
#define OBEX_HEADER_APPLICATION_PARAMETERS 0x4C
#define OBEX_HEADER_CONNECTION_ID 0xCb
#define OBEX_OPCODE_FINAL_BIT_MASK 0x80
#define OBEX_VERSION 0x14
#define OBEX_PACKET_HEADER_SIZE 3
#define OBEX_PACKET_OPCODE_OFFSET 0
#define OBEX_PACKET_LENGTH_OFFSET 1
#define OBEX_MAX_PACKETLEN_DEFAULT 0xffff
#define OBEX_CONNECTION_ID_INVALID 0xFFFFFFFF
#endif

181
src/classic/obex_iterator.c Normal file
View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#include "btstack_config.h"
#include <stdint.h>
#include <stdlib.h>
#include "hci_cmd.h"
#include "btstack_debug.h"
#include "hci.h"
#include "bluetooth_sdp.h"
#include "classic/sdp_client_rfcomm.h"
#include "btstack_event.h"
#include "classic/obex.h"
#include "classic/obex_iterator.h"
static int obex_packet_header_offset_for_opcode(uint8_t opcode){
switch (opcode){
case OBEX_OPCODE_SETPATH:
return 5;
case OBEX_OPCODE_CONNECT:
return 7;
default:
return 3;
}
}
static void obex_iterator_init(obex_iterator_t *context, int header_offset, const uint8_t * packet_data, uint16_t packet_len){
memset(context, 0, sizeof(obex_iterator_t));
context->data = packet_data + header_offset;
context->length = packet_len - header_offset;
}
void obex_iterator_init_with_request_packet(obex_iterator_t *context, const uint8_t * packet_data, uint16_t packet_len){
int header_offset = obex_packet_header_offset_for_opcode(packet_data[0]);
obex_iterator_init(context, header_offset, packet_data, packet_len);
}
void obex_iterator_init_with_response_packet(obex_iterator_t *context, uint8_t request_opcode, const uint8_t * packet_data, uint16_t packet_len){
int header_offset = request_opcode == OBEX_OPCODE_CONNECT ? 7 : 3;
obex_iterator_init(context, header_offset, packet_data, packet_len);
}
int obex_iterator_has_more(const obex_iterator_t * context){
return context->offset < context->length;
}
void obex_iterator_next(obex_iterator_t * context){
int len = 0;
const uint8_t * data = context->data + context->offset;
int encoding = data[0] >> 6;
switch (encoding){
case 0:
case 1:
// 16-bit length info prefixed
len = 2 + big_endian_read_16(data, 1);
break;
case 2:
// 8-bit value
len = 1;
break;
case 3:
// 32-bit value
len = 4;
break;
// avoid compiler warning about unused cases (by unclever compilers)
default:
break;
}
context->offset += 1 + len;
}
// OBEX packet header access functions
// @note BODY/END-OF-BODY headers might be incomplete
uint8_t obex_iterator_get_hi(const obex_iterator_t * context){
return context->data[context->offset];
}
uint8_t obex_iterator_get_data_8(const obex_iterator_t * context){
return context->data[context->offset+1];
}
uint32_t obex_iterator_get_data_32(const obex_iterator_t * context){
return big_endian_read_32(context->data, context->offset + 1);
}
uint32_t obex_iterator_get_data_len(const obex_iterator_t * context){
const uint8_t * data = context->data + context->offset;
int encoding = data[0] >> 6;
switch (encoding){
case 0:
case 1:
// 16-bit length info prefixed
return big_endian_read_16(data, 1) - 3;
case 2:
// 8-bit value
return 1;
case 3:
// 32-bit value
return 4;
// avoid compiler warning about unused cases (by unclever compilers)
default:
return 0;
}
}
const uint8_t * obex_iterator_get_data(const obex_iterator_t * context){
const uint8_t * data = context->data + context->offset;
int encoding = data[0] >> 6;
switch (encoding){
case 0:
case 1:
// 16-bit length info prefixed
return &data[3];
default:
// 8-bit value
// 32-bit value
return &data[1];
}
}
void obex_dump_packet(uint8_t request_opcode, uint8_t * packet, uint16_t size){
// printf("RCV: '");
// printf_hexdump(packet, size);
obex_iterator_t it;
printf("Opcode: 0x%02x\n", packet[0]);
for (obex_iterator_init_with_response_packet(&it, request_opcode, packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){
uint8_t hi = obex_iterator_get_hi(&it);
printf("HI: %x - ", hi);
uint8_t encoding = hi >> 6;
uint16_t len;
switch (encoding){
case 0:
case 1:
len = obex_iterator_get_data_len(&it);
printf_hexdump(obex_iterator_get_data(&it), len);
break;
case 2:
printf("%02x\n", obex_iterator_get_data_8(&it));
break;
case 3:
printf("%08x\n", (int) obex_iterator_get_data_32(&it));
break;
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#ifndef __OBEX_ITERATOR_H
#if defined __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* API_START */
typedef struct obex_iterator {
const uint8_t * data;
uint8_t offset;
uint8_t length;
} obex_iterator_t;
// OBEX packet header iterator
void obex_iterator_init_with_request_packet(obex_iterator_t *context, const uint8_t * packet_data, uint16_t packet_len);
void obex_iterator_init_with_response_packet(obex_iterator_t *context, uint8_t request_opcode, const uint8_t * packet_data, uint16_t packet_len);
int obex_iterator_has_more(const obex_iterator_t * context);
void obex_iterator_next(obex_iterator_t * context);
// OBEX packet header access functions
// @note BODY/END-OF-BODY headers might be incomplete
uint8_t obex_iterator_get_hi(const obex_iterator_t * context);
uint8_t obex_iterator_get_data_8(const obex_iterator_t * context);
uint32_t obex_iterator_get_data_32(const obex_iterator_t * context);
uint32_t obex_iterator_get_data_len(const obex_iterator_t * context);
const uint8_t * obex_iterator_get_data(const obex_iterator_t * context);
/* API_END */
// debug
void obex_dump_packet(uint8_t request_opcode, uint8_t * packet, uint16_t size);
#if defined __cplusplus
}
#endif
#endif

371
src/classic/pbap_client.c Normal file
View File

@ -0,0 +1,371 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
// *****************************************************************************
//
#if 0
0x0000 = uint32(65542),
// BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE
0x0001 = { uuid16(11 2f) },
// BLUETOOTH_PROTOCOL_L2CAP, BLUETOOTH_PROTOCOL_RFCOMM, BLUETOOTH_PROTOCOL_OBEX
0x0004 = { { uuid16(01 00) }, { uuid16(00 03), uint8(19) }, { uuid16(00 08) } }
0x0005 = { uuid16(10 02) },
// BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS, v1.01 = 0x101
0x0009 = { { uuid16(11 30), uint16(257) } },
0x0100 = string(OBEX Phonebook Access Server
// BLUETOOTH_ATTRIBUTE_SUPPORTED_FEATURES -- should be 0x317 BLUETOOTH_ATTRIBUTE_PBAP_SUPPORTED_FEATURES?
0x0311 = uint8(3),
// BLUETOOTH_ATTRIBUTE_SUPPORTED_REPOSITORIES
0x0314 = uint8(1),
#endif
//
// *****************************************************************************
#include "btstack_config.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hci_cmd.h"
#include "btstack_run_loop.h"
#include "btstack_debug.h"
#include "hci.h"
#include "btstack_memory.h"
#include "hci_dump.h"
#include "l2cap.h"
#include "bluetooth_sdp.h"
#include "classic/sdp_client_rfcomm.h"
#include "btstack_event.h"
#include "classic/obex.h"
#include "classic/obex_iterator.h"
#include "classic/goep_client.h"
#include "classic/pbap_client.h"
// 796135f0-f0c5-11d8-0966- 0800200c9a66
uint8_t pbap_uuid[] = { 0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66};
const char * pbap_type = "x-bt/phonebook";
const char * pbap_name = "pb.vcf";
typedef enum {
PBAP_INIT = 0,
PBAP_W4_GOEP_CONNECTION,
PBAP_W2_SEND_CONNECT_REQUEST,
PBAP_W4_CONNECT_RESPONSE,
PBAP_CONNECT_RESPONSE_RECEIVED,
PBAP_CONNECTED,
//
PBAP_W2_PULL_PHONE_BOOK,
PBAP_W4_PHONE_BOOK,
PBAP_W2_SET_PATH_ROOT,
PBAP_W4_SET_PATH_ROOT_COMPLETE,
PBAP_W2_SET_PATH_ELEMENT,
PBAP_W4_SET_PATH_ELEMENT_COMPLETE,
} pbap_state_t;
typedef struct pbap_client {
pbap_state_t state;
uint16_t cid;
bd_addr_t bd_addr;
hci_con_handle_t con_handle;
uint8_t incoming;
uint16_t goep_cid;
btstack_packet_handler_t client_handler;
const char * current_folder;
uint16_t set_path_offset;
} pbap_client_t;
static pbap_client_t _pbap_client;
static pbap_client_t * pbap_client = &_pbap_client;
static inline void pbap_client_emit_connected_event(pbap_client_t * context, uint8_t status){
uint8_t event[15];
int pos = 0;
event[pos++] = HCI_EVENT_PBAP_META;
pos++; // skip len
event[pos++] = PBAP_SUBEVENT_CONNECTION_OPENED;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[pos++] = status;
memcpy(&event[pos], context->bd_addr, 6);
pos += 6;
little_endian_store_16(event,pos,context->con_handle);
pos += 2;
event[pos++] = context->incoming;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("goep_client_emit_connected_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static inline void pbap_client_emit_connection_closed_event(pbap_client_t * context){
uint8_t event[5];
int pos = 0;
event[pos++] = HCI_EVENT_PBAP_META;
pos++; // skip len
event[pos++] = PBAP_SUBEVENT_CONNECTION_CLOSED;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("pbap_client_emit_connection_closed_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static inline void pbap_client_emit_operation_complete_event(pbap_client_t * context, uint8_t status){
uint8_t event[6];
int pos = 0;
event[pos++] = HCI_EVENT_PBAP_META;
pos++; // skip len
event[pos++] = PBAP_SUBEVENT_OPERATION_COMPLETED;
little_endian_store_16(event,pos,context->cid);
pos+=2;
event[pos++]= status;
event[1] = pos - 2;
if (pos != sizeof(event)) log_error("pbap_client_emit_can_send_now_event size %u", pos);
context->client_handler(HCI_EVENT_PACKET, context->cid, &event[0], pos);
}
static void pbap_handle_can_send_now(void){
uint8_t path_element[20];
uint16_t path_element_start;
uint16_t path_element_len;
switch (pbap_client->state){
case PBAP_W2_SEND_CONNECT_REQUEST:
goep_client_create_connect_request(pbap_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT);
goep_client_add_header_target(pbap_client->goep_cid, 16, pbap_uuid);
// state
pbap_client->state = PBAP_W4_CONNECT_RESPONSE;
// send packet
goep_client_execute(pbap_client->goep_cid);
return;
case PBAP_W2_PULL_PHONE_BOOK:
goep_client_create_get_request(pbap_client->goep_cid);
goep_client_add_header_type(pbap_client->goep_cid, pbap_type);
goep_client_add_header_name(pbap_client->goep_cid, pbap_name);
// state
pbap_client->state = PBAP_W4_PHONE_BOOK;
// send packet
goep_client_execute(pbap_client->goep_cid);
break;
case PBAP_W2_SET_PATH_ROOT:
goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Dont create directory
// On Android 4.2 Cyanogenmod, using "" as path fails
// goep_client_add_header_name(pbap_client->goep_cid, ""); // empty == /
// state
pbap_client->state = PBAP_W4_SET_PATH_ROOT_COMPLETE;
// send packet
goep_client_execute(pbap_client->goep_cid);
break;
case PBAP_W2_SET_PATH_ELEMENT:
// find '/' or '\0'
path_element_start = pbap_client->set_path_offset;
while (pbap_client->current_folder[pbap_client->set_path_offset] != '\0' &&
pbap_client->current_folder[pbap_client->set_path_offset] != '/'){
pbap_client->set_path_offset++;
}
// skip /
if (pbap_client->current_folder[pbap_client->set_path_offset] == '/'){
pbap_client->set_path_offset++;
}
path_element_len = pbap_client->set_path_offset-path_element_start;
memcpy(path_element, &pbap_client->current_folder[path_element_start], path_element_len);
path_element[path_element_len] = 0;
goep_client_create_set_path_request(pbap_client->goep_cid, 1 << 1); // Dont create directory
goep_client_add_header_name(pbap_client->goep_cid, (const char *) path_element); // next element
// state
pbap_client->state = PBAP_W4_SET_PATH_ELEMENT_COMPLETE;
// send packet
goep_client_execute(pbap_client->goep_cid);
break;
default:
break;
}
}
static void pbap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(channel);
UNUSED(size);
obex_iterator_t it;
uint8_t status;
switch (packet_type){
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
case HCI_EVENT_GOEP_META:
switch (hci_event_goep_meta_get_subevent_code(packet)){
case GOEP_SUBEVENT_CONNECTION_OPENED:
status = goep_subevent_connection_opened_get_status(packet);
pbap_client->con_handle = goep_subevent_connection_opened_get_con_handle(packet);
pbap_client->incoming = goep_subevent_connection_opened_get_incoming(packet);
goep_subevent_connection_opened_get_bd_addr(packet, pbap_client->bd_addr);
if (status){
log_info("pbap: connection failed %u", status);
pbap_client->state = PBAP_INIT;
pbap_client_emit_connected_event(pbap_client, status);
} else {
log_info("pbap: connection established");
pbap_client->goep_cid = goep_subevent_connection_opened_get_goep_cid(packet);
pbap_client->state = PBAP_W2_SEND_CONNECT_REQUEST;
goep_client_request_can_send_now(pbap_client->goep_cid);
}
break;
case GOEP_SUBEVENT_CONNECTION_CLOSED:
if (pbap_client->state != PBAP_CONNECTED){
pbap_client_emit_operation_complete_event(pbap_client, OBEX_DISCONNECTED);
}
pbap_client->state = PBAP_INIT;
pbap_client_emit_connection_closed_event(pbap_client);
break;
case GOEP_SUBEVENT_CAN_SEND_NOW:
pbap_handle_can_send_now();
break;
}
break;
default:
break;
}
break;
case GOEP_DATA_PACKET:
// TODO: handle chunked data
#if 0
obex_dump_packet(goep_client_get_request_opcode(pbap_client->goep_cid), packet, size);
#endif
switch (pbap_client->state){
case PBAP_W4_CONNECT_RESPONSE:
for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){
uint8_t hi = obex_iterator_get_hi(&it);
if (hi == OBEX_HEADER_CONNECTION_ID){
goep_client_set_connection_id(pbap_client->goep_cid, obex_iterator_get_data_32(&it));
}
}
if (packet[0] == OBEX_RESP_SUCCESS){
pbap_client->state = PBAP_CONNECTED;
pbap_client_emit_connected_event(pbap_client, 0);
} else {
log_info("pbap: obex connect failed, result 0x%02x", packet[0]);
pbap_client->state = PBAP_INIT;
pbap_client_emit_connected_event(pbap_client, OBEX_CONNECT_FAILED);
}
break;
case PBAP_W4_SET_PATH_ROOT_COMPLETE:
case PBAP_W4_SET_PATH_ELEMENT_COMPLETE:
if (packet[0] == OBEX_RESP_SUCCESS){
if (pbap_client->current_folder){
pbap_client->state = PBAP_W2_SET_PATH_ELEMENT;
goep_client_request_can_send_now(pbap_client->goep_cid);
} else {
pbap_client_emit_operation_complete_event(pbap_client, 0);
}
} else if (packet[0] == OBEX_RESP_NOT_FOUND){
pbap_client->state = PBAP_CONNECTED;
pbap_client_emit_operation_complete_event(pbap_client, OBEX_NOT_FOUND);
} else {
pbap_client->state = PBAP_CONNECTED;
pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR);
}
break;
case PBAP_W4_PHONE_BOOK:
for (obex_iterator_init_with_response_packet(&it, goep_client_get_request_opcode(pbap_client->goep_cid), packet, size); obex_iterator_has_more(&it) ; obex_iterator_next(&it)){
uint8_t hi = obex_iterator_get_hi(&it);
if (hi == OBEX_HEADER_BODY || hi == OBEX_HEADER_END_OF_BODY){
uint16_t data_len = obex_iterator_get_data_len(&it);
const uint8_t * data = obex_iterator_get_data(&it);
pbap_client->client_handler(PBAP_DATA_PACKET, pbap_client->cid, (uint8_t *) data, data_len);
}
}
if (packet[0] == OBEX_RESP_CONTINUE){
pbap_client->state = PBAP_W2_PULL_PHONE_BOOK;
goep_client_request_can_send_now(pbap_client->goep_cid);
} else if (packet[0] == OBEX_RESP_SUCCESS){
pbap_client->state = PBAP_CONNECTED;
pbap_client_emit_operation_complete_event(pbap_client, 0);
} else {
pbap_client->state = PBAP_CONNECTED;
pbap_client_emit_operation_complete_event(pbap_client, OBEX_UNKNOWN_ERROR);
}
break;
default:
break;
}
break;
default:
break;
}
}
void pbap_client_init(void){
memset(pbap_client, 0, sizeof(pbap_client_t));
pbap_client->state = PBAP_INIT;
pbap_client->cid = 1;
}
uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid){
if (pbap_client->state != PBAP_INIT) return BTSTACK_MEMORY_ALLOC_FAILED;
pbap_client->state = PBAP_W4_GOEP_CONNECTION;
pbap_client->client_handler = handler;
uint8_t err = goep_client_create_connection(&pbap_packet_handler, addr, BLUETOOTH_SERVICE_CLASS_PHONEBOOK_ACCESS_PSE, &pbap_client->goep_cid);
*out_cid = pbap_client->cid;
if (err) return err;
return 0;
}
uint8_t pbap_disconnect(uint16_t pbap_cid){
UNUSED(pbap_cid);
if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
goep_client_disconnect(pbap_client->goep_cid);
return 0;
}
uint8_t pbap_pull_phonebook(uint16_t pbap_cid){
UNUSED(pbap_cid);
if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
pbap_client->state = PBAP_W2_PULL_PHONE_BOOK;
goep_client_request_can_send_now(pbap_client->goep_cid);
return 0;
}
uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path){
UNUSED(pbap_cid);
if (pbap_client->state != PBAP_CONNECTED) return BTSTACK_BUSY;
pbap_client->state = PBAP_W2_SET_PATH_ROOT;
pbap_client->current_folder = path;
pbap_client->set_path_offset = 0;
goep_client_request_can_send_now(pbap_client->goep_cid);
return 0;
}

90
src/classic/pbap_client.h Normal file
View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#ifndef __GOEP_CLIENT_H
#if defined __cplusplus
extern "C" {
#endif
#include "btstack_config.h"
#include <stdint.h>
/* API_START */
/**
* Setup PhoneBook Access Client
*/
void pbap_client_init(void);
/**
* @brief Create PBAP connection to a Phone Book Server (PSE) server on a remote deivce.
* @param handler
* @param addr
* @param out_cid to use for further commands
* @result status
*/
uint8_t pbap_connect(btstack_packet_handler_t handler, bd_addr_t addr, uint16_t * out_cid);
/**
* @brief Disconnects PBAP connection with given identifier.
* @param pbap_cid
* @return status
*/
uint8_t pbap_disconnect(uint16_t pbap_cid);
/**
* @brief Set current folder on PSE
* @param pbap_cid
* @param path - note: path is not copied
* @return status
*/
uint8_t pbap_set_phonebook(uint16_t pbap_cid, const char * path);
/**
* @brief Pull phone book from PSE
* @param pbap_cid
* @return status
*/
uint8_t pbap_pull_phonebook(uint16_t pbap_cid);
/* API_END */
#if defined __cplusplus
}
#endif
#endif

View File

@ -8,6 +8,30 @@ import os
import btstack_parser as parser
meta_events = [
'ANCS',
'AVDTP',
'AVRCP',
'GOEP',
'HFP',
'HSP',
'PBAP',
'LE'
]
supported_event_groups = meta_events + [
'BTSTACK',
'GAP',
'HCI',
'SDP',
'SM',
'L2CAP',
'RFCOMM',
'GATT',
'BNEP',
'ATT',
]
program_info = '''
BTstack Event Getter Generator for BTstack
Copyright 2016, BlueKitchen GmbH
@ -232,14 +256,13 @@ def create_events(events):
fout.write(copyright)
fout.write(hfile_header_begin)
meta_events = ['HSP', 'HFP', 'ANCS', 'LE'];
for meta_event in meta_events:
fout.write(meta_event_template.format(meta_event=meta_event.lower()))
for event_type, event_name, format, args in events:
parts = event_name.split("_")
event_group = parts[0]
if not event_group in [ 'BTSTACK', 'GAP', 'HCI', 'HSP', 'HFP', 'SDP', 'ANCS', 'SM', 'L2CAP', 'RFCOMM', 'GATT', 'BNEP', 'ATT', 'AVDTP', 'AVRCP']:
if not event_group in supported_event_groups:
print("// %s " % event_name)
continue
# print(event_name)