xref: /btstack/src/ble/gatt-service/ublox_spp_service_server.c (revision 733ac1e89f3bf7a997f023020bc29323054bc91c)
1*733ac1e8SMilanka Ringwald /*
2*733ac1e8SMilanka Ringwald  * Copyright (C) 2018 BlueKitchen GmbH
3*733ac1e8SMilanka Ringwald  *
4*733ac1e8SMilanka Ringwald  * Redistribution and use in source and binary forms, with or without
5*733ac1e8SMilanka Ringwald  * modification, are permitted provided that the following conditions
6*733ac1e8SMilanka Ringwald  * are met:
7*733ac1e8SMilanka Ringwald  *
8*733ac1e8SMilanka Ringwald  * 1. Redistributions of source code must retain the above copyright
9*733ac1e8SMilanka Ringwald  *    notice, this list of conditions and the following disclaimer.
10*733ac1e8SMilanka Ringwald  * 2. Redistributions in binary form must reproduce the above copyright
11*733ac1e8SMilanka Ringwald  *    notice, this list of conditions and the following disclaimer in the
12*733ac1e8SMilanka Ringwald  *    documentation and/or other materials provided with the distribution.
13*733ac1e8SMilanka Ringwald  * 3. Neither the name of the copyright holders nor the names of
14*733ac1e8SMilanka Ringwald  *    contributors may be used to endorse or promote products derived
15*733ac1e8SMilanka Ringwald  *    from this software without specific prior written permission.
16*733ac1e8SMilanka Ringwald  * 4. Any redistribution, use, or modification is done solely for
17*733ac1e8SMilanka Ringwald  *    personal benefit and not for any commercial purpose or for
18*733ac1e8SMilanka Ringwald  *    monetary gain.
19*733ac1e8SMilanka Ringwald  *
20*733ac1e8SMilanka Ringwald  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21*733ac1e8SMilanka Ringwald  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22*733ac1e8SMilanka Ringwald  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23*733ac1e8SMilanka Ringwald  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24*733ac1e8SMilanka Ringwald  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25*733ac1e8SMilanka Ringwald  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26*733ac1e8SMilanka Ringwald  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27*733ac1e8SMilanka Ringwald  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28*733ac1e8SMilanka Ringwald  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29*733ac1e8SMilanka Ringwald  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30*733ac1e8SMilanka Ringwald  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31*733ac1e8SMilanka Ringwald  * SUCH DAMAGE.
32*733ac1e8SMilanka Ringwald  *
33*733ac1e8SMilanka Ringwald  * Please inquire about commercial licensing options at
34*733ac1e8SMilanka Ringwald  * [email protected]
35*733ac1e8SMilanka Ringwald  *
36*733ac1e8SMilanka Ringwald  */
37*733ac1e8SMilanka Ringwald 
38*733ac1e8SMilanka Ringwald #define __BTSTACK_FILE__ "ublox_spp_service_server.c"
39*733ac1e8SMilanka Ringwald 
40*733ac1e8SMilanka Ringwald /**
41*733ac1e8SMilanka Ringwald  * Implementation of the ublox SPP-like profile
42*733ac1e8SMilanka Ringwald  *
43*733ac1e8SMilanka Ringwald  * To use with your application, add '#import <ublox_spp_service.gatt' to your .gatt file
44*733ac1e8SMilanka Ringwald  * and call all functions below. All strings and blobs need to stay valid after calling the functions.
45*733ac1e8SMilanka Ringwald  */
46*733ac1e8SMilanka Ringwald 
47*733ac1e8SMilanka Ringwald #include "btstack_defines.h"
48*733ac1e8SMilanka Ringwald #include "btstack_event.h"
49*733ac1e8SMilanka Ringwald #include "btstack_debug.h"
50*733ac1e8SMilanka Ringwald #include "btstack_util.h"
51*733ac1e8SMilanka Ringwald #include "bluetooth_gatt.h"
52*733ac1e8SMilanka Ringwald #include "hci.h"
53*733ac1e8SMilanka Ringwald #include "ble/att_db.h"
54*733ac1e8SMilanka Ringwald #include "ble/att_server.h"
55*733ac1e8SMilanka Ringwald #include "ble/gatt-service/ublox_spp_service_server.h"
56*733ac1e8SMilanka Ringwald 
57*733ac1e8SMilanka Ringwald #define UBLOX_SPP_MAX_CREDITS           16
58*733ac1e8SMilanka Ringwald #define UBLOX_SPP_CREDITS_THRESHOLD      8
59*733ac1e8SMilanka Ringwald 
60*733ac1e8SMilanka Ringwald //
61*733ac1e8SMilanka Ringwald static const uint8_t ublox_spp_profile_uuid128[] = { 0x24, 0x56, 0xE1, 0xB9, 0x26, 0xE2, 0x8F, 0x83, 0xE7, 0x44, 0xF3, 0x4F, 0x01, 0xE9, 0xD7, 0x01 };
62*733ac1e8SMilanka Ringwald static const uint8_t ublox_spp_fifo_uuid128[]    = { 0x24, 0x56, 0xE1, 0xB9, 0x26, 0xE2, 0x8F, 0x83, 0xE7, 0x44, 0xF3, 0x4F, 0x01, 0xE9, 0xD7, 0x03 };
63*733ac1e8SMilanka Ringwald static const uint8_t ublox_spp_credits_uuid128[] = { 0x24, 0x56, 0xE1, 0xB9, 0x26, 0xE2, 0x8F, 0x83, 0xE7, 0x44, 0xF3, 0x4F, 0x01, 0xE9, 0xD7, 0x04 };
64*733ac1e8SMilanka Ringwald 
65*733ac1e8SMilanka Ringwald typedef struct {
66*733ac1e8SMilanka Ringwald     hci_con_handle_t con_handle;
67*733ac1e8SMilanka Ringwald 
68*733ac1e8SMilanka Ringwald     // characteristic: FIFO
69*733ac1e8SMilanka Ringwald     uint16_t fifo_value_handle;
70*733ac1e8SMilanka Ringwald     uint8_t  data[20];
71*733ac1e8SMilanka Ringwald 
72*733ac1e8SMilanka Ringwald     // characteristic descriptor: Client Characteristic Configuration
73*733ac1e8SMilanka Ringwald     uint16_t fifo_client_configuration_descriptor_handle;
74*733ac1e8SMilanka Ringwald     uint16_t fifo_client_configuration_descriptor_value; // none, notify or indicate;
75*733ac1e8SMilanka Ringwald     btstack_context_callback_registration_t fifo_callback;
76*733ac1e8SMilanka Ringwald 
77*733ac1e8SMilanka Ringwald     // characteristic: Flow control/credits
78*733ac1e8SMilanka Ringwald     uint16_t credits_value_handle;
79*733ac1e8SMilanka Ringwald     uint16_t incoming_credits;
80*733ac1e8SMilanka Ringwald     uint16_t outgoing_credits;
81*733ac1e8SMilanka Ringwald     uint8_t  delta_credits;
82*733ac1e8SMilanka Ringwald 
83*733ac1e8SMilanka Ringwald     // characteristic descriptor: Client Characteristic Configuration
84*733ac1e8SMilanka Ringwald     uint16_t credits_client_configuration_descriptor_handle;
85*733ac1e8SMilanka Ringwald     uint16_t credits_client_configuration_descriptor_value;
86*733ac1e8SMilanka Ringwald 
87*733ac1e8SMilanka Ringwald     btstack_context_callback_registration_t credits_callback;
88*733ac1e8SMilanka Ringwald 
89*733ac1e8SMilanka Ringwald     void (*client_data_callback)(hci_con_handle_t con_handle, const uint8_t * data, uint16_t size);
90*733ac1e8SMilanka Ringwald     void (*client_credits_callback)(hci_con_handle_t con_handle, uint16_t credits);
91*733ac1e8SMilanka Ringwald 
92*733ac1e8SMilanka Ringwald     // flow control
93*733ac1e8SMilanka Ringwald     btstack_context_callback_registration_t * request;
94*733ac1e8SMilanka Ringwald } ublox_spp_service_t;
95*733ac1e8SMilanka Ringwald 
96*733ac1e8SMilanka Ringwald static att_service_handler_t  ublox_spp_service;
97*733ac1e8SMilanka Ringwald static ublox_spp_service_t    ublox_spp;
98*733ac1e8SMilanka Ringwald 
99*733ac1e8SMilanka Ringwald static int ublox_spp_service_flow_control_enabled(ublox_spp_service_t * instance){
100*733ac1e8SMilanka Ringwald     return instance->credits_client_configuration_descriptor_value;
101*733ac1e8SMilanka Ringwald }
102*733ac1e8SMilanka Ringwald 
103*733ac1e8SMilanka Ringwald static ublox_spp_service_t * ublox_get_instance_for_con_handle(hci_con_handle_t con_handle){
104*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = &ublox_spp;
105*733ac1e8SMilanka Ringwald     if (con_handle == HCI_CON_HANDLE_INVALID) return NULL;
106*733ac1e8SMilanka Ringwald     instance->con_handle = con_handle;
107*733ac1e8SMilanka Ringwald     return instance;
108*733ac1e8SMilanka Ringwald }
109*733ac1e8SMilanka Ringwald 
110*733ac1e8SMilanka Ringwald static inline void ublox_spp_service_init_credits(ublox_spp_service_t * instance){
111*733ac1e8SMilanka Ringwald     instance->incoming_credits = 0;
112*733ac1e8SMilanka Ringwald     instance->outgoing_credits = 0;
113*733ac1e8SMilanka Ringwald     instance->delta_credits = UBLOX_SPP_MAX_CREDITS;
114*733ac1e8SMilanka Ringwald }
115*733ac1e8SMilanka Ringwald 
116*733ac1e8SMilanka Ringwald static uint16_t ublox_spp_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
117*733ac1e8SMilanka Ringwald     UNUSED(offset);
118*733ac1e8SMilanka Ringwald     UNUSED(buffer_size);
119*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = ublox_get_instance_for_con_handle(con_handle);
120*733ac1e8SMilanka Ringwald     if (!instance) return 0;
121*733ac1e8SMilanka Ringwald 
122*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->fifo_client_configuration_descriptor_handle){
123*733ac1e8SMilanka Ringwald         if (buffer){
124*733ac1e8SMilanka Ringwald             little_endian_store_16(buffer, 0, instance->fifo_client_configuration_descriptor_value);
125*733ac1e8SMilanka Ringwald         }
126*733ac1e8SMilanka Ringwald         return 2;
127*733ac1e8SMilanka Ringwald     }
128*733ac1e8SMilanka Ringwald 
129*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->credits_client_configuration_descriptor_handle){
130*733ac1e8SMilanka Ringwald         if (buffer){
131*733ac1e8SMilanka Ringwald             little_endian_store_16(buffer, 0, instance->credits_client_configuration_descriptor_value);
132*733ac1e8SMilanka Ringwald         }
133*733ac1e8SMilanka Ringwald         return 2;
134*733ac1e8SMilanka Ringwald     }
135*733ac1e8SMilanka Ringwald     return 0;
136*733ac1e8SMilanka Ringwald }
137*733ac1e8SMilanka Ringwald 
138*733ac1e8SMilanka Ringwald 
139*733ac1e8SMilanka Ringwald static int ublox_spp_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
140*733ac1e8SMilanka Ringwald     UNUSED(transaction_mode);
141*733ac1e8SMilanka Ringwald     UNUSED(offset);
142*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = ublox_get_instance_for_con_handle(con_handle);
143*733ac1e8SMilanka Ringwald     if (!instance) return 0;
144*733ac1e8SMilanka Ringwald 
145*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->fifo_value_handle){
146*733ac1e8SMilanka Ringwald         instance->client_data_callback(con_handle, &buffer[0], buffer_size);
147*733ac1e8SMilanka Ringwald         if (!ublox_spp_service_flow_control_enabled(instance)) return 0;
148*733ac1e8SMilanka Ringwald         if (!instance->incoming_credits) return 0;
149*733ac1e8SMilanka Ringwald         instance->incoming_credits--;
150*733ac1e8SMilanka Ringwald         if (instance->incoming_credits < UBLOX_SPP_CREDITS_THRESHOLD){
151*733ac1e8SMilanka Ringwald             att_server_request_to_send_notification(&instance->credits_callback, instance->con_handle);
152*733ac1e8SMilanka Ringwald         }
153*733ac1e8SMilanka Ringwald     }
154*733ac1e8SMilanka Ringwald 
155*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->fifo_client_configuration_descriptor_handle){
156*733ac1e8SMilanka Ringwald         if (buffer_size < 2){
157*733ac1e8SMilanka Ringwald             return ATT_ERROR_INVALID_OFFSET;
158*733ac1e8SMilanka Ringwald         }
159*733ac1e8SMilanka Ringwald         instance->fifo_client_configuration_descriptor_value = little_endian_read_16(buffer, 0);
160*733ac1e8SMilanka Ringwald         instance->client_data_callback(con_handle, NULL, 0);
161*733ac1e8SMilanka Ringwald     }
162*733ac1e8SMilanka Ringwald 
163*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->credits_value_handle){
164*733ac1e8SMilanka Ringwald         if (!ublox_spp_service_flow_control_enabled(instance)) return 0;
165*733ac1e8SMilanka Ringwald         int8_t credits = (int8_t)buffer[0];
166*733ac1e8SMilanka Ringwald         if (credits <= 0) return 0;
167*733ac1e8SMilanka Ringwald         instance->outgoing_credits += credits;
168*733ac1e8SMilanka Ringwald         if (instance->request){
169*733ac1e8SMilanka Ringwald             btstack_context_callback_registration_t * request = instance->request;
170*733ac1e8SMilanka Ringwald             instance->request = NULL;
171*733ac1e8SMilanka Ringwald             att_server_request_to_send_notification(request, instance->con_handle);
172*733ac1e8SMilanka Ringwald         }
173*733ac1e8SMilanka Ringwald     }
174*733ac1e8SMilanka Ringwald 
175*733ac1e8SMilanka Ringwald     if (attribute_handle == instance->credits_client_configuration_descriptor_handle){
176*733ac1e8SMilanka Ringwald         if (buffer_size < 2){
177*733ac1e8SMilanka Ringwald             return ATT_ERROR_INVALID_OFFSET;
178*733ac1e8SMilanka Ringwald         }
179*733ac1e8SMilanka Ringwald         instance->credits_client_configuration_descriptor_value = little_endian_read_16(buffer, 0);
180*733ac1e8SMilanka Ringwald 
181*733ac1e8SMilanka Ringwald         ublox_spp_service_init_credits(instance);
182*733ac1e8SMilanka Ringwald         if (instance->credits_client_configuration_descriptor_value){
183*733ac1e8SMilanka Ringwald             att_server_request_to_send_notification(&instance->credits_callback, instance->con_handle);
184*733ac1e8SMilanka Ringwald         }
185*733ac1e8SMilanka Ringwald     }
186*733ac1e8SMilanka Ringwald 
187*733ac1e8SMilanka Ringwald     return 0;
188*733ac1e8SMilanka Ringwald }
189*733ac1e8SMilanka Ringwald 
190*733ac1e8SMilanka Ringwald static void ublox_spp_credits_callback(void * context){
191*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = (ublox_spp_service_t *) context;
192*733ac1e8SMilanka Ringwald     if (!instance) return;
193*733ac1e8SMilanka Ringwald 
194*733ac1e8SMilanka Ringwald     instance->delta_credits = UBLOX_SPP_MAX_CREDITS - instance->incoming_credits;
195*733ac1e8SMilanka Ringwald     if (instance->delta_credits){
196*733ac1e8SMilanka Ringwald         instance->incoming_credits = UBLOX_SPP_MAX_CREDITS;
197*733ac1e8SMilanka Ringwald         att_server_notify(instance->con_handle, instance->credits_value_handle, &instance->delta_credits, 1);
198*733ac1e8SMilanka Ringwald     }
199*733ac1e8SMilanka Ringwald }
200*733ac1e8SMilanka Ringwald /**
201*733ac1e8SMilanka Ringwald  * @brief Init ublox SPP Service Server with ATT DB
202*733ac1e8SMilanka Ringwald  * @param callback for tx data from peer
203*733ac1e8SMilanka Ringwald  */
204*733ac1e8SMilanka Ringwald void ublox_spp_service_server_init(void (*client_data_callback)(hci_con_handle_t con_handle, const uint8_t * data, uint16_t size),
205*733ac1e8SMilanka Ringwald                                    void (*client_credits_callback)(hci_con_handle_t con_handle, uint16_t credits)){
206*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = &ublox_spp;
207*733ac1e8SMilanka Ringwald     instance->client_data_callback = client_data_callback;
208*733ac1e8SMilanka Ringwald     instance->client_credits_callback = client_credits_callback;
209*733ac1e8SMilanka Ringwald 
210*733ac1e8SMilanka Ringwald     instance->credits_callback.callback = ublox_spp_credits_callback;
211*733ac1e8SMilanka Ringwald     instance->credits_callback.context = instance;
212*733ac1e8SMilanka Ringwald     ublox_spp_service_init_credits(instance);
213*733ac1e8SMilanka Ringwald 
214*733ac1e8SMilanka Ringwald 
215*733ac1e8SMilanka Ringwald     // get service handle range
216*733ac1e8SMilanka Ringwald     uint16_t start_handle = 0;
217*733ac1e8SMilanka Ringwald     uint16_t end_handle   = 0xfff;
218*733ac1e8SMilanka Ringwald     int service_found = gatt_server_get_get_handle_range_for_service_with_uuid128(ublox_spp_profile_uuid128, &start_handle, &end_handle);
219*733ac1e8SMilanka Ringwald     if (!service_found) return;
220*733ac1e8SMilanka Ringwald 
221*733ac1e8SMilanka Ringwald     // get characteristic value handle and client configuration handle
222*733ac1e8SMilanka Ringwald     // FIFO
223*733ac1e8SMilanka Ringwald     instance->fifo_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid128(start_handle, end_handle, ublox_spp_fifo_uuid128);
224*733ac1e8SMilanka Ringwald     instance->fifo_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(start_handle, end_handle, ublox_spp_fifo_uuid128);
225*733ac1e8SMilanka Ringwald     // Credits
226*733ac1e8SMilanka Ringwald     instance->credits_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid128(start_handle, end_handle, ublox_spp_credits_uuid128);
227*733ac1e8SMilanka Ringwald     instance->credits_client_configuration_descriptor_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(start_handle, end_handle, ublox_spp_credits_uuid128);
228*733ac1e8SMilanka Ringwald 
229*733ac1e8SMilanka Ringwald     log_info("FIFO        value handle 0x%02x", instance->fifo_value_handle);
230*733ac1e8SMilanka Ringwald     log_info("FIFO CCC    value handle 0x%02x", instance->fifo_client_configuration_descriptor_handle);
231*733ac1e8SMilanka Ringwald     log_info("Credits     value handle 0x%02x", instance->credits_value_handle);
232*733ac1e8SMilanka Ringwald     log_info("Credits CCC value handle 0x%02x", instance->credits_client_configuration_descriptor_handle);
233*733ac1e8SMilanka Ringwald 
234*733ac1e8SMilanka Ringwald     // register service with ATT Server
235*733ac1e8SMilanka Ringwald     ublox_spp_service.start_handle   = start_handle;
236*733ac1e8SMilanka Ringwald     ublox_spp_service.end_handle     = end_handle;
237*733ac1e8SMilanka Ringwald     ublox_spp_service.read_callback  = &ublox_spp_service_read_callback;
238*733ac1e8SMilanka Ringwald     ublox_spp_service.write_callback = &ublox_spp_service_write_callback;
239*733ac1e8SMilanka Ringwald     att_server_register_service_handler(&ublox_spp_service);
240*733ac1e8SMilanka Ringwald }
241*733ac1e8SMilanka Ringwald 
242*733ac1e8SMilanka Ringwald /**
243*733ac1e8SMilanka Ringwald  * @brief Queue send request. When called, one packet can be send via ublox_spp_service_send below
244*733ac1e8SMilanka Ringwald  * @param request
245*733ac1e8SMilanka Ringwald  * @param con_handle
246*733ac1e8SMilanka Ringwald  */
247*733ac1e8SMilanka Ringwald void ublox_spp_service_server_request_can_send_now(btstack_context_callback_registration_t * request, hci_con_handle_t con_handle){
248*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = &ublox_spp;
249*733ac1e8SMilanka Ringwald     if (!ublox_spp_service_flow_control_enabled(instance) || instance->outgoing_credits) {
250*733ac1e8SMilanka Ringwald         att_server_request_to_send_notification(request, con_handle);
251*733ac1e8SMilanka Ringwald         return;
252*733ac1e8SMilanka Ringwald     }
253*733ac1e8SMilanka Ringwald     instance->request = request;
254*733ac1e8SMilanka Ringwald }
255*733ac1e8SMilanka Ringwald 
256*733ac1e8SMilanka Ringwald /**
257*733ac1e8SMilanka Ringwald  * @brief Send data
258*733ac1e8SMilanka Ringwald  * @param con_handle
259*733ac1e8SMilanka Ringwald  * @param data
260*733ac1e8SMilanka Ringwald  * @param size
261*733ac1e8SMilanka Ringwald  */
262*733ac1e8SMilanka Ringwald int ublox_spp_service_server_send(hci_con_handle_t con_handle, const uint8_t * data, uint16_t size){
263*733ac1e8SMilanka Ringwald     ublox_spp_service_t * instance = &ublox_spp;
264*733ac1e8SMilanka Ringwald     if (ublox_spp_service_flow_control_enabled(instance)){
265*733ac1e8SMilanka Ringwald         if (instance->outgoing_credits > 0){
266*733ac1e8SMilanka Ringwald             instance->outgoing_credits--;
267*733ac1e8SMilanka Ringwald         }
268*733ac1e8SMilanka Ringwald     }
269*733ac1e8SMilanka Ringwald     return att_server_notify(con_handle, instance->fifo_value_handle, &data[0], size);
270*733ac1e8SMilanka Ringwald }
271*733ac1e8SMilanka Ringwald 
272