1 /**
2  * @file lwm2m_gateway.c
3  * @brief
4  *
5  * Copyright (c) 2021 Laird Connectivity
6  *
7  * SPDX-License-Identifier: Apache-2.0
8  */
9 
10 /*
11  * Gateway
12  * https://github.com/OpenMobileAlliance/lwm2m-registry/blob/prod/25.xml
13  */
14 
15 #define LOG_MODULE_NAME net_lwm2m_gateway
16 #define LOG_LEVEL CONFIG_LWM2M_LOG_LEVEL
17 
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(LOG_MODULE_NAME);
20 
21 #include <string.h>
22 #include <zephyr/init.h>
23 
24 #include "lwm2m_object.h"
25 #include "lwm2m_engine.h"
26 #include "lwm2m_resource_ids.h"
27 #include "lwm2m_obj_gateway.h"
28 
29 #define GATEWAY_VERSION_MAJOR 2
30 #define GATEWAY_VERSION_MINOR 0
31 #define GATEWAY_MAX_ID 4
32 
33 #define MAX_INSTANCE_COUNT CONFIG_LWM2M_GATEWAY_MAX_INSTANCES
34 
35 BUILD_ASSERT(CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE >= CONFIG_LWM2M_GATEWAY_PREFIX_MAX_STR_SIZE,
36 	     "Gateway prefix requires validation");
37 
38 /*
39  * Calculate resource instances as follows:
40  * start with GATEWAY_MAX_ID
41  * subtract EXEC resources (1)
42  */
43 #define RESOURCE_INSTANCE_COUNT (GATEWAY_MAX_ID - 1)
44 
45 struct lwm2m_gw_obj {
46 	char device_id[CONFIG_LWM2M_GATEWAY_DEVICE_ID_MAX_STR_SIZE];
47 	char prefix[CONFIG_LWM2M_GATEWAY_PREFIX_MAX_STR_SIZE];
48 	char iot_device_objects[CONFIG_LWM2M_GATEWAY_IOT_DEVICE_OBJECTS_MAX_STR_SIZE];
49 };
50 
51 static struct lwm2m_gw_obj device_table[MAX_INSTANCE_COUNT];
52 static struct lwm2m_engine_obj lwm2m_gw;
53 static struct lwm2m_engine_obj_field fields[] = {
54 	OBJ_FIELD_DATA(LWM2M_GATEWAY_DEVICE_RID, R, STRING),
55 	OBJ_FIELD_DATA(LWM2M_GATEWAY_PREFIX_RID, RW, STRING),
56 	OBJ_FIELD_DATA(LWM2M_GATEWAY_IOT_DEVICE_OBJECTS_RID, R, STRING),
57 };
58 
59 static struct lwm2m_engine_obj_inst inst[MAX_INSTANCE_COUNT];
60 static struct lwm2m_engine_res res[MAX_INSTANCE_COUNT][GATEWAY_MAX_ID];
61 static struct lwm2m_engine_res_inst res_inst[MAX_INSTANCE_COUNT][RESOURCE_INSTANCE_COUNT];
62 lwm2m_engine_gateway_msg_cb gateway_msg_cb[MAX_INSTANCE_COUNT];
63 
prefix_validation_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)64 static int prefix_validation_cb(uint16_t obj_inst_id, uint16_t res_id,
65 				uint16_t res_inst_id, uint8_t *data,
66 				uint16_t data_len, bool last_block,
67 				size_t total_size, size_t offset)
68 {
69 	int i;
70 	int length;
71 	bool unique = true;
72 
73 	/* Prefix can't be empty because it is used to reference device */
74 	if (data_len == 0) {
75 		return -EINVAL;
76 	}
77 
78 	/* Prefix of each gateway must be unique */
79 	for (i = 0; i < MAX_INSTANCE_COUNT; i++) {
80 		length = strlen(device_table[i].prefix);
81 		if (length == data_len) {
82 			if (strncmp(device_table[i].prefix, data, length) == 0) {
83 				if (inst[i].obj_inst_id != obj_inst_id) {
84 					unique = false;
85 					break;
86 				}
87 			}
88 		}
89 	}
90 
91 	return unique ? 0 : -EINVAL;
92 }
93 
lwm2m_gw_create(uint16_t obj_inst_id)94 static struct lwm2m_engine_obj_inst *lwm2m_gw_create(uint16_t obj_inst_id)
95 {
96 	int index, i = 0, j = 0;
97 
98 	/* Check that there is no other instance with this ID */
99 	for (index = 0; index < MAX_INSTANCE_COUNT; index++) {
100 		if (inst[index].obj && inst[index].obj_inst_id == obj_inst_id) {
101 			LOG_ERR("Can not create instance - "
102 				"already existing: %u",
103 				obj_inst_id);
104 			return NULL;
105 		}
106 	}
107 
108 	for (index = 0; index < MAX_INSTANCE_COUNT; index++) {
109 		if (!inst[index].obj) {
110 			break;
111 		}
112 	}
113 
114 	if (index >= MAX_INSTANCE_COUNT) {
115 		LOG_ERR("Can not create instance - no more room: %u", obj_inst_id);
116 		return NULL;
117 	}
118 
119 	/* Set default values */
120 	strncpy(device_table[index].device_id, CONFIG_LWM2M_GATEWAY_DEFAULT_DEVICE_ID,
121 		CONFIG_LWM2M_GATEWAY_DEVICE_ID_MAX_STR_SIZE);
122 	snprintk(device_table[index].prefix, CONFIG_LWM2M_GATEWAY_PREFIX_MAX_STR_SIZE,
123 		 CONFIG_LWM2M_GATEWAY_DEFAULT_DEVICE_PREFIX "%u", index);
124 	strncpy(device_table[index].iot_device_objects,
125 		CONFIG_LWM2M_GATEWAY_DEFAULT_IOT_DEVICE_OBJECTS,
126 		CONFIG_LWM2M_GATEWAY_IOT_DEVICE_OBJECTS_MAX_STR_SIZE);
127 
128 	(void)memset(res[index], 0, sizeof(res[index][0]) * ARRAY_SIZE(res[index]));
129 	init_res_instance(res_inst[index], ARRAY_SIZE(res_inst[index]));
130 
131 	/* initialize instance resource data */
132 	INIT_OBJ_RES_DATA_LEN(LWM2M_GATEWAY_DEVICE_RID, res[index], i, res_inst[index], j,
133 			      device_table[index].device_id,
134 			      CONFIG_LWM2M_GATEWAY_DEVICE_ID_MAX_STR_SIZE,
135 			      strlen(device_table[index].device_id) + 1);
136 	INIT_OBJ_RES_LEN(LWM2M_GATEWAY_PREFIX_RID, res[index], i, res_inst[index], j, 1, false,
137 			 true, device_table[index].prefix, CONFIG_LWM2M_GATEWAY_PREFIX_MAX_STR_SIZE,
138 			 strlen(device_table[index].prefix) + 1, NULL, NULL, prefix_validation_cb,
139 			 NULL, NULL);
140 	INIT_OBJ_RES_DATA_LEN(LWM2M_GATEWAY_IOT_DEVICE_OBJECTS_RID, res[index], i, res_inst[index],
141 			      j, device_table[index].iot_device_objects,
142 			      sizeof(device_table[index].iot_device_objects),
143 			      strlen(device_table[index].iot_device_objects) + 1);
144 
145 	inst[index].resources = res[index];
146 	inst[index].resource_count = i;
147 	LOG_DBG("Created LWM2M gateway instance: %d", obj_inst_id);
148 	return &inst[index];
149 }
150 
lwm2m_gw_handle_req(struct lwm2m_message * msg)151 int lwm2m_gw_handle_req(struct lwm2m_message *msg)
152 {
153 	struct coap_option options[4];
154 	int ret;
155 
156 	ret = coap_find_options(msg->in.in_cpkt, COAP_OPTION_URI_PATH, options,
157 				ARRAY_SIZE(options));
158 	if (ret < 0) {
159 		return ret;
160 	}
161 
162 	for (int index = 0; index < MAX_INSTANCE_COUNT; index++) {
163 		/* Skip uninitialized objects */
164 		if (!inst[index].obj) {
165 			continue;
166 		}
167 
168 		char *prefix = device_table[index].prefix;
169 		size_t prefix_len = strlen(prefix);
170 
171 		if (prefix_len != options[0].len) {
172 			continue;
173 		}
174 		if (strncmp(options[0].value, prefix, prefix_len) != 0) {
175 			continue;
176 		}
177 
178 		if (gateway_msg_cb[index] == NULL) {
179 			return -ENOENT;
180 		}
181 		/* Delete prefix from path*/
182 		ret = coap_options_to_path(&options[1], ret - 1, &msg->path);
183 		if (ret < 0) {
184 			return ret;
185 		}
186 		return gateway_msg_cb[index](msg);
187 	}
188 	return -ENOENT;
189 }
190 
lwm2m_register_gw_callback(uint16_t obj_inst_id,lwm2m_engine_gateway_msg_cb cb)191 int lwm2m_register_gw_callback(uint16_t obj_inst_id, lwm2m_engine_gateway_msg_cb cb)
192 {
193 	for (int index = 0; index < MAX_INSTANCE_COUNT; index++) {
194 		if (inst[index].obj_inst_id == obj_inst_id) {
195 			gateway_msg_cb[index] = cb;
196 			return 0;
197 		}
198 	}
199 	return -ENOENT;
200 }
201 
lwm2m_gw_init(void)202 static int lwm2m_gw_init(void)
203 {
204 	int ret = 0;
205 
206 	/* initialize the LwM2M Gateway field data */
207 	lwm2m_gw.obj_id = LWM2M_OBJECT_GATEWAY_ID;
208 	lwm2m_gw.version_major = GATEWAY_VERSION_MAJOR;
209 	lwm2m_gw.version_minor = GATEWAY_VERSION_MINOR;
210 	lwm2m_gw.is_core = false;
211 	lwm2m_gw.fields = fields;
212 	lwm2m_gw.field_count = ARRAY_SIZE(fields);
213 	lwm2m_gw.max_instance_count = MAX_INSTANCE_COUNT;
214 	lwm2m_gw.create_cb = lwm2m_gw_create;
215 	lwm2m_register_obj(&lwm2m_gw);
216 	return ret;
217 }
218 
219 LWM2M_OBJ_INIT(lwm2m_gw_init);
220