1 /*
2  * Copyright (c) 2022 Codecoup
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <stdint.h>
9 #include <zephyr/sys/check.h>
10 #include <zephyr/logging/log.h>
11 
12 LOG_MODULE_REGISTER(bt_ias_client, CONFIG_BT_IAS_CLIENT_LOG_LEVEL);
13 
14 #include <zephyr/bluetooth/gatt.h>
15 #include <zephyr/bluetooth/conn.h>
16 #include <zephyr/bluetooth/services/ias.h>
17 
18 enum {
19 	IAS_DISCOVER_IN_PROGRESS,
20 
21 	IAS_NUM_FLAGS, /* keep as last */
22 };
23 
24 struct bt_ias_client {
25 	/* Handle for alert writes */
26 	uint16_t alert_level_handle;
27 
28 	/** Internal flags **/
29 	ATOMIC_DEFINE(flags, IAS_NUM_FLAGS);
30 
31 	/* Gatt discover procedure parameters */
32 	struct bt_gatt_discover_params discover;
33 };
34 
35 static const struct bt_uuid *alert_lvl_uuid = BT_UUID_ALERT_LEVEL;
36 static const struct bt_uuid *ias_uuid = BT_UUID_IAS;
37 
38 static const struct bt_ias_client_cb *ias_client_cb;
39 static struct bt_ias_client client_list[CONFIG_BT_MAX_CONN];
40 
client_by_conn(struct bt_conn * conn)41 static struct bt_ias_client *client_by_conn(struct bt_conn *conn)
42 {
43 	return &client_list[bt_conn_index(conn)];
44 }
45 
client_cleanup(struct bt_ias_client * ias_client)46 static void client_cleanup(struct bt_ias_client *ias_client)
47 {
48 	(void)memset(ias_client, 0, sizeof(*ias_client));
49 }
50 
discover_complete(struct bt_conn * conn,int err)51 static void discover_complete(struct bt_conn *conn, int err)
52 {
53 	LOG_DBG("conn %p", (void *)conn);
54 
55 	if (err) {
56 		client_cleanup(client_by_conn(conn));
57 		LOG_DBG("Discover failed (err %d\n)", err);
58 	}
59 
60 	if (ias_client_cb != NULL && ias_client_cb->discover != NULL) {
61 		ias_client_cb->discover(conn, err);
62 	}
63 }
64 
bt_ias_client_alert_write(struct bt_conn * conn,enum bt_ias_alert_lvl lvl)65 int bt_ias_client_alert_write(struct bt_conn *conn, enum bt_ias_alert_lvl lvl)
66 {
67 	int err;
68 	uint8_t lvl_u8;
69 
70 	CHECKIF(conn == NULL) {
71 		return -ENOTCONN;
72 	}
73 
74 	if (client_by_conn(conn)->alert_level_handle == 0) {
75 		return -EINVAL;
76 	}
77 
78 	lvl_u8 = (uint8_t)lvl;
79 
80 	if (lvl_u8 != BT_IAS_ALERT_LVL_NO_ALERT &&
81 	    lvl_u8 != BT_IAS_ALERT_LVL_MILD_ALERT &&
82 	    lvl_u8 != BT_IAS_ALERT_LVL_HIGH_ALERT) {
83 		LOG_ERR("Invalid alert value: %u", lvl_u8);
84 		return -EINVAL;
85 	}
86 
87 	err = bt_gatt_write_without_response(conn,
88 					     client_by_conn(conn)->alert_level_handle,
89 					     &lvl_u8, sizeof(lvl_u8), false);
90 	if (err < 0) {
91 		LOG_ERR("IAS client level %d write failed: %d", lvl, err);
92 	}
93 
94 	return err;
95 }
96 
bt_ias_alert_lvl_disc_cb(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * discover)97 static uint8_t bt_ias_alert_lvl_disc_cb(struct bt_conn *conn,
98 					const struct bt_gatt_attr *attr,
99 					struct bt_gatt_discover_params *discover)
100 {
101 	const struct bt_gatt_chrc *chrc;
102 
103 	atomic_clear_bit(client_by_conn(conn)->flags, IAS_DISCOVER_IN_PROGRESS);
104 
105 	if (attr == NULL) {
106 		discover_complete(conn, -ENOENT);
107 
108 		return BT_GATT_ITER_STOP;
109 	}
110 
111 	chrc = (struct bt_gatt_chrc *)attr->user_data;
112 
113 	client_by_conn(conn)->alert_level_handle = chrc->value_handle;
114 	discover_complete(conn, 0);
115 
116 	return BT_GATT_ITER_STOP;
117 }
118 
bt_ias_prim_disc_cb(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * discover)119 static uint8_t bt_ias_prim_disc_cb(struct bt_conn *conn,
120 				   const struct bt_gatt_attr *attr,
121 				   struct bt_gatt_discover_params *discover)
122 {
123 	int err;
124 	const struct bt_gatt_service_val *data;
125 	struct bt_ias_client *client = client_by_conn(conn);
126 
127 	if (!attr) {
128 		discover_complete(conn, -ENOENT);
129 
130 		return BT_GATT_ITER_STOP;
131 	}
132 
133 	data = attr->user_data;
134 
135 	client->discover.uuid = alert_lvl_uuid;
136 	client->discover.start_handle = attr->handle + 1;
137 	client->discover.end_handle = data->end_handle;
138 	client->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
139 	client->discover.func = bt_ias_alert_lvl_disc_cb;
140 
141 	err = bt_gatt_discover(conn, &client->discover);
142 	if (err) {
143 		discover_complete(conn, err);
144 	}
145 
146 	return BT_GATT_ITER_STOP;
147 }
148 
bt_ias_discover(struct bt_conn * conn)149 int bt_ias_discover(struct bt_conn *conn)
150 {
151 	int err;
152 	struct bt_ias_client *client = client_by_conn(conn);
153 
154 	CHECKIF(!conn || !ias_client_cb || !ias_client_cb->discover) {
155 		return -EINVAL;
156 	}
157 
158 	if (atomic_test_bit(client->flags, IAS_DISCOVER_IN_PROGRESS)) {
159 		return -EBUSY;
160 	}
161 
162 	client_cleanup(client);
163 	atomic_set_bit(client->flags, IAS_DISCOVER_IN_PROGRESS);
164 
165 	client->discover.uuid = ias_uuid;
166 	client->discover.func = bt_ias_prim_disc_cb;
167 	client->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
168 	client->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
169 	client->discover.type = BT_GATT_DISCOVER_PRIMARY;
170 
171 	err = bt_gatt_discover(conn, &client->discover);
172 	if (err < 0) {
173 		discover_complete(conn, err);
174 	}
175 
176 	return err;
177 }
178 
bt_ias_client_cb_register(const struct bt_ias_client_cb * cb)179 int bt_ias_client_cb_register(const struct bt_ias_client_cb *cb)
180 {
181 	CHECKIF(!cb) {
182 		return -EINVAL;
183 	}
184 
185 	CHECKIF(cb->discover == NULL) {
186 		return -EINVAL;
187 	}
188 
189 	CHECKIF(ias_client_cb) {
190 		return -EALREADY;
191 	}
192 
193 	ias_client_cb = cb;
194 
195 	return 0;
196 }
197