blob: 5b64f18c44af5851db4b1637c67d7fae285c8a27 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include "sysinit/sysinit.h"
#include "syscfg/syscfg.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "services/ans/ble_svc_ans.h"
/* Max length of new alert info string */
#define BLE_SVC_ANS_INFO_STR_MAX_LEN 18
/* Max length of a new alert notification, max string length + 2 bytes
* for category ID and count. */
#define BLE_SVC_ANS_NEW_ALERT_MAX_LEN (BLE_SVC_ANS_INFO_STR_MAX_LEN + 2)
/* Supported categories bitmasks */
static uint8_t ble_svc_ans_new_alert_cat;
static uint8_t ble_svc_ans_unr_alert_cat;
/* Characteristic values */
static uint8_t ble_svc_ans_new_alert_val[BLE_SVC_ANS_NEW_ALERT_MAX_LEN];
static uint16_t ble_svc_ans_new_alert_val_len;
static uint8_t ble_svc_ans_unr_alert_stat[2];
static uint8_t ble_svc_ans_alert_not_ctrl_pt[2];
/* Alert counts, one value for each category */
static uint8_t ble_svc_ans_new_alert_cnt[BLE_SVC_ANS_CAT_NUM];
static uint8_t ble_svc_ans_unr_alert_cnt[BLE_SVC_ANS_CAT_NUM];
/* Characteristic value handles */
static uint16_t ble_svc_ans_new_alert_val_handle;
static uint16_t ble_svc_ans_unr_alert_val_handle;
/* Connection handle
*
* TODO: In order to support multiple connections we would need to save
* the handles for every connection, not just the most recent. Then
* we would need to notify each connection when needed.
* */
static uint16_t ble_svc_ans_conn_handle;
/* Access function */
static int
ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
/* Notify new alert */
static int
ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str);
/* Notify unread alert */
static int
ble_svc_ans_unr_alert_notify(uint8_t cat_id);
/* Save written value to local characteristic value */
static int
ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
void *dst, uint16_t *len);
static const struct ble_gatt_svc_def ble_svc_ans_defs[] = {
{
/*** Alert Notification Service. */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_UUID16),
.characteristics = (struct ble_gatt_chr_def[]) { {
/** Supported New Alert Catagory
*
* This characteristic exposes what categories of new
* alert are supported in the server.
*/
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT),
.access_cb = ble_svc_ans_access,
.flags = BLE_GATT_CHR_F_READ,
}, {
/** New Alert
*
* This characteristic exposes information about
* the count of new alerts (for a given category).
*/
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_NEW_ALERT),
.access_cb = ble_svc_ans_access,
.val_handle = &ble_svc_ans_new_alert_val_handle,
.flags = BLE_GATT_CHR_F_NOTIFY,
}, {
/** Supported Unread Alert Catagory
*
* This characteristic exposes what categories of
* unread alert are supported in the server.
*/
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT),
.access_cb = ble_svc_ans_access,
.flags = BLE_GATT_CHR_F_READ,
}, {
/** Unread Alert Status
*
* This characteristic exposes the count of unread
* alert events existing in the server.
*/
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT),
.access_cb = ble_svc_ans_access,
.val_handle = &ble_svc_ans_unr_alert_val_handle,
.flags = BLE_GATT_CHR_F_NOTIFY,
}, {
/** Alert Notification Control Point
*
* This characteristic allows the peer device to
* enable/disable the alert notification of new alert
* and unread event more selectively than can be done
* by setting or clearing the notification bit in the
* Client Characteristic Configuration for each alert
* characteristic.
*/
.uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT),
.access_cb = ble_svc_ans_access,
.flags = BLE_GATT_CHR_F_WRITE,
}, {
0, /* No more characteristics in this service. */
} },
},
{
0, /* No more services. */
},
};
/**
* ANS access function
*/
static int
ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
uint16_t uuid16;
int rc;
/* ANS Control point command and catagory variables */
uint8_t cmd_id;
uint8_t cat_id;
uint8_t cat_bit_mask;
int i;
uuid16 = ble_uuid_u16(ctxt->chr->uuid);
assert(uuid16 != 0);
switch (uuid16) {
case BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT:
assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_cat,
sizeof ble_svc_ans_new_alert_cat);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
case BLE_SVC_ANS_CHR_UUID16_NEW_ALERT:
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
rc = ble_svc_ans_chr_write(ctxt->om, 0,
sizeof ble_svc_ans_new_alert_val,
ble_svc_ans_new_alert_val,
&ble_svc_ans_new_alert_val_len);
return rc;
} else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_val,
sizeof ble_svc_ans_new_alert_val);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
case BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT:
assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_cat,
sizeof ble_svc_ans_unr_alert_cat);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
case BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT:
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
rc = ble_svc_ans_chr_write(ctxt->om,
sizeof ble_svc_ans_unr_alert_stat,
sizeof ble_svc_ans_unr_alert_stat,
&ble_svc_ans_unr_alert_stat,
NULL);
return rc;
} else {
rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_stat,
sizeof ble_svc_ans_unr_alert_stat);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT:
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
rc = ble_svc_ans_chr_write(ctxt->om,
sizeof ble_svc_ans_alert_not_ctrl_pt,
sizeof ble_svc_ans_alert_not_ctrl_pt,
&ble_svc_ans_alert_not_ctrl_pt,
NULL);
if (rc != 0) {
return rc;
}
/* Get command ID and category ID */
cmd_id = ble_svc_ans_alert_not_ctrl_pt[0];
cat_id = ble_svc_ans_alert_not_ctrl_pt[1];
/* Set cat_bit_mask to the appropriate bitmask based on cat_id */
if (cat_id < BLE_SVC_ANS_CAT_NUM) {
cat_bit_mask = (1 << cat_id);
} else if (cat_id == 0xff) {
cat_bit_mask = cat_id;
} else {
/* invalid category ID */
return BLE_ATT_ERR_UNLIKELY;
}
switch (cmd_id) {
case BLE_SVC_ANS_CMD_EN_NEW_ALERT_CAT:
ble_svc_ans_new_alert_cat |= cat_bit_mask;
break;
case BLE_SVC_ANS_CMD_EN_UNR_ALERT_CAT:
ble_svc_ans_unr_alert_cat |= cat_bit_mask;
break;
case BLE_SVC_ANS_CMD_DIS_NEW_ALERT_CAT:
ble_svc_ans_new_alert_cat &= ~cat_bit_mask;
break;
case BLE_SVC_ANS_CMD_DIS_UNR_ALERT_CAT:
ble_svc_ans_unr_alert_cat &= ~cat_bit_mask;
break;
case BLE_SVC_ANS_CMD_NOT_NEW_ALERT_IMMEDIATE:
if (cat_id == 0xff) {
/* If cat_id is 0xff, notify on all enabled categories */
for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) {
if ((ble_svc_ans_new_alert_cat >> i) & 0x01) {
ble_svc_ans_new_alert_notify(i, NULL);
}
}
} else {
ble_svc_ans_new_alert_notify(cat_id, NULL);
}
break;
case BLE_SVC_ANS_CMD_NOT_UNR_ALERT_IMMEDIATE:
if (cat_id == 0xff) {
/* If cat_id is 0xff, notify on all enabled categories */
for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) {
if ((ble_svc_ans_unr_alert_cat >> i) & 0x01) {
ble_svc_ans_unr_alert_notify(i);
}
}
} else {
ble_svc_ans_unr_alert_notify(cat_id);
}
break;
default:
return BLE_SVC_ANS_ERR_CMD_NOT_SUPPORTED;
}
return 0;
} else {
rc = BLE_ATT_ERR_UNLIKELY;
}
return rc;
default:
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
}
/**
* This function must be called with the connection handlewhen a gap
* connect event is received in order to send notifications to the
* client.
*
* @params conn_handle The connection handle for the current
* connection.
*/
void
ble_svc_ans_on_gap_connect(uint16_t conn_handle)
{
ble_svc_ans_conn_handle = conn_handle;
}
/**
* Adds a new alert to the given category then notifies the client
* if the given category is valid and enabled.
*
* @param cat_flag The id for the category which should
* should be incremented and notified
* @param info_str The info string to be sent to the client
* with the notification.
*
* @return 0 on success, non-zero error code otherwise.
*/
int
ble_svc_ans_new_alert_add(uint8_t cat_id, const char * info_str)
{
uint8_t cat_bit_mask;
if (cat_id < BLE_SVC_ANS_CAT_NUM) {
cat_bit_mask = (1 << cat_id);
} else {
return BLE_HS_EINVAL;
}
if ((cat_bit_mask & ble_svc_ans_new_alert_cat) == 0) {
return BLE_HS_EINVAL;
}
ble_svc_ans_new_alert_cnt[cat_id] += 1;
return ble_svc_ans_new_alert_notify(cat_id, info_str);
}
/**
* Adds an unread alert to the given category then notifies the client
* if the given category is valid and enabled.
*
* @param cat_flag The flag for the category which should
* should be incremented and notified
*
* @return 0 on success, non-zero error code otherwise.
*/
int
ble_svc_ans_unr_alert_add(uint8_t cat_id)
{
uint8_t cat_bit_mask;
if (cat_id < BLE_SVC_ANS_CAT_NUM) {
cat_bit_mask = 1 << cat_id;
} else {
return BLE_HS_EINVAL;
}
if ((cat_bit_mask & ble_svc_ans_unr_alert_cat) == 0) {
return BLE_HS_EINVAL;
}
ble_svc_ans_unr_alert_cnt[cat_id] += 1;
return ble_svc_ans_unr_alert_notify(cat_id);
}
/**
* Send a new alert notification to the given category with the
* given info string.
*
* @param cat_id The ID of the category to send the
* notification to.
* @param info_str The info string to send with the
* notification
*
* @return 0 on success, non-zero error code otherwise.
*/
static int
ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str)
{
int info_str_len;
/* Clear notification to remove old infomation that may persist */
memset(&ble_svc_ans_new_alert_val, '\0',
BLE_SVC_ANS_NEW_ALERT_MAX_LEN);
/* Set ID and count values */
ble_svc_ans_new_alert_val[0] = cat_id;
ble_svc_ans_new_alert_val[1] = ble_svc_ans_new_alert_cnt[cat_id];
if (info_str) {
info_str_len = strlen(info_str);
if (info_str_len > BLE_SVC_ANS_INFO_STR_MAX_LEN) {
/* If info_str is longer than the max string length only
* write up to the maximum length */
memcpy(&ble_svc_ans_new_alert_val[2], info_str,
BLE_SVC_ANS_INFO_STR_MAX_LEN);
} else {
memcpy(&ble_svc_ans_new_alert_val[2], info_str, info_str_len);
}
}
return ble_gattc_notify(ble_svc_ans_conn_handle,
ble_svc_ans_new_alert_val_handle);
}
/**
* Send an unread alert notification to the given category.
*
* @param cat_id The ID of the category to send the
* notificaiton to.
*
* @return 0 on success, non-zer0 error code otherwise.
*/
static int
ble_svc_ans_unr_alert_notify(uint8_t cat_id)
{
ble_svc_ans_unr_alert_stat[0] = cat_id;
ble_svc_ans_unr_alert_stat[1] = ble_svc_ans_unr_alert_cnt[cat_id];
return ble_gattc_notify(ble_svc_ans_conn_handle,
ble_svc_ans_unr_alert_val_handle);
}
/**
* Writes the received value from a characteristic write to
* the given destination.
*/
static int
ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len,
uint16_t max_len, void *dst,
uint16_t *len)
{
uint16_t om_len;
int rc;
om_len = OS_MBUF_PKTLEN(om);
if (om_len < min_len || om_len > max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
if (rc != 0) {
return BLE_ATT_ERR_UNLIKELY;
}
return 0;
}
/**
* Initialize the ANS with initial values for enabled categories
* for new and unread alert characteristics. Bitwise or the
* catagory bitmasks to enable multiple catagories.
*
* XXX: We should technically be able to change the new alert and
* unread alert catagories when we have no active connections.
*/
void
ble_svc_ans_init(void)
{
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
rc = ble_gatts_count_cfg(ble_svc_ans_defs);
SYSINIT_PANIC_ASSERT(rc == 0);
rc = ble_gatts_add_svcs(ble_svc_ans_defs);
SYSINIT_PANIC_ASSERT(rc == 0);
ble_svc_ans_new_alert_cat = MYNEWT_VAL(BLE_SVC_ANS_NEW_ALERT_CAT);
ble_svc_ans_unr_alert_cat = MYNEWT_VAL(BLE_SVC_ANS_UNR_ALERT_CAT);
}