1 // Copyright 2019 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <stdio.h>
16 #include <string.h>
17 #include <esp_err.h>
18 #include <esp_log.h>
19 
20 #include <protocomm.h>
21 #include <protocomm_security0.h>
22 #include <protocomm_security1.h>
23 
24 #include <esp_local_ctrl.h>
25 #include "esp_local_ctrl_priv.h"
26 #include "esp_local_ctrl.pb-c.h"
27 
28 #define ESP_LOCAL_CTRL_VERSION "v1.0"
29 
30 struct inst_ctx {
31     protocomm_t *pc;
32     esp_local_ctrl_config_t config;
33     esp_local_ctrl_prop_t **props;
34     size_t props_count;
35 };
36 
37 struct inst_ctx *local_ctrl_inst_ctx;
38 
39 static const char *TAG = "esp_local_ctrl";
40 
esp_local_ctrl_start(const esp_local_ctrl_config_t * config)41 esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config)
42 {
43     esp_err_t ret;
44 
45     if (!config) {
46         ESP_LOGE(TAG, "NULL configuration provided");
47         return ESP_ERR_INVALID_ARG;
48     }
49 
50     if (!config->transport) {
51         ESP_LOGE(TAG, "No transport provided");
52         return ESP_ERR_INVALID_ARG;
53     }
54 
55     if (config->max_properties == 0) {
56         ESP_LOGE(TAG, "max_properties must be greater than 0");
57         return ESP_ERR_INVALID_ARG;
58     }
59 
60     if (!config->handlers.get_prop_values ||
61         !config->handlers.set_prop_values) {
62         ESP_LOGE(TAG, "Handlers cannot be null");
63         return ESP_ERR_INVALID_ARG;
64     }
65 
66     if (local_ctrl_inst_ctx) {
67         ESP_LOGW(TAG, "Service already active");
68         return ESP_ERR_INVALID_STATE;
69     }
70 
71     local_ctrl_inst_ctx = calloc(1, sizeof(struct inst_ctx));
72     if (!local_ctrl_inst_ctx) {
73         ESP_LOGE(TAG, "Failed to allocate memory for instance");
74         return ESP_ERR_NO_MEM;
75     }
76     memcpy(&local_ctrl_inst_ctx->config, config, sizeof(local_ctrl_inst_ctx->config));
77 
78     local_ctrl_inst_ctx->props = calloc(local_ctrl_inst_ctx->config.max_properties,
79                                         sizeof(esp_local_ctrl_prop_t *));
80     if (!local_ctrl_inst_ctx->props) {
81         ESP_LOGE(TAG, "Failed to allocate memory for properties");
82         free(local_ctrl_inst_ctx);
83         local_ctrl_inst_ctx = NULL;
84         return ESP_ERR_NO_MEM;
85     }
86 
87     /* Since the config structure will be different for different transport modes, each transport may
88      * implement a `copy_config()` function, which accepts a configuration structure as input and
89      * creates a copy of that, which can be kept in the context structure of the `esp_local_ctrl` instance.
90      * This copy can be later be freed using `free_config()` */
91     if (config->transport->copy_config) {
92         ret = config->transport->copy_config(&local_ctrl_inst_ctx->config.transport_config,
93                                              &config->transport_config);
94         if (ret != ESP_OK) {
95             esp_local_ctrl_stop();
96             return ret;
97         }
98     }
99 
100     /* For a selected transport mode, endpoints may need to be declared prior to starting the
101      * `esp_local_ctrl` service, e.g. in case of BLE. By declaration it means that the transport layer
102      * allocates some resources for an endpoint, and later, after service has started, a handler
103      * is assigned for that endpoint */
104     if (config->transport->declare_ep) {
105         /* UUIDs are 16bit unique IDs for each endpoint. This may or may not be relevant for
106          * a chosen transport. We reserve all values from FF50 to FFFF for the internal endpoints.
107          * The remaining endpoints can be used by the application for its own custom endpoints */
108         uint16_t start_uuid = 0xFF50;
109         ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
110                                             "esp_local_ctrl/version", start_uuid++);
111         if (ret != ESP_OK) {
112             esp_local_ctrl_stop();
113             return ret;
114         }
115         ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
116                                             "esp_local_ctrl/session", start_uuid++);
117         if (ret != ESP_OK) {
118             esp_local_ctrl_stop();
119             return ret;
120         }
121         ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
122                                             "esp_local_ctrl/control", start_uuid++);
123         if (ret != ESP_OK) {
124             esp_local_ctrl_stop();
125             return ret;
126         }
127     }
128 
129     local_ctrl_inst_ctx->pc = protocomm_new();
130     if (!local_ctrl_inst_ctx->pc) {
131         ESP_LOGE(TAG, "Failed to create new protocomm instance");
132         esp_local_ctrl_stop();
133         return ESP_FAIL;
134     }
135 
136     if (config->transport->start_service) {
137         ret = config->transport->start_service(local_ctrl_inst_ctx->pc,
138                                                &local_ctrl_inst_ctx->config.transport_config);
139         if (ret != ESP_OK) {
140             esp_local_ctrl_stop();
141             return ret;
142         }
143     }
144 
145     ret = protocomm_set_version(local_ctrl_inst_ctx->pc, "esp_local_ctrl/version",
146                                 ESP_LOCAL_CTRL_VERSION);
147     if (ret != ESP_OK) {
148         ESP_LOGE(TAG, "Failed to set version endpoint");
149         esp_local_ctrl_stop();
150         return ret;
151     }
152 
153     protocomm_security_t *proto_sec_handle;
154     switch (local_ctrl_inst_ctx->config.proto_sec.version) {
155         case PROTOCOM_SEC_CUSTOM:
156             proto_sec_handle = local_ctrl_inst_ctx->config.proto_sec.custom_handle;
157             break;
158         case PROTOCOM_SEC1:
159             proto_sec_handle = (protocomm_security_t *) &protocomm_security1;
160             break;
161         case PROTOCOM_SEC0:
162         default:
163             proto_sec_handle = (protocomm_security_t *) &protocomm_security0;
164             break;
165     }
166     ret = protocomm_set_security(local_ctrl_inst_ctx->pc, "esp_local_ctrl/session",
167                                  proto_sec_handle, local_ctrl_inst_ctx->config.proto_sec.pop);
168     if (ret != ESP_OK) {
169         ESP_LOGE(TAG, "Failed to set session endpoint");
170         esp_local_ctrl_stop();
171         return ret;
172     }
173 
174     ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, "esp_local_ctrl/control",
175                                  esp_local_ctrl_data_handler, NULL);
176     if (ret != ESP_OK) {
177         ESP_LOGE(TAG, "Failed to set control endpoint");
178         esp_local_ctrl_stop();
179         return ret;
180     }
181     return ESP_OK;
182 }
183 
esp_local_ctrl_stop(void)184 esp_err_t esp_local_ctrl_stop(void)
185 {
186     if (local_ctrl_inst_ctx) {
187         if (local_ctrl_inst_ctx->config.transport->free_config) {
188             local_ctrl_inst_ctx->config.transport->free_config(&local_ctrl_inst_ctx->config.transport_config);
189         }
190         if (local_ctrl_inst_ctx->pc) {
191             if (local_ctrl_inst_ctx->config.transport->stop_service) {
192                 local_ctrl_inst_ctx->config.transport->stop_service(local_ctrl_inst_ctx->pc);
193             }
194             protocomm_delete(local_ctrl_inst_ctx->pc);
195         }
196         if (local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn) {
197             local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn(
198                 local_ctrl_inst_ctx->config.handlers.usr_ctx);
199         }
200 
201         /* Iterate through all properties one by one and free them */
202         for (uint32_t i = 0; i < local_ctrl_inst_ctx->config.max_properties; i++) {
203             if (local_ctrl_inst_ctx->props[i] == NULL) {
204                 continue;
205             }
206             /* Release memory allocated for property data */
207             free(local_ctrl_inst_ctx->props[i]->name);
208             if (local_ctrl_inst_ctx->props[i]->ctx_free_fn) {
209                 local_ctrl_inst_ctx->props[i]->ctx_free_fn(local_ctrl_inst_ctx->props[i]->ctx);
210             }
211             free(local_ctrl_inst_ctx->props[i]);
212         }
213         free(local_ctrl_inst_ctx->props);
214         free(local_ctrl_inst_ctx);
215         local_ctrl_inst_ctx = NULL;
216     }
217     return ESP_OK;
218 }
219 
esp_local_ctrl_get_property_index(const char * name)220 static int esp_local_ctrl_get_property_index(const char *name)
221 {
222     if (!local_ctrl_inst_ctx || !name) {
223         return -1;
224     }
225 
226     /* Iterate through all properties one by one
227      * and find the one with matching name */
228     for (uint32_t i = 0; i < local_ctrl_inst_ctx->props_count; i++) {
229         if (strcmp(local_ctrl_inst_ctx->props[i]->name, name) == 0) {
230             return i;
231         }
232     }
233     return -1;
234 }
235 
esp_local_ctrl_add_property(const esp_local_ctrl_prop_t * prop)236 esp_err_t esp_local_ctrl_add_property(const esp_local_ctrl_prop_t *prop)
237 {
238     if (!local_ctrl_inst_ctx) {
239         ESP_LOGE(TAG, "Service not running");
240         return ESP_ERR_INVALID_STATE;
241     }
242     if (!prop || !prop->name) {
243         return ESP_ERR_INVALID_ARG;
244     }
245     if (esp_local_ctrl_get_property_index(prop->name) >= 0) {
246         ESP_LOGE(TAG, "Property with name %s exists", prop->name);
247         return ESP_ERR_INVALID_STATE;
248     }
249 
250     if (local_ctrl_inst_ctx->config.max_properties
251         == local_ctrl_inst_ctx->props_count) {
252         ESP_LOGE(TAG, "Max properties limit reached. Cannot add property %s", prop->name);
253         return ESP_ERR_NO_MEM;
254     }
255 
256     uint32_t i = local_ctrl_inst_ctx->props_count;
257     local_ctrl_inst_ctx->props[i] = calloc(1, sizeof(esp_local_ctrl_prop_t));
258     if (!local_ctrl_inst_ctx->props[i]) {
259         ESP_LOGE(TAG, "Failed to allocate memory for new property %s", prop->name);
260         return ESP_ERR_NO_MEM;
261     }
262     local_ctrl_inst_ctx->props[i]->name = strdup(prop->name);
263     if (!local_ctrl_inst_ctx->props[i]->name) {
264         ESP_LOGE(TAG, "Failed to allocate memory for property data %s", prop->name);
265         free(local_ctrl_inst_ctx->props[i]);
266         local_ctrl_inst_ctx->props[i] = NULL;
267         return ESP_ERR_NO_MEM;
268     }
269     local_ctrl_inst_ctx->props[i]->type  = prop->type;
270     local_ctrl_inst_ctx->props[i]->size  = prop->size;
271     local_ctrl_inst_ctx->props[i]->flags = prop->flags;
272     local_ctrl_inst_ctx->props[i]->ctx   = prop->ctx;
273     local_ctrl_inst_ctx->props[i]->ctx_free_fn = prop->ctx_free_fn;
274     local_ctrl_inst_ctx->props_count++;
275     return ESP_OK;
276 }
277 
278 
esp_local_ctrl_remove_property(const char * name)279 esp_err_t esp_local_ctrl_remove_property(const char *name)
280 {
281     int idx = esp_local_ctrl_get_property_index(name);
282     if (idx < 0) {
283         ESP_LOGE(TAG, "Property %s not found", name);
284         return ESP_ERR_NOT_FOUND;
285     }
286 
287     /* Release memory allocated for property data */
288     if (local_ctrl_inst_ctx->props[idx]->ctx_free_fn) {
289         local_ctrl_inst_ctx->props[idx]->ctx_free_fn(
290             local_ctrl_inst_ctx->props[idx]->ctx);
291     }
292     free(local_ctrl_inst_ctx->props[idx]->name);
293     free(local_ctrl_inst_ctx->props[idx]);
294     local_ctrl_inst_ctx->props[idx++] = NULL;
295 
296     /* Move the following properties forward, so that there is
297      * no empty space between two properties */
298     for (uint32_t i = idx; i < local_ctrl_inst_ctx->props_count; i++) {
299         if (local_ctrl_inst_ctx->props[i] == NULL) {
300             break;
301         }
302         local_ctrl_inst_ctx->props[i-1] = local_ctrl_inst_ctx->props[i];
303     }
304     local_ctrl_inst_ctx->props_count--;
305     return ESP_OK;
306 }
307 
esp_local_ctrl_get_property(const char * name)308 const esp_local_ctrl_prop_t *esp_local_ctrl_get_property(const char *name)
309 {
310     int idx = esp_local_ctrl_get_property_index(name);
311     if (idx < 0) {
312         ESP_LOGE(TAG, "Property %s not found", name);
313         return NULL;
314     }
315 
316     return local_ctrl_inst_ctx->props[idx];
317 }
318 
esp_local_ctrl_get_prop_count(size_t * count)319 esp_err_t esp_local_ctrl_get_prop_count(size_t *count)
320 {
321     if (!local_ctrl_inst_ctx) {
322         ESP_LOGE(TAG, "Service not running");
323         return ESP_ERR_INVALID_STATE;
324     }
325     if (!count) {
326         return ESP_ERR_INVALID_ARG;
327     }
328     *count = local_ctrl_inst_ctx->props_count;
329     return ESP_OK;
330 }
331 
esp_local_ctrl_get_prop_values(size_t total_indices,uint32_t * indices,esp_local_ctrl_prop_t * props,esp_local_ctrl_prop_val_t * values)332 esp_err_t esp_local_ctrl_get_prop_values(size_t total_indices, uint32_t *indices,
333                                          esp_local_ctrl_prop_t *props,
334                                          esp_local_ctrl_prop_val_t *values)
335 {
336     if (!local_ctrl_inst_ctx) {
337         ESP_LOGE(TAG, "Service not running");
338         return ESP_ERR_INVALID_STATE;
339     }
340     if (!indices || !props || !values) {
341         return ESP_ERR_INVALID_ARG;
342     }
343 
344     /* Convert indices to names */
345     for (size_t i = 0; i < total_indices; i++) {
346         if (indices[i] >= local_ctrl_inst_ctx->props_count) {
347             ESP_LOGE(TAG, "Invalid property index %d", indices[i]);
348             return ESP_ERR_INVALID_ARG;
349         }
350         props[i].name  = local_ctrl_inst_ctx->props[indices[i]]->name;
351         props[i].type  = local_ctrl_inst_ctx->props[indices[i]]->type;
352         props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
353         props[i].size  = local_ctrl_inst_ctx->props[indices[i]]->size;
354         props[i].ctx   = local_ctrl_inst_ctx->props[indices[i]]->ctx;
355     }
356 
357     esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
358     esp_err_t ret = h->get_prop_values(total_indices, props, values, h->usr_ctx);
359 
360     /* Properties with fixed sizes need to be checked */
361     for (size_t i = 0; i < total_indices; i++) {
362         if (local_ctrl_inst_ctx->props[indices[i]]->size != 0) {
363             values[i].size = local_ctrl_inst_ctx->props[indices[i]]->size;
364         }
365     }
366     return ret;
367 }
368 
esp_local_ctrl_set_prop_values(size_t total_indices,uint32_t * indices,const esp_local_ctrl_prop_val_t * values)369 esp_err_t esp_local_ctrl_set_prop_values(size_t total_indices, uint32_t *indices,
370                                          const esp_local_ctrl_prop_val_t *values)
371 {
372    if (!local_ctrl_inst_ctx) {
373         ESP_LOGE(TAG, "Service not running");
374         return ESP_ERR_INVALID_STATE;
375     }
376     if (!indices || !values) {
377         return ESP_ERR_INVALID_ARG;
378     }
379 
380     esp_local_ctrl_prop_t *props = calloc(total_indices,
381                                           sizeof(esp_local_ctrl_prop_t));
382     if (!props) {
383         ESP_LOGE(TAG, "Unable to allocate memory for properties array");
384         return ESP_ERR_NO_MEM;
385     }
386     for (size_t i = 0; i < total_indices; i++) {
387         if (indices[i] >= local_ctrl_inst_ctx->props_count) {
388             ESP_LOGE(TAG, "Invalid property index %d", indices[i]);
389             free(props);
390             return ESP_ERR_INVALID_ARG;
391         }
392 
393         /* Properties with fixed sizes need to be checked */
394         if ((local_ctrl_inst_ctx->props[indices[i]]->size != values[i].size) &&
395             (local_ctrl_inst_ctx->props[indices[i]]->size != 0)) {
396             ESP_LOGE(TAG, "Invalid property size %d. Expected %d",
397                      values[i].size, local_ctrl_inst_ctx->props[indices[i]]->size);
398             free(props);
399             return ESP_ERR_INVALID_ARG;
400         }
401 
402         props[i].name  = local_ctrl_inst_ctx->props[indices[i]]->name;
403         props[i].type  = local_ctrl_inst_ctx->props[indices[i]]->type;
404         props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
405         props[i].size  = local_ctrl_inst_ctx->props[indices[i]]->size;
406         props[i].ctx   = local_ctrl_inst_ctx->props[indices[i]]->ctx;
407     }
408 
409     esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
410     esp_err_t ret = h->set_prop_values(total_indices, props, values, h->usr_ctx);
411 
412     free(props);
413     return ret;
414 }
415 
esp_local_ctrl_set_handler(const char * ep_name,protocomm_req_handler_t handler,void * priv_data)416 esp_err_t esp_local_ctrl_set_handler(const char *ep_name,
417                                      protocomm_req_handler_t handler,
418                                      void *priv_data)
419 {
420     esp_err_t ret = ESP_ERR_INVALID_STATE;
421 
422     if (local_ctrl_inst_ctx) {
423         ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, ep_name,
424                                      handler, priv_data);
425     }
426 
427     if (ret != ESP_OK) {
428         ESP_LOGE(TAG, "Failed to register endpoint handler");
429     }
430     return ret;
431 }
432