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