diff --git a/src/btstack.h b/src/btstack.h index 19c070a7b..101bc3d8c 100644 --- a/src/btstack.h +++ b/src/btstack.h @@ -132,6 +132,7 @@ #include "classic/device_id_server.h" #include "classic/gatt_sdp.h" #include "classic/goep_client.h" +#include "classic/goep_server.h" #include "classic/hfp.h" #include "classic/hfp_ag.h" #include "classic/hfp_hf.h" diff --git a/src/classic/goep_server.c b/src/classic/goep_server.c new file mode 100644 index 000000000..7df37c96d --- /dev/null +++ b/src/classic/goep_server.c @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2019 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 + * + */ + +#define BTSTACK_FILE__ "goep_server.c" + +#include "btstack_config.h" + +#include +#include + +#include "hci_cmd.h" +#include "btstack_debug.h" +#include "btstack_defines.h" +#include "hci.h" +#include "btstack_memory.h" +#include "hci_dump.h" +#include "btstack_event.h" + +#include "classic/goep_server.h" + +#ifdef ENABLE_GOEP_L2CAP +#include "l2cap.h" +#include "obex_message_builder.h" + +static l2cap_ertm_config_t ertm_config = { + 1, // ertm mandatory + 2, // max transmit, some tests require > 1 + 2000, + 12000, + (GOEP_SERVER_ERTM_BUFFER / 2), // l2cap ertm mtu + 2, + 2, + 1, // 16-bit FCS +}; + +static uint8_t goep_server_l2cap_packet_buffer[1000]; + +#endif + +static btstack_linked_list_t goep_server_connections = NULL; +static btstack_linked_list_t goep_server_services = NULL; +static uint16_t goep_server_cid_counter = 0; + +static goep_server_service_t * goep_server_get_service_for_rfcomm_channel(uint8_t rfcomm_channel){ + btstack_linked_item_t *it; + for (it = (btstack_linked_item_t *) goep_server_services; it ; it = it->next){ + goep_server_service_t * service = ((goep_server_service_t *) it); + if (service->rfcomm_channel == rfcomm_channel){ + return service; + }; + } + return NULL; +} + +static goep_server_service_t * goep_server_get_service_for_l2cap_psm(uint16_t l2cap_psm){ + btstack_linked_item_t *it; + for (it = (btstack_linked_item_t *) goep_server_services; it ; it = it->next){ + goep_server_service_t * service = ((goep_server_service_t *) it); + if (service->l2cap_psm == l2cap_psm){ + return service; + }; + } + return NULL; +} + +static goep_server_connection_t * goep_server_get_connection_for_rfcomm_cid(uint16_t bearer_cid){ + btstack_linked_item_t *it; + for (it = (btstack_linked_item_t *) goep_server_connections; it ; it = it->next){ + goep_server_connection_t * connection = ((goep_server_connection_t *) it); + if (connection->type != GOEP_CONNECTION_RFCOMM) continue; + if (connection->bearer_cid == bearer_cid){ + return connection; + }; + } + return NULL; +} + +static goep_server_connection_t * goep_server_get_connection_for_l2cap_cid(uint16_t bearer_cid){ + btstack_linked_item_t *it; + for (it = (btstack_linked_item_t *) goep_server_connections; it ; it = it->next){ + goep_server_connection_t * connection = ((goep_server_connection_t *) it); + if (connection->type != GOEP_CONNECTION_L2CAP) continue; + if (connection->bearer_cid == bearer_cid){ + return connection; + }; + } + return NULL; +} + +static goep_server_connection_t * goep_server_get_connection_for_goep_cid(uint16_t goep_cid){ + btstack_linked_item_t *it; + for (it = (btstack_linked_item_t *) goep_server_connections; it ; it = it->next){ + goep_server_connection_t * connection = ((goep_server_connection_t *) it); + if (connection->goep_cid == goep_cid){ + return connection; + }; + } + return NULL; +} + +static uint16_t goep_server_get_next_goep_cid(void){ + goep_server_cid_counter++; + if (goep_server_cid_counter == 0){ + goep_server_cid_counter = 1; + } + return goep_server_cid_counter; +} + +static inline void goep_server_emit_connection_opened(btstack_packet_handler_t callback, uint16_t goep_cid, bd_addr_t bd_addr, hci_con_handle_t con_handle, uint8_t status){ + uint8_t event[15]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + event[pos++] = 15 - 2; + event[pos++] = GOEP_SUBEVENT_CONNECTION_OPENED; + little_endian_store_16(event, pos, goep_cid); + pos+=2; + event[pos++] = status; + memcpy(&event[pos], bd_addr, 6); + pos += 6; + little_endian_store_16(event, pos, con_handle); + pos += 2; + event[pos++] = 1; + btstack_assert(pos == sizeof(event)); + callback(HCI_EVENT_PACKET, goep_cid, &event[0], pos); +} + +static inline void goep_server_emit_connection_closed(btstack_packet_handler_t callback, uint16_t goep_cid){ + uint8_t event[5]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + event[pos++] = 5 - 3; + event[pos++] = GOEP_SUBEVENT_CONNECTION_CLOSED; + little_endian_store_16(event, pos, goep_cid); + pos += 2; + btstack_assert(pos == sizeof(event)); + callback(HCI_EVENT_PACKET, goep_cid, &event[0], pos); +} + +static inline void goep_server_emit_can_send_now_event(goep_server_connection_t * connection){ + uint8_t event[5]; + uint16_t pos = 0; + event[pos++] = HCI_EVENT_GOEP_META; + event[pos++] = 5 - 3; + event[pos++] = GOEP_SUBEVENT_CAN_SEND_NOW; + little_endian_store_16(event,pos,connection->goep_cid); + pos += 2; + btstack_assert(pos == sizeof(event)); + connection->callback(HCI_EVENT_PACKET, connection->goep_cid, &event[0], pos); +} + +static void goep_server_handle_connection_opened(goep_server_connection_t * context, bd_addr_t addr, hci_con_handle_t con_handle, uint8_t status, uint16_t bearer_cid, uint16_t bearer_mtu){ + + uint16_t goep_cid = context->goep_cid; + btstack_packet_handler_t packet_handler = context->callback; + + if (status) { + log_info("goep_client: open failed, status %u", status); + btstack_linked_list_remove(&goep_server_connections, (btstack_linked_item_t *) context); + btstack_memory_goep_server_connection_free(context); + } else { + // context->bearer_mtu = mtu; + context->state = GOEP_SERVER_CONNECTED; + context->bearer_cid = bearer_cid; +#ifdef ENABLE_GOEP_L2CAP + if (context->type == GOEP_CONNECTION_L2CAP){ + bearer_mtu = btstack_min(bearer_mtu, sizeof(goep_server_l2cap_packet_buffer)); + } +#endif + context->bearer_mtu = bearer_mtu; + log_info("goep_server: connection opened. cid %u, max frame size %u", context->bearer_cid, bearer_mtu); + } + + goep_server_emit_connection_opened(packet_handler, goep_cid, addr, con_handle, status); +} + +static void goep_server_handle_connection_closed(goep_server_connection_t * goep_connection){ + uint16_t goep_cid = goep_connection->goep_cid; + btstack_packet_handler_t packet_handler = goep_connection->callback; + + btstack_linked_list_remove(&goep_server_connections, (btstack_linked_item_t *) goep_connection); + btstack_memory_goep_server_connection_free(goep_connection); + + goep_server_emit_connection_closed(packet_handler, goep_cid); +} + +#ifdef ENABLE_GOEP_L2CAP +static void goep_server_packet_handler_l2cap(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + bd_addr_t event_addr; + uint16_t l2cap_psm; + uint16_t l2cap_cid; + goep_server_connection_t * goep_connection; + goep_server_service_t * goep_service; + + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case L2CAP_EVENT_INCOMING_CONNECTION: + l2cap_psm = l2cap_event_incoming_connection_get_psm(packet); + l2cap_cid = l2cap_event_incoming_connection_get_local_cid(packet); + goep_service = goep_server_get_service_for_l2cap_psm(l2cap_psm); + if (!goep_service){ + l2cap_decline_connection(l2cap_cid); + break; + } + + // alloc structure + goep_connection = btstack_memory_goep_server_connection_get(); + if (!goep_connection){ + l2cap_decline_connection(l2cap_cid); + break; + } + + // setup connection + goep_connection->goep_cid = goep_server_get_next_goep_cid(); + goep_connection->bearer_cid = l2cap_cid; + goep_connection->callback = goep_service->callback; + goep_connection->type = GOEP_CONNECTION_L2CAP; + goep_connection->state = GOEP_SERVER_W4_CONNECTED; + btstack_linked_list_add(&goep_server_connections, (btstack_linked_item_t *) goep_connection); + l2cap_ertm_accept_connection(l2cap_cid, &ertm_config, goep_connection->ertm_buffer, GOEP_SERVER_ERTM_BUFFER); + break; + + case L2CAP_EVENT_CHANNEL_OPENED: + l2cap_cid = l2cap_event_channel_opened_get_local_cid(packet); + goep_connection = goep_server_get_connection_for_l2cap_cid(l2cap_cid); + btstack_assert(goep_connection != NULL); + btstack_assert(goep_connection->state == GOEP_SERVER_W4_CONNECTED); + l2cap_event_channel_opened_get_address(packet, event_addr); + goep_server_handle_connection_opened(goep_connection, event_addr, + l2cap_event_channel_opened_get_handle(packet), + l2cap_event_channel_opened_get_status(packet), + l2cap_cid, + l2cap_event_channel_opened_get_remote_mtu(packet) ); + return; + case L2CAP_EVENT_CAN_SEND_NOW: + l2cap_cid = l2cap_event_can_send_now_get_local_cid(packet); + goep_connection = goep_server_get_connection_for_l2cap_cid(l2cap_cid); + btstack_assert(goep_connection != NULL); + goep_server_emit_can_send_now_event(goep_connection); + break; + case L2CAP_EVENT_CHANNEL_CLOSED: + l2cap_cid = l2cap_event_channel_closed_get_local_cid(packet); + goep_connection = goep_server_get_connection_for_l2cap_cid(l2cap_cid); + btstack_assert(goep_connection != NULL); + goep_server_handle_connection_closed(goep_connection); + break; + default: + break; + } + break; + case L2CAP_DATA_PACKET: + goep_connection = goep_server_get_connection_for_l2cap_cid(channel); + btstack_assert(goep_connection != NULL); + goep_connection->callback(GOEP_DATA_PACKET, goep_connection->goep_cid, packet, size); + break; + default: + break; + } +} +#endif + +static void goep_server_packet_handler_rfcomm(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + bd_addr_t event_addr; + uint8_t rfcomm_channel; + uint16_t rfcomm_cid; + goep_server_service_t * goep_service; + goep_server_connection_t * goep_connection; + + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case RFCOMM_EVENT_INCOMING_CONNECTION: + rfcomm_channel = rfcomm_event_incoming_connection_get_server_channel(packet); + rfcomm_cid = rfcomm_event_incoming_connection_get_rfcomm_cid(packet); + + goep_service = goep_server_get_service_for_rfcomm_channel(rfcomm_channel); + if (!goep_service){ + rfcomm_decline_connection(rfcomm_cid); + break; + } + + // alloc structure + goep_connection = btstack_memory_goep_server_connection_get(); + if (!goep_connection){ + rfcomm_decline_connection(rfcomm_cid); + break; + } + + // setup connection + goep_connection->goep_cid = goep_server_get_next_goep_cid(); + goep_connection->bearer_cid = rfcomm_cid; + goep_connection->callback = goep_service->callback; + goep_connection->type = GOEP_CONNECTION_RFCOMM; + goep_connection->state = GOEP_SERVER_W4_CONNECTED; + btstack_linked_list_add(&goep_server_connections, (btstack_linked_item_t *) goep_connection); + rfcomm_accept_connection(rfcomm_cid); + break; + + case RFCOMM_EVENT_CHANNEL_OPENED: + rfcomm_cid = rfcomm_event_channel_opened_get_rfcomm_cid(packet); + goep_connection = goep_server_get_connection_for_rfcomm_cid(rfcomm_cid); + btstack_assert(goep_connection != NULL); + btstack_assert(goep_connection->state == GOEP_SERVER_W4_CONNECTED); + rfcomm_event_channel_opened_get_bd_addr(packet, event_addr); + goep_server_handle_connection_opened(goep_connection, event_addr, + rfcomm_event_channel_opened_get_con_handle(packet), + rfcomm_event_channel_opened_get_status(packet), + rfcomm_cid, + rfcomm_event_channel_opened_get_max_frame_size(packet) ); + break; + + case RFCOMM_EVENT_CAN_SEND_NOW: + rfcomm_cid = rfcomm_event_can_send_now_get_rfcomm_cid(packet); + goep_connection = goep_server_get_connection_for_rfcomm_cid(rfcomm_cid); + btstack_assert(goep_connection != NULL); + goep_server_emit_can_send_now_event(goep_connection); + break; + + case RFCOMM_EVENT_CHANNEL_CLOSED: + rfcomm_cid = rfcomm_event_channel_closed_get_rfcomm_cid(packet); + goep_connection = goep_server_get_connection_for_rfcomm_cid(rfcomm_cid); + btstack_assert(goep_connection != NULL); + goep_server_handle_connection_closed(goep_connection); + break; + + default: + break; + } + break; + + case RFCOMM_DATA_PACKET: + goep_connection = goep_server_get_connection_for_rfcomm_cid(channel); + btstack_assert(goep_connection != NULL); + goep_connection->callback(GOEP_DATA_PACKET, goep_connection->goep_cid, packet, size); + break; + + default: + break; + } +} + +void goep_server_init(void){ +} + +uint8_t goep_server_register_service(btstack_packet_handler_t callback, uint8_t rfcomm_channel, uint16_t rfcomm_max_frame_size, + uint16_t l2cap_psm, uint16_t l2cap_mtu, gap_security_level_t security_level){ + + log_info("rfcomm_channel 0x%02x rfcomm_max_frame_size %u l2cap_psm 0x%02x l2cap_mtu %u", + rfcomm_channel, rfcomm_max_frame_size, l2cap_psm, l2cap_mtu); + + // check if service is already registered + goep_server_service_t * service; + service = goep_server_get_service_for_rfcomm_channel(rfcomm_channel); + if (service != NULL) { + return RFCOMM_CHANNEL_ALREADY_REGISTERED; + } + +#ifdef ENABLE_GOEP_L2CAP + if (l2cap_psm != 0){ + service = goep_server_get_service_for_l2cap_psm(l2cap_psm); + if (service != NULL) { + return L2CAP_SERVICE_ALREADY_REGISTERED; + } + } +#endif + + // alloc structure + service = btstack_memory_goep_server_service_get(); + if (service == NULL) { + return BTSTACK_MEMORY_ALLOC_FAILED; + } + + // fill in + service->callback = callback; + service->rfcomm_channel = rfcomm_channel; + service->l2cap_psm = l2cap_psm; + + uint8_t status = ERROR_CODE_SUCCESS; + bool rfcomm_registered = false; + +#ifdef ENABLE_GOEP_L2CAP + bool l2cap_registered = false; + // register with L2CAP + if (l2cap_psm != 0){ + status = l2cap_register_service(goep_server_packet_handler_l2cap, l2cap_psm, l2cap_mtu, security_level); + if (status == ERROR_CODE_SUCCESS){ + l2cap_registered = true; + } + } +#endif + + // register with RFCOMM + if (status == ERROR_CODE_SUCCESS){ + status = rfcomm_register_service(goep_server_packet_handler_rfcomm, rfcomm_channel, rfcomm_max_frame_size); + if (status == ERROR_CODE_SUCCESS){ + rfcomm_registered = true; + } + } + + // add service on success + if (status == ERROR_CODE_SUCCESS){ + btstack_linked_list_add(&goep_server_services, (btstack_linked_item_t *) service); + return ERROR_CODE_SUCCESS; + } + + // unrestore otherwise + btstack_memory_goep_server_service_free(service); +#ifdef ENABLE_GOEP_L2CAP + if (l2cap_registered){ + l2cap_unregister_service(l2cap_psm); + } +#endif + if (rfcomm_registered){ + rfcomm_unregister_service(rfcomm_channel); + } + return status; +} + +uint8_t goep_server_request_can_send_now(uint16_t goep_cid){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL){ + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + switch (connection->type){ + case GOEP_CONNECTION_RFCOMM: + rfcomm_request_can_send_now_event(connection->bearer_cid); + break; +#ifdef ENABLE_GOEP_L2CAP + case GOEP_CONNECTION_L2CAP: + l2cap_request_can_send_now_event(connection->bearer_cid); + break; +#endif + default: + btstack_unreachable(); + break; + } + return ERROR_CODE_SUCCESS; +} + +static uint8_t * goep_server_get_outgoing_buffer(goep_server_connection_t * connection){ + switch (connection->type){ +#ifdef ENABLE_GOEP_L2CAP + case GOEP_CONNECTION_L2CAP: + return goep_server_l2cap_packet_buffer; +#endif + case GOEP_CONNECTION_RFCOMM: + return rfcomm_get_outgoing_buffer(); + default: + btstack_unreachable(); + return NULL; + } +} + +static uint16_t goep_server_get_outgoing_buffer_len(goep_server_connection_t * connection){ + return connection->bearer_mtu; +} + +static void goep_server_packet_init(goep_server_connection_t * connection){ + switch (connection->type){ +#ifdef ENABLE_GOEP_L2CAP + case GOEP_CONNECTION_L2CAP: + break; +#endif + case GOEP_CONNECTION_RFCOMM: + rfcomm_reserve_packet_buffer(); + break; + default: + btstack_unreachable(); + break; + } +} + +uint8_t goep_server_set_connection_id(uint16_t goep_cid, uint32_t connection_id){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + connection->obex_connection_id = connection_id; + return ERROR_CODE_SUCCESS; +} + +uint8_t goep_server_response_create_connect(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + goep_server_packet_init(connection); + + // workaround: limit OBEX packet len to L2CAP/RFCOMM MTU + maximum_obex_packet_length = btstack_min(maximum_obex_packet_length, connection->bearer_mtu); + + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t buffer_len = goep_server_get_outgoing_buffer_len(connection); + return obex_message_builder_response_create_connect(buffer, buffer_len, obex_version_number, flags, maximum_obex_packet_length, connection->obex_connection_id); +} + +uint8_t goep_server_response_create_general(uint16_t goep_cid, uint8_t opcode){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + goep_server_packet_init(connection); + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t buffer_len = goep_server_get_outgoing_buffer_len(connection); + return obex_message_builder_response_create_general(buffer, buffer_len, opcode); +} + +uint8_t goep_server_header_add_end_of_body(uint16_t goep_cid, const uint8_t * end_of_body, uint16_t length){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t buffer_len = goep_server_get_outgoing_buffer_len(connection); + return obex_message_builder_body_add_static(buffer, buffer_len, end_of_body, length); +} + +uint8_t goep_server_header_add_who(uint16_t goep_cid, const uint8_t * target){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t buffer_len = goep_server_get_outgoing_buffer_len(connection); + return obex_message_builder_header_add_who(buffer, buffer_len, target); +} + +uint8_t goep_server_header_add_srm_enable(uint16_t goep_cid){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t buffer_len = goep_server_get_outgoing_buffer_len(connection); + return obex_message_builder_header_add_srm_enable(buffer, buffer_len); +} + +uint8_t goep_server_execute(uint16_t goep_cid){ + goep_server_connection_t * connection = goep_server_get_connection_for_goep_cid(goep_cid); + if (connection == NULL) { + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + + uint8_t * buffer = goep_server_get_outgoing_buffer(connection); + uint16_t pos = big_endian_read_16(buffer, 1); + switch (connection->type) { +#ifdef ENABLE_GOEP_L2CAP + case GOEP_CONNECTION_L2CAP: + return l2cap_send(connection->bearer_cid, buffer, pos); + break; +#endif + case GOEP_CONNECTION_RFCOMM: + return rfcomm_send_prepared(connection->bearer_cid, pos); + break; + default: + btstack_unreachable(); + return ERROR_CODE_SUCCESS; + } +} + +void goep_server_deinit(void){ + goep_server_cid_counter = 0; + goep_server_services = NULL; +} diff --git a/src/classic/goep_server.h b/src/classic/goep_server.h index 641c9394c..57cce3f22 100644 --- a/src/classic/goep_server.h +++ b/src/classic/goep_server.h @@ -45,17 +45,23 @@ extern "C" { #include "btstack_config.h" #include +#ifdef ENABLE_GOEP_L2CAP +#ifndef GOEP_SERVER_ERTM_BUFFER +#define GOEP_SERVER_ERTM_BUFFER 1000 +#endif +#endif + /* API_START */ typedef enum { GOEP_SERVER_IDLE, - GOEP_SERVER_W4_RFCOMM_CONNECTED, - GOEP_SERVER_RFCOMM_CONNECTED + GOEP_SERVER_W4_CONNECTED, + GOEP_SERVER_CONNECTED } goep_server_state_t; typedef enum { - GOEP_L2CAP_CONNECTION = 0, - GOEP_RFCOMM_CONNECTION + GOEP_CONNECTION_RFCOMM = 0, + GOEP_CONNECTION_L2CAP, } goep_connection_type_t; typedef struct { @@ -71,12 +77,101 @@ typedef struct { typedef struct { // linked list - assert: first field btstack_linked_item_t item; - uint16_t bearer_cid; + uint16_t goep_cid; - goep_connection_type_t type; - goep_server_service_t * service; goep_server_state_t state; + goep_connection_type_t type; + uint16_t bearer_cid; + uint16_t bearer_mtu; + btstack_packet_handler_t callback; + uint32_t obex_connection_id; + +#ifdef ENABLE_GOEP_L2CAP + uint8_t ertm_buffer[GOEP_SERVER_ERTM_BUFFER]; +#endif + } goep_server_connection_t; + +/** + * Init GOEP Server + */ +void goep_server_init(void); + + +/** + * Register OBEX Service + * @param packet_handler + * @param rfcomm_channel + * @param rfcomm_max_frame_size + * @param l2cap_psm + * @param l2cap_mtu + * @param security_level + * @return status + */ +uint8_t goep_server_register_service(btstack_packet_handler_t packet_handler, uint8_t rfcomm_channel, uint16_t rfcomm_max_frame_size, + uint16_t l2cap_psm, uint16_t l2cap_mtu, gap_security_level_t security_level); + +/** + * Request GOEP_SUBEVENT_CAN_SEND_NOW + * @param goep_cid + * @return status + */ +uint8_t goep_server_request_can_send_now(uint16_t goep_cid); + +/** + * @brief Set Connection ID used for newly created requests + * @param goep_cid + * @return status + */ +uint8_t goep_server_set_connection_id(uint16_t goep_cid, uint32_t connection_id); + +/** + * @brief Start Connect response + * @param goep_cid + * @param obex_version_number + * @param flags + * @param maximum_obex_packet_length + * @return status + */ +uint8_t goep_server_response_create_connect(uint16_t goep_cid, uint8_t obex_version_number, uint8_t flags, uint16_t maximum_obex_packet_length); + +/** + * @brief Start General response with opcode + * @param goep_cid + * @param opcode + * @return status + */ +uint8_t goep_server_response_create_general(uint16_t goep_cid, uint8_t opcode); + +/** + * @brief Add who header to current response + * @param goep_cid + * @param who - 16 bytes + * @return status + */ +uint8_t goep_server_header_add_who(uint16_t goep_cid, const uint8_t * who); + +/** + * @brief Add end_of_body header to current response + * @param goep_cid + * @param end_of_body + * @param length + * @return status + */ +uint8_t goep_server_header_add_end_of_body(uint16_t goep_cid, const uint8_t * end_of_body, uint16_t length); + +/** + * @brief Execute prepared request + * @param goep_cid + * @return status + */ +uint8_t goep_server_execute(uint16_t goep_cid); + +/** + * De-Init + */ +void geop_server_deinit(void); + /* API_END */ #if defined __cplusplus