1 /** @file
2  *  @brief GATT Current Time Service
3  */
4 
5 /*
6  * Copyright (c) 2024 Croxel Inc.
7  *
8  * SPDX-License-Identifier: Apache-2.0
9  */
10 
11 #undef _POSIX_C_SOURCE
12 #define _POSIX_C_SOURCE 200809L /* To get gmtime_r()'s prototype */
13 
14 #ifdef CONFIG_BT_CTS_HELPER_API
15 #include <time.h>
16 #include <zephyr/sys/timeutil.h>
17 #endif
18 
19 #include <stdbool.h>
20 #include <zephyr/sys/byteorder.h>
21 
22 #include <zephyr/bluetooth/conn.h>
23 #include <zephyr/bluetooth/gatt.h>
24 #include <zephyr/bluetooth/uuid.h>
25 #include <zephyr/bluetooth/services/cts.h>
26 
27 #include <zephyr/logging/log.h>
28 LOG_MODULE_REGISTER(cts, CONFIG_BT_CTS_LOG_LEVEL);
29 
30 #define BT_CTS_ATT_ERR_VALUES_IGNORED 0x80
31 #define BT_CTS_FRACTION_256_MAX_VALUE 255
32 
33 static const struct bt_cts_cb *cts_cb;
34 
35 #ifdef CONFIG_BT_CTS_HELPER_API
36 
bt_cts_time_to_unix_ms(const struct bt_cts_time_format * ct_time,int64_t * unix_ms)37 int bt_cts_time_to_unix_ms(const struct bt_cts_time_format *ct_time, int64_t *unix_ms)
38 {
39 	struct tm date_time;
40 	/* fill date time structure */
41 	date_time.tm_year = sys_le16_to_cpu(ct_time->year); /* year (little endian) */
42 	date_time.tm_year -= TIME_UTILS_BASE_YEAR;
43 	date_time.tm_mon = ct_time->mon - 1;   /* month start from 1, but need from 0 */
44 	date_time.tm_mday = ct_time->mday;     /* day of month */
45 	date_time.tm_hour = ct_time->hours;    /* hours of day */
46 	date_time.tm_min = ct_time->min;       /* minute of hour */
47 	date_time.tm_sec = ct_time->sec;       /* seconds of minute */
48 	date_time.tm_wday = ct_time->wday % 7; /* for sundays convert to 0, else keep same */
49 
50 	LOG_DBG("CTS Write Time: %d/%d/%d %d:%d:%d", date_time.tm_year, date_time.tm_mon,
51 		date_time.tm_mday, date_time.tm_hour, date_time.tm_min, date_time.tm_sec);
52 	/* get unit timestamp from datetime */
53 	(*unix_ms) = timeutil_timegm64(&date_time);
54 	if ((*unix_ms) == ((time_t)-1)) {
55 		return -EOVERFLOW;
56 	}
57 	LOG_DBG("CTS Write Unix: %lld", (*unix_ms));
58 	(*unix_ms) *= MSEC_PER_SEC;
59 	/* add fraction 256 part*/
60 	(*unix_ms) += ((ct_time->fractions256 * MSEC_PER_SEC) / BT_CTS_FRACTION_256_MAX_VALUE);
61 
62 	return 0;
63 }
64 
bt_cts_time_from_unix_ms(struct bt_cts_time_format * ct_time,int64_t unix_ms)65 int bt_cts_time_from_unix_ms(struct bt_cts_time_format *ct_time, int64_t unix_ms)
66 {
67 	struct tm date_time;
68 	time_t unix_ts = unix_ms / MSEC_PER_SEC;
69 
70 	/* 'Fractions 256 part of 'Exact Time 256' */
71 	unix_ms %= MSEC_PER_SEC;
72 	unix_ms *= BT_CTS_FRACTION_256_MAX_VALUE;
73 	unix_ms /= MSEC_PER_SEC;
74 	ct_time->fractions256 = unix_ms;
75 
76 	/* convert unix_ts to */
77 	LOG_DBG("CTS Read Unix: %lld", unix_ts);
78 	/* generate date time from unix timestamp */
79 	if (gmtime_r(&unix_ts, &date_time) == NULL) {
80 		return -EOVERFLOW;
81 	}
82 	date_time.tm_year += TIME_UTILS_BASE_YEAR;
83 
84 	LOG_DBG("CTS Read Time: %d/%d/%d %d:%d:%d", date_time.tm_year, date_time.tm_mon,
85 		date_time.tm_mday, date_time.tm_hour, date_time.tm_min, date_time.tm_sec);
86 
87 	/* 'Exact Time 256' contains 'Day Date Time' which contains
88 	 * 'Date Time' - characteristic contains fields for:
89 	 * year, month, day, hours, minutes and seconds.
90 	 */
91 	ct_time->year = sys_cpu_to_le16(date_time.tm_year);
92 	ct_time->mon = date_time.tm_mon + 1; /* months starting from 1 */
93 	ct_time->mday = date_time.tm_mday;   /* Day of month */
94 	ct_time->hours = date_time.tm_hour;  /* hours */
95 	ct_time->min = date_time.tm_min;     /* minutes */
96 	ct_time->sec = date_time.tm_sec;     /* seconds */
97 	/* day of week starting from 1-monday, 7-sunday */
98 	ct_time->wday = date_time.tm_wday;
99 	if (ct_time->wday == 0) {
100 		ct_time->wday = 7; /* sunday is represented as 7 */
101 	}
102 	return 0;
103 }
104 
105 #endif /* CONFIG_BT_CTS_HELPER_API */
106 
ct_ccc_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)107 static void ct_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
108 {
109 	bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
110 
111 	LOG_INF("CTS Notifications %s", notif_enabled ? "enabled" : "disabled");
112 
113 	if (cts_cb->notification_changed) {
114 		cts_cb->notification_changed(notif_enabled);
115 	}
116 }
117 
read_ct(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)118 static ssize_t read_ct(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
119 		       uint16_t len, uint16_t offset)
120 {
121 	int err;
122 	struct bt_cts_time_format ct_time;
123 
124 	err = cts_cb->fill_current_cts_time(&ct_time);
125 	ct_time.reason = BT_CTS_UPDATE_REASON_UNKNOWN;
126 
127 	if (!err) {
128 		return bt_gatt_attr_read(conn, attr, buf, len, offset, &ct_time, sizeof(ct_time));
129 	} else {
130 		return BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE);
131 	}
132 }
133 
write_ct(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)134 static ssize_t write_ct(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
135 			uint16_t len, uint16_t offset, uint8_t flags)
136 {
137 	int err;
138 	struct bt_cts_time_format ct_time;
139 
140 	if (cts_cb->cts_time_write == NULL) {
141 		return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
142 	}
143 
144 	if ((offset != 0) || (offset + len != sizeof(ct_time))) {
145 		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
146 	}
147 
148 	memcpy(&ct_time, buf, sizeof(ct_time));
149 	err = cts_cb->cts_time_write(&ct_time);
150 	if (err) {
151 		return BT_GATT_ERR(BT_CTS_ATT_ERR_VALUES_IGNORED);
152 	}
153 
154 	err = bt_cts_send_notification(BT_CTS_UPDATE_REASON_MANUAL);
155 	if (err) {
156 		return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
157 	}
158 
159 	return len;
160 }
161 
162 /* Current Time Service Declaration */
163 BT_GATT_SERVICE_DEFINE(cts_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_CTS),
164 		       BT_GATT_CHARACTERISTIC(BT_UUID_CTS_CURRENT_TIME,
165 					      BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
166 						      BT_GATT_CHRC_NOTIFY,
167 					      BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, read_ct,
168 					      write_ct, NULL),
169 		       BT_GATT_CCC(ct_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));
170 
bt_cts_init(const struct bt_cts_cb * cb)171 int bt_cts_init(const struct bt_cts_cb *cb)
172 {
173 	__ASSERT(cb == NULL, "Current Time service need valid `struct bt_cts_cb` callback");
174 	__ASSERT(cb->fill_current_cts_time == NULL,
175 		 "`fill_current_cts_time` callback api is required for functioning of CTS");
176 	if (!cb || !cb->fill_current_cts_time) {
177 		return -EINVAL;
178 	}
179 	cts_cb = cb;
180 	return 0;
181 }
182 
bt_cts_send_notification(enum bt_cts_update_reason reason)183 int bt_cts_send_notification(enum bt_cts_update_reason reason)
184 {
185 	int err;
186 	struct bt_cts_time_format ct_time;
187 
188 	err = cts_cb->fill_current_cts_time(&ct_time);
189 	ct_time.reason = reason;
190 	if (err) {
191 		return err;
192 	}
193 	return bt_gatt_notify(NULL, &cts_svc.attrs[1], &ct_time, sizeof(ct_time));
194 }
195