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 <logging/log.h>
12 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
13 
14 #include <string.h>
15 #include <init.h>
16 
17 #include "lwm2m_object.h"
18 #include "lwm2m_engine.h"
19 
20 #define FIRMWARE_VERSION_MAJOR 1
21 #define FIRMWARE_VERSION_MINOR 0
22 
23 /* Firmware resource IDs */
24 #define FIRMWARE_PACKAGE_ID			0
25 #define FIRMWARE_PACKAGE_URI_ID			1
26 #define FIRMWARE_UPDATE_ID			2
27 #define FIRMWARE_STATE_ID			3
28 #define FIRMWARE_UPDATE_RESULT_ID		5
29 #define FIRMWARE_PACKAGE_NAME_ID		6
30 #define FIRMWARE_PACKAGE_VERSION_ID		7
31 #define FIRMWARE_UPDATE_PROTO_SUPPORT_ID	8 /* TODO */
32 #define FIRMWARE_UPDATE_DELIV_METHOD_ID		9
33 
34 #define FIRMWARE_MAX_ID				10
35 
36 #define DELIVERY_METHOD_PULL_ONLY		0
37 #define DELIVERY_METHOD_PUSH_ONLY		1
38 #define DELIVERY_METHOD_BOTH			2
39 
40 #define PACKAGE_URI_LEN				255
41 
42 /*
43  * Calculate resource instances as follows:
44  * start with FIRMWARE_MAX_ID
45  * subtract EXEC resources (1)
46  */
47 #define RESOURCE_INSTANCE_COUNT	(FIRMWARE_MAX_ID - 1)
48 
49 /* resource state variables */
50 static uint8_t update_state;
51 static uint8_t update_result;
52 static uint8_t delivery_method;
53 static char package_uri[PACKAGE_URI_LEN];
54 
55 /* only 1 instance of firmware object exists */
56 static struct lwm2m_engine_obj firmware;
57 static struct lwm2m_engine_obj_field fields[] = {
58 	OBJ_FIELD_DATA(FIRMWARE_PACKAGE_ID, W, OPAQUE),
59 	OBJ_FIELD_DATA(FIRMWARE_PACKAGE_URI_ID, RW, STRING),
60 	OBJ_FIELD_EXECUTE(FIRMWARE_UPDATE_ID),
61 	OBJ_FIELD_DATA(FIRMWARE_STATE_ID, R, U8),
62 	OBJ_FIELD_DATA(FIRMWARE_UPDATE_RESULT_ID, R, U8),
63 	OBJ_FIELD_DATA(FIRMWARE_PACKAGE_NAME_ID, R_OPT, STRING),
64 	OBJ_FIELD_DATA(FIRMWARE_PACKAGE_VERSION_ID, R_OPT, STRING),
65 	OBJ_FIELD_DATA(FIRMWARE_UPDATE_PROTO_SUPPORT_ID, R_OPT, U8),
66 	OBJ_FIELD_DATA(FIRMWARE_UPDATE_DELIV_METHOD_ID, R, U8)
67 };
68 
69 static struct lwm2m_engine_obj_inst inst;
70 static struct lwm2m_engine_res res[FIRMWARE_MAX_ID];
71 static struct lwm2m_engine_res_inst res_inst[RESOURCE_INSTANCE_COUNT];
72 
73 static lwm2m_engine_set_data_cb_t write_cb;
74 static lwm2m_engine_execute_cb_t update_cb;
75 
76 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
77 extern int lwm2m_firmware_start_transfer(char *package_uri);
78 #endif
79 
lwm2m_firmware_get_update_state(void)80 uint8_t lwm2m_firmware_get_update_state(void)
81 {
82 	return update_state;
83 }
84 
lwm2m_firmware_set_update_state(uint8_t state)85 void lwm2m_firmware_set_update_state(uint8_t state)
86 {
87 	bool error = false;
88 
89 	/* Check LWM2M SPEC appendix E.6.1 */
90 	switch (state) {
91 	case STATE_DOWNLOADING:
92 		if (update_state != STATE_IDLE) {
93 			error = true;
94 		}
95 		break;
96 	case STATE_DOWNLOADED:
97 		if (update_state != STATE_DOWNLOADING &&
98 		    update_state != STATE_UPDATING) {
99 			error = true;
100 		}
101 		break;
102 	case STATE_UPDATING:
103 		if (update_state != STATE_DOWNLOADED) {
104 			error = true;
105 		}
106 		break;
107 	case STATE_IDLE:
108 		break;
109 	default:
110 		LOG_ERR("Unhandled state: %u", state);
111 		return;
112 	}
113 
114 	if (error) {
115 		LOG_ERR("Invalid state transition: %u -> %u",
116 			update_state, state);
117 	}
118 
119 	update_state = state;
120 	NOTIFY_OBSERVER(LWM2M_OBJECT_FIRMWARE_ID, 0, FIRMWARE_STATE_ID);
121 	LOG_DBG("Update state = %d", update_state);
122 }
123 
lwm2m_firmware_get_update_result(void)124 uint8_t lwm2m_firmware_get_update_result(void)
125 {
126 	return update_result;
127 }
128 
lwm2m_firmware_set_update_result(uint8_t result)129 void lwm2m_firmware_set_update_result(uint8_t result)
130 {
131 	uint8_t state;
132 	bool error = false;
133 
134 	/* Check LWM2M SPEC appendix E.6.1 */
135 	switch (result) {
136 	case RESULT_DEFAULT:
137 		lwm2m_firmware_set_update_state(STATE_IDLE);
138 		break;
139 	case RESULT_SUCCESS:
140 		if (update_state != STATE_UPDATING) {
141 			error = true;
142 			state = update_state;
143 		}
144 
145 		lwm2m_firmware_set_update_state(STATE_IDLE);
146 		break;
147 	case RESULT_NO_STORAGE:
148 	case RESULT_OUT_OF_MEM:
149 	case RESULT_CONNECTION_LOST:
150 	case RESULT_UNSUP_FW:
151 	case RESULT_INVALID_URI:
152 	case RESULT_UNSUP_PROTO:
153 		if (update_state != STATE_DOWNLOADING) {
154 			error = true;
155 			state = update_state;
156 		}
157 
158 		lwm2m_firmware_set_update_state(STATE_IDLE);
159 		break;
160 	case RESULT_INTEGRITY_FAILED:
161 		if (update_state != STATE_DOWNLOADING &&
162 		    update_state != STATE_UPDATING) {
163 			error = true;
164 			state = update_state;
165 		}
166 
167 		lwm2m_firmware_set_update_state(STATE_IDLE);
168 		break;
169 	case RESULT_UPDATE_FAILED:
170 		if (update_state != STATE_DOWNLOADING &&
171 		    update_state != STATE_UPDATING) {
172 			error = true;
173 			state = update_state;
174 		}
175 
176 		lwm2m_firmware_set_update_state(STATE_IDLE);
177 		break;
178 	default:
179 		LOG_ERR("Unhandled result: %u", result);
180 		return;
181 	}
182 
183 	if (error) {
184 		LOG_ERR("Unexpected result(%u) set while state is %u",
185 			result, state);
186 	}
187 
188 	update_result = result;
189 	NOTIFY_OBSERVER(LWM2M_OBJECT_FIRMWARE_ID, 0, FIRMWARE_UPDATE_RESULT_ID);
190 	LOG_DBG("Update result = %d", update_result);
191 }
192 
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)193 static int package_write_cb(uint16_t obj_inst_id, uint16_t res_id,
194 			    uint16_t res_inst_id, uint8_t *data, uint16_t data_len,
195 			    bool last_block, size_t total_size)
196 {
197 	uint8_t state;
198 	int ret;
199 
200 	state = lwm2m_firmware_get_update_state();
201 	if (state == STATE_IDLE) {
202 		/* TODO: setup timer to check download status,
203 		 * make sure it fail after timeout
204 		 */
205 		lwm2m_firmware_set_update_state(STATE_DOWNLOADING);
206 	} else if (state != STATE_DOWNLOADING) {
207 		if (data_len == 0U && state == STATE_DOWNLOADED) {
208 			/* reset to state idle and result default */
209 			lwm2m_firmware_set_update_result(RESULT_DEFAULT);
210 			return 0;
211 		}
212 
213 		LOG_DBG("Cannot download: state = %d", state);
214 		return -EPERM;
215 	}
216 
217 	ret = write_cb ? write_cb(obj_inst_id, res_id, res_inst_id,
218 				  data, data_len,
219 				  last_block, total_size) : 0;
220 	if (ret >= 0) {
221 		if (last_block) {
222 			lwm2m_firmware_set_update_state(STATE_DOWNLOADED);
223 		}
224 
225 		return 0;
226 	} else if (ret == -ENOMEM) {
227 		lwm2m_firmware_set_update_result(RESULT_OUT_OF_MEM);
228 	} else if (ret == -ENOSPC) {
229 		lwm2m_firmware_set_update_result(RESULT_NO_STORAGE);
230 		/* Response 4.13 (RFC7959, section 2.9.3) */
231 		/* TODO: should include size1 option to indicate max size */
232 		ret = -EFBIG;
233 	} else if (ret == -EFAULT) {
234 		lwm2m_firmware_set_update_result(RESULT_INTEGRITY_FAILED);
235 	} else {
236 		lwm2m_firmware_set_update_result(RESULT_UPDATE_FAILED);
237 	}
238 
239 	return ret;
240 }
241 
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)242 static int package_uri_write_cb(uint16_t obj_inst_id, uint16_t res_id,
243 				uint16_t res_inst_id, uint8_t *data, uint16_t data_len,
244 				bool last_block, size_t total_size)
245 {
246 	LOG_DBG("PACKAGE_URI WRITE: %s", log_strdup(package_uri));
247 
248 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
249 	uint8_t state = lwm2m_firmware_get_update_state();
250 
251 	if (state == STATE_IDLE) {
252 		lwm2m_firmware_set_update_result(RESULT_DEFAULT);
253 
254 		if (data_len > 0) {
255 			lwm2m_firmware_start_transfer(package_uri);
256 		}
257 	} else if (state == STATE_DOWNLOADED && data_len == 0U) {
258 		/* reset to state idle and result default */
259 		lwm2m_firmware_set_update_result(RESULT_DEFAULT);
260 	}
261 
262 	return 0;
263 #else
264 	return -EINVAL;
265 #endif
266 }
267 
lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb)268 void lwm2m_firmware_set_write_cb(lwm2m_engine_set_data_cb_t cb)
269 {
270 	write_cb = cb;
271 }
272 
lwm2m_firmware_get_write_cb(void)273 lwm2m_engine_set_data_cb_t lwm2m_firmware_get_write_cb(void)
274 {
275 	return write_cb;
276 }
277 
lwm2m_firmware_set_update_cb(lwm2m_engine_execute_cb_t cb)278 void lwm2m_firmware_set_update_cb(lwm2m_engine_execute_cb_t cb)
279 {
280 	update_cb = cb;
281 }
282 
lwm2m_firmware_get_update_cb(void)283 lwm2m_engine_execute_cb_t lwm2m_firmware_get_update_cb(void)
284 {
285 	return update_cb;
286 }
287 
firmware_update_cb(uint16_t obj_inst_id,uint8_t * args,uint16_t args_len)288 static int firmware_update_cb(uint16_t obj_inst_id,
289 			      uint8_t *args, uint16_t args_len)
290 {
291 	lwm2m_engine_execute_cb_t callback;
292 	uint8_t state;
293 	int ret;
294 
295 	state = lwm2m_firmware_get_update_state();
296 	if (state != STATE_DOWNLOADED) {
297 		LOG_ERR("State other than downloaded: %d", state);
298 		return -EPERM;
299 	}
300 
301 	lwm2m_firmware_set_update_state(STATE_UPDATING);
302 
303 	callback = lwm2m_firmware_get_update_cb();
304 	if (callback) {
305 		ret = callback(obj_inst_id, args, args_len);
306 		if (ret < 0) {
307 			LOG_ERR("Failed to update firmware: %d", ret);
308 			lwm2m_firmware_set_update_result(
309 				ret == -EINVAL ? RESULT_INTEGRITY_FAILED :
310 						 RESULT_UPDATE_FAILED);
311 			return 0;
312 		}
313 	}
314 
315 	return 0;
316 }
317 
firmware_create(uint16_t obj_inst_id)318 static struct lwm2m_engine_obj_inst *firmware_create(uint16_t obj_inst_id)
319 {
320 	int i = 0, j = 0;
321 
322 	init_res_instance(res_inst, ARRAY_SIZE(res_inst));
323 
324 	/* initialize instance resource data */
325 	INIT_OBJ_RES_OPT(FIRMWARE_PACKAGE_ID, res, i, res_inst, j, 1, false,
326 			 true, NULL, NULL, NULL, package_write_cb, NULL);
327 	INIT_OBJ_RES(FIRMWARE_PACKAGE_URI_ID, res, i, res_inst, j, 1, false,
328 		     true, package_uri, PACKAGE_URI_LEN,
329 		     NULL, NULL, NULL, package_uri_write_cb, NULL);
330 	INIT_OBJ_RES_EXECUTE(FIRMWARE_UPDATE_ID, res, i, firmware_update_cb);
331 	INIT_OBJ_RES_DATA(FIRMWARE_STATE_ID, res, i, res_inst, j,
332 			  &update_state, sizeof(update_state));
333 	INIT_OBJ_RES_DATA(FIRMWARE_UPDATE_RESULT_ID, res, i, res_inst, j,
334 			  &update_result, sizeof(update_result));
335 	INIT_OBJ_RES_DATA(FIRMWARE_UPDATE_DELIV_METHOD_ID, res, i, res_inst, j,
336 			  &delivery_method, sizeof(delivery_method));
337 
338 	inst.resources = res;
339 	inst.resource_count = i;
340 
341 	LOG_DBG("Create LWM2M firmware instance: %d", obj_inst_id);
342 	return &inst;
343 }
344 
lwm2m_firmware_init(const struct device * dev)345 static int lwm2m_firmware_init(const struct device *dev)
346 {
347 	struct lwm2m_engine_obj_inst *obj_inst = NULL;
348 	int ret = 0;
349 
350 	/* Set default values */
351 	package_uri[0] = '\0';
352 	/* Initialize state machine */
353 	/* TODO: should be restored from the permanent storage */
354 	update_state = STATE_IDLE;
355 	update_result = RESULT_DEFAULT;
356 #ifdef CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT
357 	delivery_method = DELIVERY_METHOD_BOTH;
358 #else
359 	delivery_method = DELIVERY_METHOD_PUSH_ONLY;
360 #endif
361 
362 	firmware.obj_id = LWM2M_OBJECT_FIRMWARE_ID;
363 	firmware.version_major = FIRMWARE_VERSION_MAJOR;
364 	firmware.version_minor = FIRMWARE_VERSION_MINOR;
365 	firmware.is_core = true;
366 	firmware.fields = fields;
367 	firmware.field_count = ARRAY_SIZE(fields);
368 	firmware.max_instance_count = 1U;
369 	firmware.create_cb = firmware_create;
370 	lwm2m_register_obj(&firmware);
371 
372 	/* auto create the only instance */
373 	ret = lwm2m_create_obj_inst(LWM2M_OBJECT_FIRMWARE_ID, 0, &obj_inst);
374 	if (ret < 0) {
375 		LOG_DBG("Create LWM2M instance 0 error: %d", ret);
376 	}
377 
378 	return ret;
379 }
380 
381 SYS_INIT(lwm2m_firmware_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
382