xref: /nrf52832-nimble/packages/NimBLE-latest/nimble/host/services/ans/src/ble_svc_ans.c (revision 042d53a763ad75cb1465103098bb88c245d95138)
1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include <assert.h>
21 #include <string.h>
22 
23 #include "sysinit/sysinit.h"
24 #include "syscfg/syscfg.h"
25 #include "host/ble_hs.h"
26 #include "host/ble_gap.h"
27 #include "services/ans/ble_svc_ans.h"
28 
29 /* Max length of new alert info string */
30 #define BLE_SVC_ANS_INFO_STR_MAX_LEN        18
31 /* Max length of a new alert notification, max string length + 2 bytes
32  * for category ID and count. */
33 #define BLE_SVC_ANS_NEW_ALERT_MAX_LEN   (BLE_SVC_ANS_INFO_STR_MAX_LEN + 2)
34 
35 /* Supported categories bitmasks */
36 static uint8_t ble_svc_ans_new_alert_cat;
37 static uint8_t ble_svc_ans_unr_alert_cat;
38 
39 /* Characteristic values */
40 static uint8_t ble_svc_ans_new_alert_val[BLE_SVC_ANS_NEW_ALERT_MAX_LEN];
41 static uint16_t ble_svc_ans_new_alert_val_len;
42 static uint8_t ble_svc_ans_unr_alert_stat[2];
43 static uint8_t ble_svc_ans_alert_not_ctrl_pt[2];
44 
45 /* Alert counts, one value for each category */
46 static uint8_t ble_svc_ans_new_alert_cnt[BLE_SVC_ANS_CAT_NUM];
47 static uint8_t ble_svc_ans_unr_alert_cnt[BLE_SVC_ANS_CAT_NUM];
48 
49 /* Characteristic value handles */
50 static uint16_t ble_svc_ans_new_alert_val_handle;
51 static uint16_t ble_svc_ans_unr_alert_val_handle;
52 
53 /* Connection handle
54  *
55  * TODO: In order to support multiple connections we would need to save
56  *       the handles for every connection, not just the most recent. Then
57  *       we would need to notify each connection when needed.
58  * */
59 static uint16_t ble_svc_ans_conn_handle;
60 
61 /* Access function */
62 static int
63 ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle,
64                    struct ble_gatt_access_ctxt *ctxt, void *arg);
65 
66 /* Notify new alert */
67 static int
68 ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str);
69 
70 /* Notify unread alert */
71 static int
72 ble_svc_ans_unr_alert_notify(uint8_t cat_id);
73 
74 /* Save written value to local characteristic value */
75 static int
76 ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
77                       void *dst, uint16_t *len);
78 
79 static const struct ble_gatt_svc_def ble_svc_ans_defs[] = {
80     {
81         /*** Alert Notification Service. */
82         .type = BLE_GATT_SVC_TYPE_PRIMARY,
83         .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_UUID16),
84         .characteristics = (struct ble_gatt_chr_def[]) { {
85             /** Supported New Alert Catagory
86              *
87              * This characteristic exposes what categories of new
88              * alert are supported in the server.
89              */
90             .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT),
91             .access_cb = ble_svc_ans_access,
92             .flags = BLE_GATT_CHR_F_READ,
93         }, {
94             /** New Alert
95              *
96              * This characteristic exposes information about
97              * the count of new alerts (for a given category).
98              */
99             .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_NEW_ALERT),
100             .access_cb = ble_svc_ans_access,
101             .val_handle = &ble_svc_ans_new_alert_val_handle,
102             .flags = BLE_GATT_CHR_F_NOTIFY,
103         }, {
104             /** Supported Unread Alert Catagory
105              *
106              * This characteristic exposes what categories of
107              * unread alert are supported in the server.
108              */
109             .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT),
110             .access_cb = ble_svc_ans_access,
111             .flags = BLE_GATT_CHR_F_READ,
112         }, {
113             /** Unread Alert Status
114              *
115              * This characteristic exposes the count of unread
116              * alert events existing in the server.
117              */
118             .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT),
119             .access_cb = ble_svc_ans_access,
120             .val_handle = &ble_svc_ans_unr_alert_val_handle,
121             .flags = BLE_GATT_CHR_F_NOTIFY,
122         }, {
123             /** Alert Notification Control Point
124              *
125              * This characteristic allows the peer device to
126              * enable/disable the alert notification of new alert
127              * and unread event more selectively than can be done
128              * by setting or clearing the notification bit in the
129              * Client Characteristic Configuration for each alert
130              * characteristic.
131              */
132             .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT),
133             .access_cb = ble_svc_ans_access,
134             .flags = BLE_GATT_CHR_F_WRITE,
135         }, {
136             0, /* No more characteristics in this service. */
137         } },
138     },
139 
140     {
141         0, /* No more services. */
142     },
143 };
144 
145 /**
146  * ANS access function
147  */
148 static int
ble_svc_ans_access(uint16_t conn_handle,uint16_t attr_handle,struct ble_gatt_access_ctxt * ctxt,void * arg)149 ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle,
150                           struct ble_gatt_access_ctxt *ctxt,
151                           void *arg)
152 {
153     uint16_t uuid16;
154     int rc;
155 
156     /* ANS Control point command and catagory variables */
157     uint8_t cmd_id;
158     uint8_t cat_id;
159     uint8_t cat_bit_mask;
160     int i;
161 
162     uuid16 = ble_uuid_u16(ctxt->chr->uuid);
163     assert(uuid16 != 0);
164 
165     switch (uuid16) {
166     case BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT:
167         assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
168         rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_cat,
169                             sizeof ble_svc_ans_new_alert_cat);
170         return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
171 
172     case BLE_SVC_ANS_CHR_UUID16_NEW_ALERT:
173         if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
174             rc = ble_svc_ans_chr_write(ctxt->om, 0,
175                                        sizeof ble_svc_ans_new_alert_val,
176                                        ble_svc_ans_new_alert_val,
177                                        &ble_svc_ans_new_alert_val_len);
178             return rc;
179 
180         } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
181             rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_val,
182                                 sizeof ble_svc_ans_new_alert_val);
183             return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
184         }
185 
186         assert(0);
187         return BLE_ATT_ERR_UNLIKELY;
188     case BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT:
189         assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
190         rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_cat,
191                             sizeof ble_svc_ans_unr_alert_cat);
192         return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
193 
194     case BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT:
195         if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
196             rc = ble_svc_ans_chr_write(ctxt->om,
197                                        sizeof ble_svc_ans_unr_alert_stat,
198                                        sizeof ble_svc_ans_unr_alert_stat,
199                                        &ble_svc_ans_unr_alert_stat,
200                                        NULL);
201             return rc;
202         } else {
203             rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_stat,
204                                 sizeof ble_svc_ans_unr_alert_stat);
205             return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
206         }
207 
208     case BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT:
209         if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
210             rc = ble_svc_ans_chr_write(ctxt->om,
211                                        sizeof ble_svc_ans_alert_not_ctrl_pt,
212                                        sizeof ble_svc_ans_alert_not_ctrl_pt,
213                                        &ble_svc_ans_alert_not_ctrl_pt,
214                                        NULL);
215             if (rc != 0) {
216                 return rc;
217             }
218 
219             /* Get command ID and category ID */
220             cmd_id = ble_svc_ans_alert_not_ctrl_pt[0];
221             cat_id = ble_svc_ans_alert_not_ctrl_pt[1];
222 
223 
224             /* Set cat_bit_mask to the appropriate bitmask based on cat_id */
225             if (cat_id < BLE_SVC_ANS_CAT_NUM) {
226                 cat_bit_mask = (1 << cat_id);
227             } else if (cat_id == 0xff) {
228                 cat_bit_mask = cat_id;
229             } else {
230                 /* invalid category ID */
231                 return BLE_ATT_ERR_UNLIKELY;
232             }
233 
234             switch (cmd_id) {
235             case BLE_SVC_ANS_CMD_EN_NEW_ALERT_CAT:
236                 ble_svc_ans_new_alert_cat |= cat_bit_mask;
237                 break;
238             case BLE_SVC_ANS_CMD_EN_UNR_ALERT_CAT:
239                 ble_svc_ans_unr_alert_cat |= cat_bit_mask;
240                 break;
241             case BLE_SVC_ANS_CMD_DIS_NEW_ALERT_CAT:
242                 ble_svc_ans_new_alert_cat &= ~cat_bit_mask;
243                 break;
244             case BLE_SVC_ANS_CMD_DIS_UNR_ALERT_CAT:
245                 ble_svc_ans_unr_alert_cat &= ~cat_bit_mask;
246                 break;
247             case BLE_SVC_ANS_CMD_NOT_NEW_ALERT_IMMEDIATE:
248                 if (cat_id == 0xff) {
249                     /* If cat_id is 0xff, notify on all enabled categories */
250                     for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) {
251                         if ((ble_svc_ans_new_alert_cat >> i) & 0x01) {
252                             ble_svc_ans_new_alert_notify(i, NULL);
253                         }
254                     }
255                 } else {
256                     ble_svc_ans_new_alert_notify(cat_id, NULL);
257                 }
258                 break;
259             case BLE_SVC_ANS_CMD_NOT_UNR_ALERT_IMMEDIATE:
260                 if (cat_id == 0xff) {
261                     /* If cat_id is 0xff, notify on all enabled categories */
262                     for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) {
263                         if ((ble_svc_ans_unr_alert_cat >> i) & 0x01) {
264                             ble_svc_ans_unr_alert_notify(i);
265                         }
266                     }
267                 } else {
268                     ble_svc_ans_unr_alert_notify(cat_id);
269                 }
270                 break;
271             default:
272                 return BLE_SVC_ANS_ERR_CMD_NOT_SUPPORTED;
273             }
274             return 0;
275         } else {
276             rc = BLE_ATT_ERR_UNLIKELY;
277         }
278         return rc;
279 
280     default:
281         assert(0);
282         return BLE_ATT_ERR_UNLIKELY;
283     }
284 }
285 
286 /**
287  * This function must be called with the connection handlewhen a gap
288  * connect event is received in order to send notifications to the
289  * client.
290  *
291  * @params conn_handle          The connection handle for the current
292  *                                  connection.
293  */
294 void
ble_svc_ans_on_gap_connect(uint16_t conn_handle)295 ble_svc_ans_on_gap_connect(uint16_t conn_handle)
296 {
297     ble_svc_ans_conn_handle = conn_handle;
298 }
299 
300 /**
301  * Adds a new alert to the given category then notifies the client
302  * if the given category is valid and enabled.
303  *
304  * @param cat_flag              The id for the category which should
305  *                                  should be incremented and notified
306  * @param info_str              The info string to be sent to the client
307  *                                  with the notification.
308  *
309  * @return 0 on success, non-zero error code otherwise.
310  */
311 int
ble_svc_ans_new_alert_add(uint8_t cat_id,const char * info_str)312 ble_svc_ans_new_alert_add(uint8_t cat_id, const char * info_str)
313 {
314     uint8_t cat_bit_mask;
315 
316     if (cat_id < BLE_SVC_ANS_CAT_NUM) {
317         cat_bit_mask = (1 << cat_id);
318     } else {
319         return BLE_HS_EINVAL;
320     }
321 
322     if ((cat_bit_mask & ble_svc_ans_new_alert_cat) == 0) {
323         return BLE_HS_EINVAL;
324     }
325 
326     ble_svc_ans_new_alert_cnt[cat_id] += 1;
327     return ble_svc_ans_new_alert_notify(cat_id, info_str);
328 }
329 
330 /**
331  * Adds an unread alert to the given category then notifies the client
332  * if the given category is valid and enabled.
333  *
334  * @param cat_flag              The flag for the category which should
335  *                                  should be incremented and notified
336  *
337  * @return 0 on success, non-zero error code otherwise.
338  */
339 int
ble_svc_ans_unr_alert_add(uint8_t cat_id)340 ble_svc_ans_unr_alert_add(uint8_t cat_id)
341 {
342     uint8_t cat_bit_mask;
343 
344     if (cat_id < BLE_SVC_ANS_CAT_NUM) {
345         cat_bit_mask = 1 << cat_id;
346     } else {
347         return BLE_HS_EINVAL;
348     }
349 
350     if ((cat_bit_mask & ble_svc_ans_unr_alert_cat) == 0) {
351         return BLE_HS_EINVAL;
352     }
353 
354     ble_svc_ans_unr_alert_cnt[cat_id] += 1;
355     return ble_svc_ans_unr_alert_notify(cat_id);
356 }
357 
358 /**
359  * Send a new alert notification to the given category with the
360  * given info string.
361  *
362  * @param cat_id                The ID of the category to send the
363  *                                  notification to.
364  * @param info_str              The info string to send with the
365  *                                  notification
366  *
367  * @return 0 on success, non-zero error code otherwise.
368  */
369 static int
ble_svc_ans_new_alert_notify(uint8_t cat_id,const char * info_str)370 ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str)
371 {
372     int info_str_len;
373 
374     /* Clear notification to remove old infomation that may persist */
375     memset(&ble_svc_ans_new_alert_val, '\0',
376            BLE_SVC_ANS_NEW_ALERT_MAX_LEN);
377 
378     /* Set ID and count values */
379     ble_svc_ans_new_alert_val[0] = cat_id;
380     ble_svc_ans_new_alert_val[1] = ble_svc_ans_new_alert_cnt[cat_id];
381 
382     if (info_str) {
383         info_str_len = strlen(info_str);
384         if (info_str_len > BLE_SVC_ANS_INFO_STR_MAX_LEN) {
385             /* If info_str is longer than the max string length only
386              * write up to the maximum length */
387             memcpy(&ble_svc_ans_new_alert_val[2], info_str,
388                    BLE_SVC_ANS_INFO_STR_MAX_LEN);
389         } else {
390             memcpy(&ble_svc_ans_new_alert_val[2], info_str, info_str_len);
391         }
392     }
393     return ble_gattc_notify(ble_svc_ans_conn_handle,
394                             ble_svc_ans_new_alert_val_handle);
395 }
396 
397 /**
398  * Send an unread alert notification to the given category.
399  *
400  * @param cat_id                The ID of the category to send the
401  *                                  notificaiton to.
402  *
403  * @return 0 on success, non-zer0 error code otherwise.
404  */
405 static int
ble_svc_ans_unr_alert_notify(uint8_t cat_id)406 ble_svc_ans_unr_alert_notify(uint8_t cat_id)
407 {
408     ble_svc_ans_unr_alert_stat[0] = cat_id;
409     ble_svc_ans_unr_alert_stat[1] = ble_svc_ans_unr_alert_cnt[cat_id];
410     return ble_gattc_notify(ble_svc_ans_conn_handle,
411                             ble_svc_ans_unr_alert_val_handle);
412 }
413 
414 /**
415  * Writes the received value from a characteristic write to
416  * the given destination.
417  */
418 static int
ble_svc_ans_chr_write(struct os_mbuf * om,uint16_t min_len,uint16_t max_len,void * dst,uint16_t * len)419 ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len,
420                       uint16_t max_len, void *dst,
421                       uint16_t *len)
422 {
423     uint16_t om_len;
424     int rc;
425 
426     om_len = OS_MBUF_PKTLEN(om);
427     if (om_len < min_len || om_len > max_len) {
428         return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
429     }
430 
431     rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
432     if (rc != 0) {
433         return BLE_ATT_ERR_UNLIKELY;
434     }
435 
436     return 0;
437 }
438 
439 /**
440  * Initialize the ANS with initial values for enabled categories
441  * for new and unread alert characteristics. Bitwise or the
442  * catagory bitmasks to enable multiple catagories.
443  *
444  * XXX: We should technically be able to change the new alert and
445  *      unread alert catagories when we have no active connections.
446  */
447 void
ble_svc_ans_init(void)448 ble_svc_ans_init(void)
449 {
450     int rc;
451 
452     /* Ensure this function only gets called by sysinit. */
453     SYSINIT_ASSERT_ACTIVE();
454 
455     rc = ble_gatts_count_cfg(ble_svc_ans_defs);
456     SYSINIT_PANIC_ASSERT(rc == 0);
457 
458     rc = ble_gatts_add_svcs(ble_svc_ans_defs);
459     SYSINIT_PANIC_ASSERT(rc == 0);
460 
461     ble_svc_ans_new_alert_cat = MYNEWT_VAL(BLE_SVC_ANS_NEW_ALERT_CAT);
462     ble_svc_ans_unr_alert_cat = MYNEWT_VAL(BLE_SVC_ANS_UNR_ALERT_CAT);
463 }
464