From 70801626abd7043a3ef8778e99cf78c8cdfc4860 Mon Sep 17 00:00:00 2001 From: Roshan Bangar Date: Wed, 30 Aug 2023 20:39:04 +0530 Subject: [PATCH] Added support for Current Time Service --- .../cts/include/services/cts/ble_svc_cts.h | 160 ++++++++++ nimble/host/services/cts/pkg.yml | 34 +++ nimble/host/services/cts/src/ble_svc_cts.c | 280 ++++++++++++++++++ nimble/host/services/cts/syscfg.yml | 18 ++ 4 files changed, 492 insertions(+) create mode 100644 nimble/host/services/cts/include/services/cts/ble_svc_cts.h create mode 100644 nimble/host/services/cts/pkg.yml create mode 100644 nimble/host/services/cts/src/ble_svc_cts.c create mode 100644 nimble/host/services/cts/syscfg.yml diff --git a/nimble/host/services/cts/include/services/cts/ble_svc_cts.h b/nimble/host/services/cts/include/services/cts/ble_svc_cts.h new file mode 100644 index 000000000..cf62f912d --- /dev/null +++ b/nimble/host/services/cts/include/services/cts/ble_svc_cts.h @@ -0,0 +1,160 @@ +/** + * 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. + */ + +#ifndef H_BLE_SVC_CTS_ +#define H_BLE_SVC_CTS_ + +#define BLE_SVC_CTS_ERR_DATA_FIELD_IGNORED 0x80 +/* 16 Bit Current Time Service UUID */ +#define BLE_SVC_CTS_UUID16 0x1805 + +/* 16 Bit Current Time Service Characteristics UUIDs */ +#define BLE_SVC_CTS_CHR_UUID16_CURRENT_TIME 0x2A2B /* read write notify */ +#define BLE_SVC_CTS_CHR_UUID16_LOCAL_TIME_INFO 0x2A0F /* read write */ +#define BLE_SVC_CTS_CHR_UUID16_REF_TIME_INFO 0x2A14 /* read */ + +/* adjust reason masks */ +#define MANUAL_TIME_UPDATE_MASK 1 +#define EXTERNAL_REFERENCE_TIME_UPDATE_MASK 2 +#define CHANGE_OF_TIME_ZONE_MASK 4 +#define CHANGE_OF_DST_MASK 8 + +/* Time Source Values */ +#define TIME_SOURCE_UNKNOWN 0 +#define TIME_SOURCE_NETWORK_TIME_PROTOCOL 1 +#define TIME_SOURCE_GPS 2 +#define TIME_SOURCE_RADIO_TIME_SIGNAL 3 +#define TIME_SOURCE_MANUAL 4 +#define TIME_SOURCE_ATOMIC_CLOCK 5 +#define TIME_SOURCE_CELLULAR_NETWORK 6 + +/* DST VALUES */ +#define TIME_STANDARD 0 +#define TIME_HALF_AN_HOUR_DAYLIGHT 2 +#define TIME_DAYLIGHT 4 +#define TIME_DOUBLE_DAYLIGHT 8 +#define TIME_DST_OFFSET_UNKNOWN 255 + +/* current time characteristic */ +struct date_time { + /** year. + * valid range : 1582 - 9999 + * 0 means year not known + */ + uint16_t year; + /** month. + * valid range : 1(January) - 12(December) + * 0 means month not known + */ + uint8_t month; + /** day. + * valid range : 1 - 31 + * 0 means day not known + */ + uint8_t day; + /** hours. + * valid range : 0 - 23 + */ + uint8_t hours; + /** minutes. + * valid range : 0 - 59 + */ + uint8_t minutes; + /** seconds. + * valid range : 0 - 59 + */ + uint8_t seconds; +}__attribute__((packed)); + +struct day_date_time { + struct date_time d_t; + /** day_of_week. + valid range : 1(Monday) - 7(Sunday) + * 0 means day of week not known + */ + uint8_t day_of_week; +}__attribute__((packed)); + +struct exact_time_256 { + struct day_date_time d_d_t; + /** fractions_256. + * the number of 1 / 256 fractions of second + * valid range : 0 - 255 + */ + uint8_t fractions_256; +}__attribute__((packed)); + +struct ble_svc_cts_curr_time { + struct exact_time_256 et_256; + uint8_t adjust_reason; +}__attribute__((packed)); + +/* local time information characteristic */ +struct ble_svc_cts_local_time_info { + /** timezone. + * valid range : -48 to 56 + * -128 means timezone offset not known + */ + int8_t timezone; + /** dst_offset. + * allowed values : 0, 2, 4, 8, 255 + */ + uint8_t dst_offset; +}__attribute__((packed)); + +/* reference time information */ +struct ble_svc_cts_reference_time_info { + /** time_source. + * valid range : 0 - 253 + * 255 means not known + */ + uint8_t time_source; + uint8_t time_accuracy; + /** days_since_update. + * valid range : 0 - 254 + */ + uint8_t days_since_update; + /** hours_since_update. + * valid range : 0 - 23 + */ + uint8_t hours_since_update; +}__attribute__((packed)); + +/* callback to be called to get current time */ +typedef int ble_svc_cts_fetch_current_time_fn(struct ble_svc_cts_curr_time *); +typedef int ble_svc_cts_fetch_local_time_info_fn(struct ble_svc_cts_local_time_info *); +typedef int ble_svc_cts_fetch_ref_time_info_fn(struct ble_svc_cts_reference_time_info *); +typedef int ble_svc_cts_set_local_time_info_fn(struct ble_svc_cts_local_time_info); +typedef int ble_svc_cts_set_current_time_fn(struct ble_svc_cts_curr_time); + +/* CTS configuration to be filled at init */ +struct ble_svc_cts_cfg { + ble_svc_cts_fetch_current_time_fn *fetch_time_cb; + ble_svc_cts_fetch_local_time_info_fn *local_time_info_cb; + ble_svc_cts_fetch_ref_time_info_fn *ref_time_info_cb; + ble_svc_cts_set_current_time_fn *set_time_cb; + ble_svc_cts_set_local_time_info_fn *set_local_time_info_cb; +}; + +void +ble_svc_cts_init(struct ble_svc_cts_cfg cfg); +void +ble_svc_cts_time_updated(void); + +#endif diff --git a/nimble/host/services/cts/pkg.yml b/nimble/host/services/cts/pkg.yml new file mode 100644 index 000000000..295e0f4bd --- /dev/null +++ b/nimble/host/services/cts/pkg.yml @@ -0,0 +1,34 @@ +# +# 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. +# + +pkg.name: nimble/host/services/cts +pkg.description: Current Time Service +pkg.author: "Apache Mynewt " +pkg.homepage: "http://mynewt.apache.org/" +pkg.keywords: + - ble + - bluetooth + - cts + - nimble + +pkg.deps: + - nimble/host + +pkg.init: + ble_svc_cts_init: 'MYNEWT_VAL(BLE_SVC_CTS_SYSINIT_STAGE)' diff --git a/nimble/host/services/cts/src/ble_svc_cts.c b/nimble/host/services/cts/src/ble_svc_cts.c new file mode 100644 index 000000000..63304cbe6 --- /dev/null +++ b/nimble/host/services/cts/src/ble_svc_cts.c @@ -0,0 +1,280 @@ +/** + * 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 +#include + +#include "sysinit/sysinit.h" +#include "syscfg/syscfg.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "services/cts/ble_svc_cts.h" +#include "time.h" +#include + + +struct ble_svc_cts_cfg cts_cfg = {0}; + +/* characteristic values */ +struct ble_svc_cts_curr_time current_local_time_val; +struct ble_svc_cts_local_time_info local_time_info_val; +struct ble_svc_cts_reference_time_info ref_time_info_val; + +/* Characteristic value handles */ +uint16_t ble_svc_cts_curr_time_handle; +uint16_t ble_svc_cts_local_time_info_handle; +uint16_t ble_svc_cts_ref_time_handle; + +/* Access function */ +static int +ble_svc_cts_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static const struct ble_gatt_svc_def ble_svc_cts_defs[] = { + { + /*** Current Time Service. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(BLE_SVC_CTS_UUID16), + .characteristics = (struct ble_gatt_chr_def[]) { { + /*** Current Time characteristic */ + .uuid = BLE_UUID16_DECLARE(BLE_SVC_CTS_CHR_UUID16_CURRENT_TIME), + .access_cb = ble_svc_cts_access, + .val_handle = &ble_svc_cts_curr_time_handle, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | /* optional */ + BLE_GATT_CHR_F_NOTIFY + }, { + /*** Local info characteristic */ + .uuid = BLE_UUID16_DECLARE(BLE_SVC_CTS_CHR_UUID16_LOCAL_TIME_INFO), + .access_cb = ble_svc_cts_access, + .val_handle = &ble_svc_cts_local_time_info_handle, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE + }, { + /*** Reference time info Characteristic */ + .uuid = BLE_UUID16_DECLARE(BLE_SVC_CTS_CHR_UUID16_REF_TIME_INFO), + .access_cb = ble_svc_cts_access, + .val_handle = &ble_svc_cts_ref_time_handle, + .flags = BLE_GATT_CHR_F_READ + }, { + 0, /* No more characteristics in this service. */ + } }, + }, + + { + 0, /* No more services. */ + }, +}; + +static int +ble_svc_cts_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; +} + +int ble_svc_cts_curr_time_validate(struct ble_svc_cts_curr_time curr_time) { + if(curr_time.et_256.d_d_t.day_of_week > 7 || + (curr_time.et_256.d_d_t.d_t.year < 1582 && + curr_time.et_256.d_d_t.d_t.year != 0 ) || + curr_time.et_256.d_d_t.d_t.year > 9999 || + curr_time.et_256.d_d_t.d_t.month > 12 || + curr_time.et_256.d_d_t.d_t.day > 31 || + curr_time.et_256.d_d_t.d_t.hours > 23 || + curr_time.et_256.d_d_t.d_t.minutes > 59 || + curr_time.et_256.d_d_t.d_t.seconds > 59 || + curr_time.adjust_reason >> 4 > 0 + ) { + return BLE_SVC_CTS_ERR_DATA_FIELD_IGNORED; + } + return 0; +} + +int ble_svc_cts_local_time_info_validate(struct ble_svc_cts_local_time_info local_time_info) { + if((local_time_info.timezone < -48 && + local_time_info.timezone != -128) || + local_time_info.timezone > 56 || + (local_time_info.dst_offset != 0 && + local_time_info.dst_offset != 2 && + local_time_info.dst_offset != 4 && + local_time_info.dst_offset != 8 && + local_time_info.dst_offset != 255) + ) { + return BLE_SVC_CTS_ERR_DATA_FIELD_IGNORED; + } + return 0; +} +/** + * CTS access function + */ +static int +ble_svc_cts_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg) +{ + uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid); + struct ble_svc_cts_local_time_info local_time_info; + struct ble_svc_cts_curr_time curr_time; + int rc = 0; + + switch (uuid16) { + case BLE_SVC_CTS_CHR_UUID16_CURRENT_TIME: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR); + switch(ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + if(cts_cfg.fetch_time_cb != NULL) { + rc = cts_cfg.fetch_time_cb(¤t_local_time_val); + if(rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + } + else { + memset(¤t_local_time_val, 0, sizeof(current_local_time_val)); + } + rc = os_mbuf_append(ctxt->om, ¤t_local_time_val, + sizeof current_local_time_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = ble_hs_mbuf_to_flat(ctxt->om, &curr_time, sizeof curr_time, NULL); + assert(rc == 0); + rc = ble_svc_cts_curr_time_validate(curr_time); + if(rc != 0) { + return rc; + } + rc = ble_svc_cts_chr_write(ctxt->om, 0, sizeof current_local_time_val, ¤t_local_time_val, NULL); + if(rc != 0) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if(cts_cfg.set_time_cb == NULL) { + return BLE_ATT_ERR_UNLIKELY; + } + + rc = cts_cfg.set_time_cb(curr_time); + if(rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + /* schedule notifications for subscribed peers */ + if(rc == 0) { + ble_gatts_chr_updated(attr_handle); + } + return 0; + } + + case BLE_SVC_CTS_CHR_UUID16_LOCAL_TIME_INFO: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR); + switch(ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + if(cts_cfg.local_time_info_cb != NULL) { + rc = cts_cfg.local_time_info_cb(&local_time_info_val); + if(rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + } + else { + memset(&local_time_info_val, 0, sizeof(local_time_info_val)); /* 0 is unknown */ + } + rc = os_mbuf_append(ctxt->om, &local_time_info_val, + sizeof local_time_info_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = ble_hs_mbuf_to_flat(ctxt->om, &local_time_info, 2, NULL); + assert(rc == 0); + rc = ble_svc_cts_local_time_info_validate(local_time_info); + if(rc != 0) { + return rc; + } + rc = ble_svc_cts_chr_write(ctxt->om, 0, sizeof local_time_info_val, &local_time_info_val, NULL); + if(rc != 0) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + + if(cts_cfg.set_local_time_info_cb == NULL) { + return BLE_ATT_ERR_UNLIKELY; + } + + rc = cts_cfg.set_local_time_info_cb(local_time_info); + if(rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + if(rc == 0) { + /* set the adjust reason mask */ + /* notify the connected clients about the change in timezone and time */ + ble_gatts_chr_updated(ble_svc_cts_curr_time_handle); + } + return 0; + } + case BLE_SVC_CTS_CHR_UUID16_REF_TIME_INFO: + assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); + if(cts_cfg.ref_time_info_cb != NULL) { + rc = cts_cfg.ref_time_info_cb(&ref_time_info_val); + if(rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + } + rc = os_mbuf_append(ctxt->om, &ref_time_info_val, + sizeof ref_time_info_val); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } +} + +/* this function is invoked to make the service aware that the time is updated */ +void ble_svc_cts_time_updated() { + /* notify the bonded clients */ + ble_gatts_chr_updated(ble_svc_cts_curr_time_handle); +} + +/** + * Initialize the Current Time Service. + */ +void +ble_svc_cts_init(struct ble_svc_cts_cfg cfg) +{ + int rc; + + /* Ensure this function only gets called by sysinit. */ + SYSINIT_ASSERT_ACTIVE(); + memcpy(&cts_cfg, &cfg, sizeof cfg); + + rc = ble_gatts_count_cfg(ble_svc_cts_defs); + SYSINIT_PANIC_ASSERT(rc == 0); + + rc = ble_gatts_add_svcs(ble_svc_cts_defs); + SYSINIT_PANIC_ASSERT(rc == 0); +} diff --git a/nimble/host/services/cts/syscfg.yml b/nimble/host/services/cts/syscfg.yml new file mode 100644 index 000000000..6f55bee46 --- /dev/null +++ b/nimble/host/services/cts/syscfg.yml @@ -0,0 +1,18 @@ +# 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. + +syscfg.defs: