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