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