1 /*
2 * Copyright (c) 2017 Linaro Limited
3 * Copyright (c) 2018-2019 Foundries.io
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define LOG_MODULE_NAME net_lwm2m_obj_firmware
9 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
10
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
13
14 #include <string.h>
15 #include <stdio.h>
16 #include <zephyr/init.h>
17
18 #include "lwm2m_object.h"
19 #include "lwm2m_engine.h"
20
21 #define FIRMWARE_VERSION_MAJOR 1
22 #define FIRMWARE_VERSION_MINOR 0
23
24 #if defined(CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT_MULTIPLE)
25 #define MAX_INSTANCE_COUNT CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_INSTANCE_COUNT
26 #else
27 #define MAX_INSTANCE_COUNT 1
28 #endif
29
30 /* Firmware resource IDs */
31 #define FIRMWARE_PACKAGE_ID 0
32 #define FIRMWARE_PACKAGE_URI_ID 1
33 #define FIRMWARE_UPDATE_ID 2
34 #define FIRMWARE_STATE_ID 3
35 #define FIRMWARE_UPDATE_RESULT_ID 5
36 #define FIRMWARE_PACKAGE_NAME_ID 6
37 #define FIRMWARE_PACKAGE_VERSION_ID 7
38 #define FIRMWARE_UPDATE_PROTO_SUPPORT_ID 8
39 #define FIRMWARE_UPDATE_DELIV_METHOD_ID 9
40
41 #define FIRMWARE_MAX_ID 10
42
43 #define DELIVERY_METHOD_PULL_ONLY 0
44 #define DELIVERY_METHOD_PUSH_ONLY 1
45 #define DELIVERY_METHOD_BOTH 2
46
47 #define PACKAGE_URI_LEN 255
48
49 /*
50 * Calculate resource instances as follows:
51 * start with FIRMWARE_MAX_ID
52 * subtract EXEC resources (1)
53 */
54 #define RESOURCE_INSTANCE_COUNT (FIRMWARE_MAX_ID - 1)
55
56 /* resource state variables */
57 static uint8_t update_state[MAX_INSTANCE_COUNT];
58 static uint8_t update_result[MAX_INSTANCE_COUNT];
59 static uint8_t delivery_method[MAX_INSTANCE_COUNT];
60 static char package_uri[MAX_INSTANCE_COUNT][PACKAGE_URI_LEN];
61
62 /* A varying number of firmware object exists */
63 static struct lwm2m_engine_obj firmware;
64 static struct lwm2m_engine_obj_field fields[] = {
65 OBJ_FIELD_DATA(FIRMWARE_PACKAGE_ID, W, OPAQUE),
66 OBJ_FIELD_DATA(FIRMWARE_PACKAGE_URI_ID, RW, STRING),
67 OBJ_FIELD_EXECUTE(FIRMWARE_UPDATE_ID),
68 OBJ_FIELD_DATA(FIRMWARE_STATE_ID, R, U8),
69 OBJ_FIELD_DATA(FIRMWARE_UPDATE_RESULT_ID, R, U8),
70 OBJ_FIELD_DATA(FIRMWARE_PACKAGE_NAME_ID, R_OPT, STRING),
71 OBJ_FIELD_DATA(FIRMWARE_PACKAGE_VERSION_ID, R_OPT, STRING),
72 OBJ_FIELD_DATA(FIRMWARE_UPDATE_PROTO_SUPPORT_ID, R_OPT, U8),
73 OBJ_FIELD_DATA(FIRMWARE_UPDATE_DELIV_METHOD_ID, R, U8)
74 };
75
76 static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT];
77 static struct lwm2m_engine_res res[MAX_INSTANCE_COUNT][FIRMWARE_MAX_ID];
78 static struct lwm2m_engine_res_inst res_inst[MAX_INSTANCE_COUNT][RESOURCE_INSTANCE_COUNT];
79
80 static lwm2m_engine_set_data_cb_t write_cb[MAX_INSTANCE_COUNT];
81 static lwm2m_engine_execute_cb_t update_cb[MAX_INSTANCE_COUNT];
82 static lwm2m_engine_user_cb_t cancel_cb[MAX_INSTANCE_COUNT];
83
84 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
85 extern int lwm2m_firmware_start_transfer(uint16_t obj_inst_id, char *package_uri);
86 #endif
87
lwm2m_firmware_get_update_state_inst(uint16_t obj_inst_id)88 uint8_t lwm2m_firmware_get_update_state_inst(uint16_t obj_inst_id)
89 {
90 return update_state[obj_inst_id];
91 }
92
lwm2m_firmware_get_update_state(void)93 uint8_t lwm2m_firmware_get_update_state(void)
94 {
95 return lwm2m_firmware_get_update_state_inst(0);
96 }
97
lwm2m_firmware_set_update_state_inst(uint16_t obj_inst_id,uint8_t state)98 void lwm2m_firmware_set_update_state_inst(uint16_t obj_inst_id, uint8_t state)
99 {
100 bool error = false;
101 struct lwm2m_obj_path path = LWM2M_OBJ(LWM2M_OBJECT_FIRMWARE_ID, obj_inst_id,
102 FIRMWARE_UPDATE_RESULT_ID);
103
104 lwm2m_registry_lock();
105 /* Check LWM2M SPEC appendix E.6.1 */
106 switch (state) {
107 case STATE_DOWNLOADING:
108 if (update_state[obj_inst_id] == STATE_IDLE) {
109 lwm2m_set_u8(&path, RESULT_DEFAULT);
110 } else {
111 error = true;
112 }
113 break;
114 case STATE_DOWNLOADED:
115 if (update_state[obj_inst_id] == STATE_DOWNLOADING) {
116 lwm2m_set_u8(&path, RESULT_DEFAULT);
117 } else if (update_state[obj_inst_id] == STATE_UPDATING) {
118 lwm2m_set_u8(&path, RESULT_UPDATE_FAILED);
119 } else {
120 error = true;
121 }
122 break;
123 case STATE_UPDATING:
124 if (update_state[obj_inst_id] != STATE_DOWNLOADED) {
125 error = true;
126 }
127 break;
128 case STATE_IDLE:
129 break;
130 default:
131 LOG_ERR("Unhandled state: %u", state);
132 lwm2m_registry_unlock();
133 return;
134 }
135
136 if (error) {
137 LOG_ERR("Invalid state transition: %u -> %u",
138 update_state[obj_inst_id], state);
139 }
140
141 path.res_id = FIRMWARE_STATE_ID;
142
143 lwm2m_set_u8(&path, state);
144 lwm2m_registry_unlock();
145
146 LOG_DBG("Update state = %d", state);
147 }
148
lwm2m_firmware_set_update_state(uint8_t state)149 void lwm2m_firmware_set_update_state(uint8_t state)
150 {
151 lwm2m_firmware_set_update_state_inst(0, state);
152 }
153
lwm2m_firmware_get_update_result_inst(uint16_t obj_inst_id)154 uint8_t lwm2m_firmware_get_update_result_inst(uint16_t obj_inst_id)
155 {
156 return update_result[obj_inst_id];
157 }
158
lwm2m_firmware_get_update_result(void)159 uint8_t lwm2m_firmware_get_update_result(void)
160 {
161 return lwm2m_firmware_get_update_result_inst(0);
162 }
163
lwm2m_firmware_set_update_result_inst(uint16_t obj_inst_id,uint8_t result)164 void lwm2m_firmware_set_update_result_inst(uint16_t obj_inst_id, uint8_t result)
165 {
166 uint8_t state;
167 bool error = false;
168 struct lwm2m_obj_path path = LWM2M_OBJ(LWM2M_OBJECT_FIRMWARE_ID, obj_inst_id,
169 FIRMWARE_UPDATE_RESULT_ID);
170
171 lwm2m_registry_lock();
172 /* Check LWM2M SPEC appendix E.6.1 */
173 switch (result) {
174 case RESULT_DEFAULT:
175 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_IDLE);
176 break;
177 case RESULT_SUCCESS:
178 if (update_state[obj_inst_id] != STATE_UPDATING) {
179 error = true;
180 state = update_state[obj_inst_id];
181 }
182
183 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_IDLE);
184 break;
185 case RESULT_NO_STORAGE:
186 case RESULT_OUT_OF_MEM:
187 case RESULT_CONNECTION_LOST:
188 case RESULT_UNSUP_FW:
189 case RESULT_INVALID_URI:
190 case RESULT_UNSUP_PROTO:
191 if (update_state[obj_inst_id] != STATE_DOWNLOADING) {
192 error = true;
193 state = update_state[obj_inst_id];
194 }
195
196 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_IDLE);
197 break;
198 case RESULT_INTEGRITY_FAILED:
199 if (update_state[obj_inst_id] != STATE_DOWNLOADING &&
200 update_state[obj_inst_id] != STATE_UPDATING) {
201 error = true;
202 state = update_state[obj_inst_id];
203 }
204
205 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_IDLE);
206 break;
207 case RESULT_UPDATE_FAILED:
208 if (update_state[obj_inst_id] != STATE_DOWNLOADING &&
209 update_state[obj_inst_id] != STATE_UPDATING) {
210 error = true;
211 state = update_state[obj_inst_id];
212 }
213
214 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_IDLE);
215 break;
216 default:
217 LOG_ERR("Unhandled result: %u", result);
218 lwm2m_registry_unlock();
219 return;
220 }
221
222 if (error) {
223 LOG_ERR("Unexpected result(%u) set while state is %u",
224 result, state);
225 }
226
227 lwm2m_set_u8(&path, result);
228 lwm2m_registry_unlock();
229 LOG_DBG("Update result = %d", result);
230 }
231
lwm2m_firmware_set_update_result(uint8_t result)232 void lwm2m_firmware_set_update_result(uint8_t result)
233 {
234 lwm2m_firmware_set_update_result_inst(0, result);
235 }
236
package_write_cb(uint16_t obj_inst_id,uint16_t res_id,uint16_t res_inst_id,uint8_t * data,uint16_t data_len,bool last_block,size_t total_size,size_t offset)237 static int package_write_cb(uint16_t obj_inst_id, uint16_t res_id,
238 uint16_t res_inst_id, uint8_t *data,
239 uint16_t data_len, bool last_block,
240 size_t total_size, size_t offset)
241 {
242 uint8_t state;
243 int ret = 0;
244 lwm2m_engine_set_data_cb_t write_callback;
245 lwm2m_engine_user_cb_t cancel_callback;
246
247 state = lwm2m_firmware_get_update_state_inst(obj_inst_id);
248 if (state == STATE_IDLE) {
249 /* TODO: setup timer to check download status,
250 * make sure it fail after timeout
251 */
252 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_DOWNLOADING);
253 } else if (state == STATE_DOWNLOADED) {
254 if (data_len == 0U || (data_len == 1U && data[0] == '\0')) {
255 /* reset to state idle and result default */
256 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_DEFAULT);
257 cancel_callback = lwm2m_firmware_get_cancel_cb_inst(obj_inst_id);
258 if (cancel_callback) {
259 ret = cancel_callback(obj_inst_id);
260 }
261 LOG_DBG("Update canceled by writing %d bytes", data_len);
262 return 0;
263 }
264 LOG_WRN("Download has already completed");
265 return -EPERM;
266 } else if (state != STATE_DOWNLOADING) {
267 LOG_WRN("Cannot download: state = %d", state);
268 return -EPERM;
269 }
270
271 write_callback = lwm2m_firmware_get_write_cb_inst(obj_inst_id);
272 if (write_callback) {
273 ret = write_callback(obj_inst_id, res_id, res_inst_id, data, data_len, last_block,
274 total_size, offset);
275 }
276
277 if (ret >= 0) {
278 if (last_block) {
279 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_DOWNLOADED);
280 }
281
282 return 0;
283 } else if (ret == -ENOMEM) {
284 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_OUT_OF_MEM);
285 } else if (ret == -ENOSPC) {
286 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_NO_STORAGE);
287 /* Response 4.13 (RFC7959, section 2.9.3) */
288 /* TODO: should include size1 option to indicate max size */
289 ret = -EFBIG;
290 } else if (ret == -EFAULT) {
291 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_INTEGRITY_FAILED);
292 } else if (ret == -ENOMSG) {
293 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_UNSUP_FW);
294 } else {
295 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_UPDATE_FAILED);
296 }
297
298 return ret;
299 }
300
package_uri_write_cb(uint16_t obj_inst_id,uint16_t res_id,uint16_t res_inst_id,uint8_t * data,uint16_t data_len,bool last_block,size_t total_size,size_t offset)301 static int package_uri_write_cb(uint16_t obj_inst_id, uint16_t res_id,
302 uint16_t res_inst_id, uint8_t *data,
303 uint16_t data_len, bool last_block,
304 size_t total_size, size_t offset)
305 {
306 LOG_DBG("PACKAGE_URI WRITE: %s", package_uri[obj_inst_id]);
307
308 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
309 uint8_t state = lwm2m_firmware_get_update_state_inst(obj_inst_id);
310 bool empty_uri = data_len == 0 || strnlen(data, data_len) == 0;
311
312 if (state == STATE_IDLE) {
313 if (!empty_uri) {
314 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_DOWNLOADING);
315 lwm2m_firmware_start_transfer(obj_inst_id, package_uri[obj_inst_id]);
316 }
317 } else if (state == STATE_DOWNLOADED && empty_uri) {
318 /* reset to state idle and result default */
319 lwm2m_firmware_set_update_result_inst(obj_inst_id, RESULT_DEFAULT);
320 }
321
322 return 0;
323 #else
324 return -EINVAL;
325 #endif
326 }
327
lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb)328 void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb)
329 {
330 lwm2m_firmware_set_write_cb_inst(0, cb);
331 }
332
lwm2m_firmware_get_write_cb(void)333 lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void)
334 {
335 return lwm2m_firmware_get_write_cb_inst(0);
336 }
337
lwm2m_firmware_set_update_cb(lwm2m_engine_execute_cb_t cb)338 void lwm2m_firmware_set_update_cb(lwm2m_engine_execute_cb_t cb)
339 {
340 lwm2m_firmware_set_update_cb_inst(0, cb);
341 }
342
lwm2m_firmware_get_update_cb(void)343 lwm2m_engine_execute_cb_t lwm2m_firmware_get_update_cb(void)
344 {
345 return lwm2m_firmware_get_update_cb_inst(0);
346 }
347
lwm2m_firmware_set_cancel_cb(lwm2m_engine_user_cb_t cb)348 void lwm2m_firmware_set_cancel_cb(lwm2m_engine_user_cb_t cb)
349 {
350 lwm2m_firmware_set_cancel_cb_inst(0, cb);
351 }
352
lwm2m_firmware_get_cancel_cb(void)353 lwm2m_engine_user_cb_t lwm2m_firmware_get_cancel_cb(void)
354 {
355 return lwm2m_firmware_get_cancel_cb_inst(0);
356 }
357
lwm2m_firmware_set_write_cb_inst(uint16_t obj_inst_id,lwm2m_engine_set_data_cb_t cb)358 void lwm2m_firmware_set_write_cb_inst(uint16_t obj_inst_id, lwm2m_engine_set_data_cb_t cb)
359 {
360 write_cb[obj_inst_id] = cb;
361 }
362
lwm2m_firmware_get_write_cb_inst(uint16_t obj_inst_id)363 lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb_inst(uint16_t obj_inst_id)
364 {
365 return write_cb[obj_inst_id];
366 }
367
lwm2m_firmware_set_update_cb_inst(uint16_t obj_inst_id,lwm2m_engine_execute_cb_t cb)368 void lwm2m_firmware_set_update_cb_inst(uint16_t obj_inst_id, lwm2m_engine_execute_cb_t cb)
369 {
370 update_cb[obj_inst_id] = cb;
371 }
372
lwm2m_firmware_get_update_cb_inst(uint16_t obj_inst_id)373 lwm2m_engine_execute_cb_t lwm2m_firmware_get_update_cb_inst(uint16_t obj_inst_id)
374 {
375 return update_cb[obj_inst_id];
376 }
377
lwm2m_firmware_set_cancel_cb_inst(uint16_t obj_inst_id,lwm2m_engine_user_cb_t cb)378 void lwm2m_firmware_set_cancel_cb_inst(uint16_t obj_inst_id, lwm2m_engine_user_cb_t cb)
379 {
380 cancel_cb[obj_inst_id] = cb;
381 }
382
lwm2m_firmware_get_cancel_cb_inst(uint16_t obj_inst_id)383 lwm2m_engine_user_cb_t lwm2m_firmware_get_cancel_cb_inst(uint16_t obj_inst_id)
384 {
385 return cancel_cb[obj_inst_id];
386 }
387
firmware_update_cb(uint16_t obj_inst_id,uint8_t * args,uint16_t args_len)388 static int firmware_update_cb(uint16_t obj_inst_id,
389 uint8_t *args, uint16_t args_len)
390 {
391 lwm2m_engine_execute_cb_t callback;
392 uint8_t state;
393 int ret;
394
395 state = lwm2m_firmware_get_update_state_inst(obj_inst_id);
396 if (state != STATE_DOWNLOADED) {
397 LOG_ERR("State other than downloaded: %d", state);
398 return -EPERM;
399 }
400
401 lwm2m_firmware_set_update_state_inst(obj_inst_id, STATE_UPDATING);
402
403 callback = lwm2m_firmware_get_update_cb_inst(obj_inst_id);
404 if (callback) {
405 ret = callback(obj_inst_id, args, args_len);
406 if (ret < 0) {
407 LOG_ERR("Failed to update firmware: %d", ret);
408 lwm2m_firmware_set_update_result_inst(obj_inst_id,
409 ret == -EINVAL ? RESULT_INTEGRITY_FAILED :
410 RESULT_UPDATE_FAILED);
411 return 0;
412 }
413 }
414
415 return 0;
416 }
417
firmware_create(uint16_t obj_inst_id)418 static struct lwm2m_engine_obj_inst *firmware_create(uint16_t obj_inst_id)
419 {
420 int index, i = 0, j = 0;
421
422 /* Check that there is no other instance with this ID */
423 for (index = 0; index < MAX_INSTANCE_COUNT; index++) {
424 if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) {
425 LOG_ERR("Can not create instance - "
426 "already existing: %u", obj_inst_id);
427 return NULL;
428 }
429 }
430
431 for (index = 0; index < MAX_INSTANCE_COUNT; index++) {
432 if (!inst[index].obj) {
433 break;
434 }
435 }
436
437 if (index >= MAX_INSTANCE_COUNT) {
438 LOG_ERR("Can not create instance - "
439 "no more room: %u", obj_inst_id);
440 return NULL;
441 }
442
443 init_res_instance(res_inst[index], ARRAY_SIZE(res_inst[index]));
444
445 /* initialize instance resource data */
446 INIT_OBJ_RES_OPT(FIRMWARE_PACKAGE_ID, res[index], i, res_inst[index], j, 1,
447 false, true, NULL, NULL, NULL, package_write_cb, NULL);
448 INIT_OBJ_RES_LEN(FIRMWARE_PACKAGE_URI_ID, res[index], i, res_inst[index], j, 1,
449 false, true, package_uri[index], PACKAGE_URI_LEN, 0, NULL, NULL, NULL,
450 package_uri_write_cb, NULL);
451 INIT_OBJ_RES_EXECUTE(FIRMWARE_UPDATE_ID, res[index], i, firmware_update_cb);
452 INIT_OBJ_RES_DATA(FIRMWARE_STATE_ID, res[index], i, res_inst[index], j,
453 &(update_state[index]), sizeof(update_state[index]));
454 INIT_OBJ_RES_DATA(FIRMWARE_UPDATE_RESULT_ID, res[index], i, res_inst[index], j,
455 &(update_result[index]), sizeof(update_result[index]));
456 INIT_OBJ_RES_OPTDATA(FIRMWARE_PACKAGE_NAME_ID, res[index], i,
457 res_inst[index], j);
458 INIT_OBJ_RES_OPTDATA(FIRMWARE_PACKAGE_VERSION_ID, res[index], i,
459 res_inst[index], j);
460 INIT_OBJ_RES_MULTI_OPTDATA(FIRMWARE_UPDATE_PROTO_SUPPORT_ID, res[index], i,
461 res_inst[index], j, 1, false);
462 INIT_OBJ_RES_DATA(FIRMWARE_UPDATE_DELIV_METHOD_ID, res[index], i,
463 res_inst[index], j, &(delivery_method[index]),
464 sizeof(delivery_method[index]));
465
466 inst[index].resources = res[index];
467 inst[index].resource_count = i;
468
469 LOG_DBG("Create LWM2M firmware instance: %d", obj_inst_id);
470 return &inst[index];
471 }
472
lwm2m_firmware_init(void)473 static int lwm2m_firmware_init(void)
474 {
475 struct lwm2m_engine_obj_inst *obj_inst = NULL;
476 int ret = 0;
477
478 /* Set default values */
479 firmware.obj_id = LWM2M_OBJECT_FIRMWARE_ID;
480 firmware.version_major = FIRMWARE_VERSION_MAJOR;
481 firmware.version_minor = FIRMWARE_VERSION_MINOR;
482 firmware.is_core = true;
483 firmware.fields = fields;
484 firmware.field_count = ARRAY_SIZE(fields);
485 firmware.max_instance_count = MAX_INSTANCE_COUNT;
486 firmware.create_cb = firmware_create;
487 lwm2m_register_obj(&firmware);
488
489
490 for (int idx = 0; idx < MAX_INSTANCE_COUNT; idx++) {
491 package_uri[idx][0] = '\0';
492
493 /* Initialize state machine */
494 /* TODO: should be restored from the permanent storage */
495 update_state[idx] = STATE_IDLE;
496 update_result[idx] = RESULT_DEFAULT;
497 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
498 delivery_method[idx] = DELIVERY_METHOD_BOTH;
499 #else
500 delivery_method[idx] = DELIVERY_METHOD_PUSH_ONLY;
501 #endif
502 ret = lwm2m_create_obj_inst(LWM2M_OBJECT_FIRMWARE_ID, idx, &obj_inst);
503 if (ret < 0) {
504 LOG_DBG("Create LWM2M instance %d error: %d", idx, ret);
505 break;
506 }
507 }
508
509 return ret;
510 }
511
512 LWM2M_CORE_INIT(lwm2m_firmware_init);
513