xref: /btstack/src/ble/gatt-service/ancs_client.c (revision 77043c43b8afb83c6683758c4c21bfba99b3a253)
1 /*
2  * Copyright (C) 2014 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
24  * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "ancs_client.c"
39 
40 #include "btstack_config.h"
41 
42 #include <stdint.h>
43 #include <string.h>
44 
45 #include "ble/gatt-service/ancs_client.h"
46 
47 #include "ble/core.h"
48 #include "ble/gatt_client.h"
49 #include "ble/sm.h"
50 #include "btstack_debug.h"
51 #include "btstack_event.h"
52 #include "gap.h"
53 
54 // ancs_client.h Start
55 typedef enum ancs_chunk_parser_state {
56     W4_ATTRIBUTE_ID,
57     W4_ATTRIBUTE_LEN,
58     W4_ATTRIBUTE_COMPLETE,
59 } ancs_chunk_parser_state_t;
60 
61 typedef enum {
62     TC_IDLE,
63     TC_W4_ENCRYPTED_CONNECTION,
64     TC_W2_QUERY_SERVICE,
65     TC_W4_SERVICE_RESULT,
66     TC_W2_QUERY_CARACTERISTIC,
67     TC_W4_CHARACTERISTIC_RESULT,
68     TC_W2_SUBSCRIBE_DATA_SOURCE,
69     TC_W4_DATA_SOURCE_SUBSCRIBED,
70     TC_W2_ENABLE_NOTIFICATION,
71     TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED,
72     TC_SUBSCRIBED,
73     TC_W4_DISCONNECT
74 } tc_state_t;
75 
76 static uint32_t ancs_notification_uid;
77 static hci_con_handle_t gc_handle;
78 static gatt_client_notification_t ancs_notification_source_notification;
79 static gatt_client_notification_t ancs_data_source_notification;
80 static int ancs_service_found;
81 static gatt_client_service_t  ancs_service;
82 static gatt_client_characteristic_t ancs_notification_source_characteristic;
83 static gatt_client_characteristic_t ancs_control_point_characteristic;
84 static gatt_client_characteristic_t ancs_data_source_characteristic;
85 static int ancs_characteristcs;
86 static tc_state_t tc_state = TC_IDLE;
87 
88 static ancs_chunk_parser_state_t chunk_parser_state;
89 static uint8_t  ancs_notification_buffer[50];
90 static uint16_t ancs_bytes_received;
91 static uint16_t ancs_bytes_needed;
92 static uint8_t  ancs_attribute_id;
93 static uint16_t ancs_attribute_len;
94 
95 static btstack_packet_handler_t client_handler;
96 static btstack_packet_callback_registration_t hci_event_callback_registration;
97 static btstack_context_callback_registration_t ancs_client_handle_can_send_now;
98 
99 static void ancs_client_handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
100 
ancs_client_register_callback(btstack_packet_handler_t handler)101 void ancs_client_register_callback(btstack_packet_handler_t handler){
102     client_handler = handler;
103 }
104 
ancs_client_request_send_gatt_query(void)105 static uint8_t ancs_client_request_send_gatt_query(void){
106     uint8_t status = gatt_client_request_to_send_gatt_query(&ancs_client_handle_can_send_now, gc_handle);
107     if (status != ERROR_CODE_SUCCESS){
108         tc_state = TC_IDLE;
109         gc_handle = HCI_CON_HANDLE_INVALID;
110     }
111     return status;
112 }
113 
notify_client_text(int event_type)114 static void notify_client_text(int event_type){
115     if (!client_handler) return;
116     uint8_t event[7 + sizeof(ancs_notification_buffer) + 1];
117     event[0] = HCI_EVENT_ANCS_META;
118     event[1] = 5u + ancs_attribute_len;
119     event[2] = event_type;
120     little_endian_store_16(event, 3, gc_handle);
121     little_endian_store_16(event, 5, ancs_attribute_id);
122     (void)memcpy(&event[7], ancs_notification_buffer, ancs_attribute_len);
123     // we're nice
124     event[7u+ancs_attribute_len] = 0u;
125     (*client_handler)(HCI_EVENT_PACKET, 0u, event, event[1u] + 2u);
126 }
127 
notify_client_simple(int event_type)128 static void notify_client_simple(int event_type){
129     if (!client_handler) return;
130     uint8_t event[5];
131     event[0] = HCI_EVENT_ANCS_META;
132     event[1] = 3;
133     event[2] = event_type;
134     little_endian_store_16(event, 3, gc_handle);
135     (*client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
136 }
137 
ancs_chunk_parser_init(void)138 static void ancs_chunk_parser_init(void){
139     // skip comand id and notification uid
140     chunk_parser_state = W4_ATTRIBUTE_ID;
141     ancs_bytes_received = 0;
142     ancs_bytes_needed = 6;
143 }
144 
ancs_client_attribute_name_for_id(int id)145 const char * ancs_client_attribute_name_for_id(int id){
146     static const char * ancs_attribute_names[] = {
147             "AppIdentifier",
148             "IDTitle",
149             "IDSubtitle",
150             "IDMessage",
151             "IDMessageSize",
152             "IDDate"
153     };
154 
155     static const uint16_t ANCS_ATTRIBUTE_NAMES_COUNT = sizeof(ancs_attribute_names) / sizeof(char *);
156 
157     if (id >= ANCS_ATTRIBUTE_NAMES_COUNT) return NULL;
158     return ancs_attribute_names[id];
159 }
160 
ancs_chunk_parser_handle_byte(uint8_t data)161 static void ancs_chunk_parser_handle_byte(uint8_t data){
162     ancs_notification_buffer[ancs_bytes_received++] = data;
163     if (ancs_bytes_received < ancs_bytes_needed) return;
164     switch (chunk_parser_state){
165         case W4_ATTRIBUTE_ID:
166             ancs_attribute_id   = ancs_notification_buffer[ancs_bytes_received-1u];
167             ancs_bytes_received = 0;
168             ancs_bytes_needed   = 2;
169             chunk_parser_state  = W4_ATTRIBUTE_LEN;
170             break;
171         case W4_ATTRIBUTE_LEN:
172             ancs_attribute_len  = little_endian_read_16(ancs_notification_buffer, ancs_bytes_received-2u);
173             ancs_bytes_received = 0;
174             ancs_bytes_needed   = ancs_attribute_len;
175             if (ancs_attribute_len == 0u) {
176                 ancs_bytes_needed   = 1;
177                 chunk_parser_state  = W4_ATTRIBUTE_ID;
178                 break;
179             }
180             chunk_parser_state  = W4_ATTRIBUTE_COMPLETE;
181             break;
182         case W4_ATTRIBUTE_COMPLETE:
183             ancs_notification_buffer[ancs_bytes_received] = 0;
184             notify_client_text(ANCS_SUBEVENT_CLIENT_NOTIFICATION);
185             ancs_bytes_received = 0;
186             ancs_bytes_needed   = 1;
187             chunk_parser_state  = W4_ATTRIBUTE_ID;
188             break;
189         default:
190             btstack_unreachable();
191             break;
192     }
193 }
194 
ancs_client_handle_notification(uint16_t value_handle,const uint8_t * value,uint16_t value_length)195 static void ancs_client_handle_notification(uint16_t value_handle, const uint8_t * value, uint16_t value_length){
196 
197     log_info("ANCS Notification, value handle %u", value_handle);
198 
199     if (value_handle == ancs_data_source_characteristic.value_handle){
200         int i;
201         for (i=0;i<value_length;i++) {
202             ancs_chunk_parser_handle_byte(value[i]);
203         }
204     } else if (value_handle == ancs_notification_source_characteristic.value_handle){
205         ancs_notification_uid = little_endian_read_32(value, 4);
206         log_info("Notification received: EventID %02x, EventFlags %02x, CategoryID %02x, CategoryCount %u, UID %04x",
207                  value[0], value[1], value[2], value[3], (int) ancs_notification_uid);
208         static uint8_t get_notification_attributes[] = {0, 0,0,0,0,  0,  1,32,0,  2,32,0, 3,32,0, 4, 5};
209         little_endian_store_32(get_notification_attributes, 1, ancs_notification_uid);
210         ancs_notification_uid = 0;
211         ancs_chunk_parser_init();
212         gatt_client_write_value_of_characteristic(ancs_client_handle_gatt_client_event, gc_handle, ancs_control_point_characteristic.value_handle,
213                                                   sizeof(get_notification_attributes), get_notification_attributes);
214     } else {
215         log_info("Unknown Source: ");
216         log_info_hexdump(value , value_length);
217     }
218 }
219 
ancs_client_send_next_query(void * context)220 static void ancs_client_send_next_query(void * context){
221     UNUSED(context);
222     static const uint8_t ancs_service_uuid[] =             {0x79,0x05,0xF4,0x31,0xB5,0xCE,0x4E,0x99,0xA4,0x0F,0x4B,0x1E,0x12,0x2D,0x00,0xD0};
223 
224     switch(tc_state){
225         case TC_W2_QUERY_SERVICE:
226             tc_state = TC_W4_SERVICE_RESULT;
227             (void) gatt_client_discover_primary_services_by_uuid128(ancs_client_handle_gatt_client_event, gc_handle, ancs_service_uuid);
228             break;
229 
230         case TC_W2_QUERY_CARACTERISTIC:
231             tc_state = TC_W4_CHARACTERISTIC_RESULT;
232             log_info("ANCS Client - Discover characteristics for ANCS SERVICE ");
233             gatt_client_discover_characteristics_for_service(ancs_client_handle_gatt_client_event, gc_handle, &ancs_service);
234             break;
235 
236         case TC_W2_ENABLE_NOTIFICATION:
237             tc_state = TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED;
238             gatt_client_listen_for_characteristic_value_updates(&ancs_notification_source_notification, &ancs_client_handle_gatt_client_event, gc_handle, &ancs_notification_source_characteristic);
239             gatt_client_write_client_characteristic_configuration(ancs_client_handle_gatt_client_event, gc_handle, &ancs_notification_source_characteristic,
240                                                                         GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
241             break;
242 
243         case TC_W2_SUBSCRIBE_DATA_SOURCE:
244             tc_state = TC_W4_DATA_SOURCE_SUBSCRIBED;
245             gatt_client_listen_for_characteristic_value_updates(&ancs_data_source_notification, &ancs_client_handle_gatt_client_event, gc_handle, &ancs_data_source_characteristic);
246             gatt_client_write_client_characteristic_configuration(ancs_client_handle_gatt_client_event, gc_handle, &ancs_data_source_characteristic,
247                                                                           GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
248             break;
249 
250         default:
251             break;
252     }
253 }
254 
ancs_client_handle_gatt_client_event_in_w4_service_result(uint8_t * packet)255 static void ancs_client_handle_gatt_client_event_in_w4_service_result(uint8_t* packet) {
256     switch(hci_event_packet_get_type(packet)){
257         case GATT_EVENT_SERVICE_QUERY_RESULT:
258             gatt_event_service_query_result_get_service(packet, &ancs_service);
259             ancs_service_found = 1;
260             break;
261         case GATT_EVENT_QUERY_COMPLETE:
262             if (!ancs_service_found){
263                 log_info("ANCS Service not found");
264                 tc_state = TC_IDLE;
265                 break;
266             }
267             tc_state = TC_W2_QUERY_CARACTERISTIC;
268             break;
269         default:
270             break;
271     }
272 }
273 
ancs_client_handle_gatt_client_event(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)274 static void ancs_client_handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
275 
276     UNUSED(packet_type);
277     UNUSED(channel);
278     UNUSED(size);
279 
280     static const uint8_t ancs_notification_source_uuid[] = {0x9F,0xBF,0x12,0x0D,0x63,0x01,0x42,0xD9,0x8C,0x58,0x25,0xE6,0x99,0xA2,0x1D,0xBD};
281     static const uint8_t ancs_control_point_uuid[] =       {0x69,0xD1,0xD8,0xF3,0x45,0xE1,0x49,0xA8,0x98,0x21,0x9B,0xBD,0xFD,0xAA,0xD9,0xD9};
282     static const uint8_t ancs_data_source_uuid[] =         {0x22,0xEA,0xC6,0xE9,0x24,0xD6,0x4B,0xB5,0xBE,0x44,0xB3,0x6A,0xCE,0x7C,0x7B,0xFB};
283 
284     gatt_client_characteristic_t characteristic;
285 
286     switch(tc_state){
287         case TC_W4_SERVICE_RESULT:
288             ancs_client_handle_gatt_client_event_in_w4_service_result(packet);
289             break;
290 
291         case TC_W4_CHARACTERISTIC_RESULT:
292             switch(hci_event_packet_get_type(packet)){
293                 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
294                     gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
295                     if (memcmp(characteristic.uuid128, ancs_notification_source_uuid, 16) == 0){
296                         log_info("ANCS Notification Source found, attribute handle %u", characteristic.value_handle);
297                         ancs_notification_source_characteristic = characteristic;
298                         ancs_characteristcs++;
299                         break;
300                     }
301                     if (memcmp(characteristic.uuid128, ancs_control_point_uuid, 16) == 0){
302                         log_info("ANCS Control Point found, attribute handle %u", characteristic.value_handle);
303                         ancs_control_point_characteristic = characteristic;
304                         ancs_characteristcs++;
305                         break;
306                     }
307                     if (memcmp(characteristic.uuid128, ancs_data_source_uuid, 16) == 0){
308                         log_info("ANCS Data Source found, attribute handle %u", characteristic.value_handle);
309                         ancs_data_source_characteristic = characteristic;
310                         ancs_characteristcs++;
311                         break;
312                     }
313                     break;
314                 case GATT_EVENT_QUERY_COMPLETE:
315                     log_info("ANCS Characteristcs count %u", ancs_characteristcs);
316                     tc_state = TC_W2_ENABLE_NOTIFICATION;
317                     break;
318                 default:
319                     break;
320             }
321             break;
322 
323         case TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED:
324             switch(hci_event_packet_get_type(packet)){
325                 case GATT_EVENT_QUERY_COMPLETE:
326                     log_info("ANCS Notification Source subscribed");
327                     tc_state = TC_W2_SUBSCRIBE_DATA_SOURCE;
328 
329                     break;
330                 default:
331                     break;
332             }
333             break;
334 
335         case TC_W4_DATA_SOURCE_SUBSCRIBED:
336             switch(hci_event_packet_get_type(packet)){
337                 case GATT_EVENT_QUERY_COMPLETE:
338                     log_info("ANCS Data Source subscribed");
339                     tc_state = TC_SUBSCRIBED;
340                     notify_client_simple(ANCS_SUBEVENT_CLIENT_CONNECTED);
341                     break;
342                 default:
343                     break;
344             }
345             break;
346 
347         case TC_SUBSCRIBED:
348             switch(hci_event_packet_get_type(packet)){
349                 case GATT_EVENT_NOTIFICATION:
350                     ancs_client_handle_notification(
351                         gatt_event_notification_get_value_handle(packet),
352                         gatt_event_notification_get_value(packet),
353                         gatt_event_notification_get_value_length(packet)
354                     );
355                     break;
356                 case GATT_EVENT_INDICATION:
357                     ancs_client_handle_notification(
358                         gatt_event_indication_get_value_handle(packet),
359                         gatt_event_indication_get_value(packet),
360                         gatt_event_indication_get_value_length(packet)
361                     );
362                 break;
363                 default:
364                     break;
365             }
366             break;
367 
368         default:
369             break;
370     }
371 
372     uint8_t status;
373     switch(tc_state){
374         case TC_W2_QUERY_SERVICE:
375         case TC_W2_QUERY_CARACTERISTIC:
376         case TC_W2_ENABLE_NOTIFICATION:
377         case TC_W2_SUBSCRIBE_DATA_SOURCE:
378             status = gatt_client_request_to_send_gatt_query(&ancs_client_handle_can_send_now, gc_handle);
379             if (status != ERROR_CODE_SUCCESS){
380                 notify_client_simple(ANCS_SUBEVENT_CLIENT_DISCONNECTED);
381             }
382             break;
383 
384         default:
385             break;
386     }
387 }
388 
handle_hci_event(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)389 static void handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
390 
391     UNUSED(packet_type); // ok: only hci events
392     UNUSED(channel);     // ok: there is no channel
393     UNUSED(size);        // ok: fixed format events read from HCI buffer
394 
395     int connection_encrypted;
396     uint8_t status;
397 
398     // handle connect / disconncet events first
399     switch (hci_event_packet_get_type(packet)) {
400         case HCI_EVENT_META_GAP:
401             switch (hci_event_gap_meta_get_subevent_code(packet)) {
402                 case GAP_SUBEVENT_LE_CONNECTION_COMPLETE:
403                     gc_handle = gap_subevent_le_connection_complete_get_connection_handle(packet);
404                     log_info("Connection handle 0x%04x, request encryption", gc_handle);
405 
406                     // we need to be paired to enable notifications
407                     tc_state = TC_W4_ENCRYPTED_CONNECTION;
408                     ancs_service_found = false;
409                     sm_request_pairing(gc_handle);
410                     break;
411                 default:
412                     break;
413             }
414             return;
415 
416         case HCI_EVENT_ENCRYPTION_CHANGE:
417         case HCI_EVENT_ENCRYPTION_CHANGE_V2:
418             if (gc_handle != hci_event_encryption_change_get_connection_handle(packet)) return;
419             connection_encrypted = hci_event_encryption_change_get_encryption_enabled(packet);
420             log_info("Encryption state change: %u", connection_encrypted);
421             if (!connection_encrypted) return;
422             if (tc_state != TC_W4_ENCRYPTED_CONNECTION) return;
423 
424             // let's start
425             log_info("\nANCS Client - CONNECTED, discover ANCS service");
426             tc_state = TC_W2_QUERY_SERVICE;
427             status = ancs_client_request_send_gatt_query();
428             if (status != ERROR_CODE_SUCCESS){
429                notify_client_simple(ANCS_SUBEVENT_CLIENT_DISCONNECTED);
430             }
431             return;
432 
433         case HCI_EVENT_DISCONNECTION_COMPLETE:
434             if (hci_event_disconnection_complete_get_connection_handle(packet) != gc_handle) break;
435             if (tc_state == TC_SUBSCRIBED){
436                 notify_client_simple(ANCS_SUBEVENT_CLIENT_DISCONNECTED);
437             }
438             tc_state = TC_IDLE;
439             gc_handle = HCI_CON_HANDLE_INVALID;
440             return;
441 
442         default:
443             break;
444     }
445 }
446 
ancs_client_init(void)447 void ancs_client_init(void){
448     hci_event_callback_registration.callback = &handle_hci_event;
449     hci_add_event_handler(&hci_event_callback_registration);
450     ancs_client_handle_can_send_now.callback = &ancs_client_send_next_query;
451 }
452 
453 // unit test only
454 #if defined __cplusplus
455 extern "C"
456 #endif
457 void ancs_client_set_invalid_parser_state(void);
ancs_client_set_invalid_parser_state(void)458 void ancs_client_set_invalid_parser_state(void){
459     chunk_parser_state = (ancs_chunk_parser_state_t) 0x17;
460 }
461