1 /* btp_ots.c - Bluetooth OTS Tester */
2
3 /*
4 * Copyright (c) 2024 Codecoup
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8 #include <errno.h>
9 #include <inttypes.h>
10 #include <stdint.h>
11 #include <string.h>
12
13 #include <zephyr/autoconf.h>
14 #include <zephyr/bluetooth/conn.h>
15 #include <zephyr/bluetooth/services/ots.h>
16 #include <zephyr/bluetooth/uuid.h>
17 #include <zephyr/logging/log.h>
18
19 #include <zephyr/sys/byteorder.h>
20 #include <zephyr/sys/util.h>
21 #include <sys/types.h>
22
23 #include "btp/btp.h"
24
25 #define LOG_MODULE_NAME bttester_ots
26 LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL);
27
28 #define OBJ_POOL_SIZE CONFIG_BT_OTS_MAX_OBJ_CNT
29 #define OBJ_MAX_SIZE 100
30
31 static struct object {
32 uint8_t data[OBJ_MAX_SIZE];
33 char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1];
34 bool in_use;
35 } objects[OBJ_POOL_SIZE];
36
37 struct object_creation_data {
38 struct object *object;
39 struct bt_ots_obj_size size;
40 uint32_t props;
41 };
42
43 #define OTS_OBJ_ID_TO_OBJ_IDX(id) (((id) - BT_OTS_OBJ_ID_MIN) % ARRAY_SIZE(objects))
44
45 static struct object_creation_data *object_being_created;
46
47 static struct bt_ots *ots;
48
ots_supported_commands(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)49 static uint8_t ots_supported_commands(const void *cmd, uint16_t cmd_len,
50 void *rsp, uint16_t *rsp_len)
51 {
52 struct btp_ots_read_supported_commands_rp *rp = rsp;
53
54 *rsp_len = tester_supported_commands(BTP_SERVICE_ID_OTS, rp->data);
55 *rsp_len += sizeof(*rp);
56
57 return BTP_STATUS_SUCCESS;
58 }
59
get_object(void)60 static struct object *get_object(void)
61 {
62 for (size_t i = 0; i < ARRAY_SIZE(objects); i++) {
63 if (!objects[i].in_use) {
64 objects[i].in_use = true;
65 return &objects[i];
66 }
67 }
68
69 return NULL;
70 }
71
register_object(const void * cmd,uint16_t cmd_len,void * rsp,uint16_t * rsp_len)72 static uint8_t register_object(const void *cmd, uint16_t cmd_len,
73 void *rsp, uint16_t *rsp_len)
74 {
75 const struct btp_ots_register_object_cmd *cp = cmd;
76 struct btp_ots_register_object_rp *rp = rsp;
77 struct object_creation_data obj_data;
78 struct bt_ots_obj_add_param param;
79 uint32_t supported_props = 0;
80 struct object *obj;
81 uint32_t props;
82 int err;
83
84 if ((cmd_len < sizeof(*cp)) || (cmd_len != sizeof(*cp) + cp->name_len)) {
85 return BTP_STATUS_FAILED;
86 }
87
88 if (cp->name_len == 0 || cp->name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
89 return BTP_STATUS_FAILED;
90 }
91
92 /* all supported props (execute, append, truncate not supported) */
93 BT_OTS_OBJ_SET_PROP_DELETE(supported_props);
94 BT_OTS_OBJ_SET_PROP_READ(supported_props);
95 BT_OTS_OBJ_SET_PROP_WRITE(supported_props);
96 BT_OTS_OBJ_SET_PROP_PATCH(supported_props);
97
98 props = sys_le32_to_cpu(cp->ots_props);
99 if (cp->flags & BTP_OTS_REGISTER_OBJECT_FLAGS_SKIP_UNSUPPORTED_PROPS) {
100 props &= supported_props;
101 }
102
103 obj = get_object();
104 if (!obj) {
105 return BTP_STATUS_FAILED;
106 }
107
108 (void)memset(&obj_data, 0, sizeof(obj_data));
109
110 memcpy(obj->name, cp->name, cp->name_len);
111 obj_data.object = obj;
112 obj_data.size.cur = sys_le32_to_cpu(cp->current_size);
113 obj_data.size.alloc = sys_le32_to_cpu(cp->alloc_size);
114 obj_data.props = props;
115
116 /* bt_ots_obj_add() lacks user_data so we need to use global for
117 * passing this
118 */
119 object_being_created = &obj_data;
120
121 param.size = obj_data.size.alloc;
122 param.type.uuid.type = BT_UUID_TYPE_16;
123 param.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL;
124
125 err = bt_ots_obj_add(ots, ¶m);
126 object_being_created = NULL;
127
128 if (err < 0) {
129 memset(obj, 0, sizeof(*obj));
130 return BTP_STATUS_FAILED;
131 }
132
133 rp->object_id = sys_cpu_to_le64(err);
134 *rsp_len = sizeof(*rp);
135
136 return BTP_STATUS_SUCCESS;
137 }
138
139 static const struct btp_handler ots_handlers[] = {
140 {
141 .opcode = BTP_OTS_READ_SUPPORTED_COMMANDS,
142 .index = BTP_INDEX_NONE,
143 .expect_len = 0,
144 .func = ots_supported_commands
145 },
146 {
147 .opcode = BTP_OTS_REGISTER_OBJECT,
148 .index = 0,
149 .expect_len = BTP_HANDLER_LENGTH_VARIABLE,
150 .func = register_object
151 },
152 };
153
ots_obj_created(struct bt_ots * ots,struct bt_conn * conn,uint64_t id,const struct bt_ots_obj_add_param * add_param,struct bt_ots_obj_created_desc * created_desc)154 static int ots_obj_created(struct bt_ots *ots, struct bt_conn *conn, uint64_t id,
155 const struct bt_ots_obj_add_param *add_param,
156 struct bt_ots_obj_created_desc *created_desc)
157 {
158 struct object *obj;
159
160 LOG_DBG("id=%"PRIu64" size=%u", id, add_param->size);
161
162 /* TS suggests to use OTS service UUID for testing this */
163 if (conn && bt_uuid_cmp(&add_param->type.uuid, BT_UUID_OTS) == 0) {
164 return -ENOTSUP;
165 }
166
167 if (add_param->size > OBJ_MAX_SIZE) {
168 return -ENOMEM;
169 }
170
171 if (conn || !object_being_created) {
172 uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id);
173
174 if (obj_index >= OBJ_POOL_SIZE) {
175 return -ENOMEM;
176 }
177
178 obj = &objects[obj_index];
179 if (obj->in_use) {
180 return -ENOMEM;
181 }
182
183 obj->in_use = false;
184 created_desc->name = obj->name;
185 created_desc->size.alloc = OBJ_MAX_SIZE;
186 BT_OTS_OBJ_SET_PROP_READ(created_desc->props);
187 BT_OTS_OBJ_SET_PROP_WRITE(created_desc->props);
188 BT_OTS_OBJ_SET_PROP_PATCH(created_desc->props);
189 BT_OTS_OBJ_SET_PROP_DELETE(created_desc->props);
190 } else {
191 obj = object_being_created->object;
192 created_desc->name = obj->name;
193 created_desc->size = object_being_created->size;
194 created_desc->props = object_being_created->props;
195 }
196
197 return 0;
198 }
199
ots_obj_deleted(struct bt_ots * ots,struct bt_conn * conn,uint64_t id)200 static int ots_obj_deleted(struct bt_ots *ots, struct bt_conn *conn,
201 uint64_t id)
202 {
203 uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id);
204 struct object *obj;
205
206 LOG_DBG("id=%"PRIu64, id);
207
208 if (obj_index >= OBJ_POOL_SIZE) {
209 return -ENOENT;
210 }
211
212 obj = &objects[obj_index];
213 memset(obj, 0, sizeof(*obj));
214
215 return 0;
216 }
217
ots_obj_selected(struct bt_ots * ots,struct bt_conn * conn,uint64_t id)218 static void ots_obj_selected(struct bt_ots *ots, struct bt_conn *conn,
219 uint64_t id)
220 {
221 LOG_DBG("id=%"PRIu64, id);
222 }
223
ots_obj_read(struct bt_ots * ots,struct bt_conn * conn,uint64_t id,void ** data,size_t len,off_t offset)224 static ssize_t ots_obj_read(struct bt_ots *ots, struct bt_conn *conn,
225 uint64_t id, void **data, size_t len,
226 off_t offset)
227 {
228 uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id);
229
230 LOG_DBG("id=%"PRIu64" data=%p offset=%ld len=%zu", id, data, (long)offset, len);
231
232 if (!data) {
233 return 0;
234 }
235
236 if (obj_index >= OBJ_POOL_SIZE) {
237 return -ENOENT;
238 }
239
240 *data = &objects[obj_index].data[offset];
241
242 return len;
243 }
244
ots_obj_write(struct bt_ots * ots,struct bt_conn * conn,uint64_t id,const void * data,size_t len,off_t offset,size_t rem)245 static ssize_t ots_obj_write(struct bt_ots *ots, struct bt_conn *conn,
246 uint64_t id, const void *data, size_t len,
247 off_t offset, size_t rem)
248 {
249 uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id);
250
251 LOG_DBG("id=%"PRIu64" data=%p offset=%ld len=%zu", id, data, (long)offset, len);
252
253 if (obj_index >= OBJ_POOL_SIZE) {
254 return -ENOENT;
255 }
256
257 (void)memcpy(&objects[obj_index].data[offset], data, len);
258
259 return len;
260 }
261
ots_obj_name_written(struct bt_ots * ots,struct bt_conn * conn,uint64_t id,const char * cur_name,const char * new_name)262 static void ots_obj_name_written(struct bt_ots *ots, struct bt_conn *conn,
263 uint64_t id, const char *cur_name, const char *new_name)
264 {
265 LOG_DBG("id=%"PRIu64"cur_name=%s new_name=%s", id, cur_name, new_name);
266 }
267
ots_obj_cal_checksum(struct bt_ots * ots,struct bt_conn * conn,uint64_t id,off_t offset,size_t len,void ** data)268 static int ots_obj_cal_checksum(struct bt_ots *ots, struct bt_conn *conn, uint64_t id,
269 off_t offset, size_t len, void **data)
270 {
271 uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id);
272
273 if (obj_index >= OBJ_POOL_SIZE) {
274 return -ENOENT;
275 }
276
277 *data = &objects[obj_index].data[offset];
278 return 0;
279 }
280
281 static struct bt_ots_cb ots_callbacks = {
282 .obj_created = ots_obj_created,
283 .obj_deleted = ots_obj_deleted,
284 .obj_selected = ots_obj_selected,
285 .obj_read = ots_obj_read,
286 .obj_write = ots_obj_write,
287 .obj_name_written = ots_obj_name_written,
288 .obj_cal_checksum = ots_obj_cal_checksum,
289 };
290
ots_init(void)291 static int ots_init(void)
292 {
293 int err;
294 struct bt_ots_init_param ots_init;
295
296 /* Configure OTS initialization. */
297 (void)memset(&ots_init, 0, sizeof(ots_init));
298 BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp);
299 BT_OTS_OACP_SET_FEAT_WRITE(ots_init.features.oacp);
300 BT_OTS_OACP_SET_FEAT_CREATE(ots_init.features.oacp);
301 BT_OTS_OACP_SET_FEAT_DELETE(ots_init.features.oacp);
302 BT_OTS_OACP_SET_FEAT_CHECKSUM(ots_init.features.oacp);
303 BT_OTS_OACP_SET_FEAT_PATCH(ots_init.features.oacp);
304 BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp);
305 ots_init.cb = &ots_callbacks;
306
307 /* Initialize OTS instance. */
308 err = bt_ots_init(ots, &ots_init);
309 if (err) {
310 LOG_ERR("Failed to init OTS (err:%d)\n", err);
311 return err;
312 }
313
314 return 0;
315 }
316
tester_init_ots(void)317 uint8_t tester_init_ots(void)
318 {
319 int err;
320
321 /* TODO there is no API to return OTS instance to pool */
322 if (!ots) {
323 ots = bt_ots_free_instance_get();
324 }
325
326 if (!ots) {
327 return BTP_STATUS_FAILED;
328 }
329
330 err = ots_init();
331 if (err) {
332 return BTP_STATUS_VAL(err);
333 }
334
335 tester_register_command_handlers(BTP_SERVICE_ID_OTS, ots_handlers,
336 ARRAY_SIZE(ots_handlers));
337
338 return BTP_STATUS_SUCCESS;
339 }
340
tester_unregister_ots(void)341 uint8_t tester_unregister_ots(void)
342 {
343 memset(objects, 0, sizeof(objects));
344
345 return BTP_STATUS_SUCCESS;
346 }
347