Fix potential memory leak
[apps/agl-service-navigation.git] / binding / navigation-api.c
1 /*
2  * Copyright (C) 2019 Konsulko Group
3  * Author: Matt Ranostay <matt.ranostay@konsulko.com>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/time.h>
24 #include <time.h>
25
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include <gio/gio.h>
29 #include <json-c/json.h>
30
31 #define AFB_BINDING_VERSION 3
32 #include <afb/afb-binding.h>
33
34 #include "navigation-api.h"
35
36 afb_api_t g_api;
37
38 static const char *vshl_capabilities_events[] = {
39         "setDestination",
40         "cancelNavigation",
41         NULL,
42 };
43
44 struct navigation_state *navigation_get_userdata(void)
45 {
46         return afb_api_get_userdata(g_api);
47 }
48
49 static afb_event_t get_event_from_value(struct navigation_state *ns,
50                         const char *value)
51 {
52         if (!g_strcmp0(value, "status"))
53                 return ns->status_event;
54
55         if (!g_strcmp0(value, "position"))
56                 return ns->position_event;
57
58         if (!g_strcmp0(value, "waypoints"))
59                 return ns->waypoints_event;
60
61         return NULL;
62 }
63
64 static json_object **get_storage_from_value(struct navigation_state *ns,
65                         const char *value)
66 {
67         if (!g_strcmp0(value, "status"))
68                 return &ns->status_storage;
69
70         if (!g_strcmp0(value, "position"))
71                 return &ns->position_storage;
72
73         if (!g_strcmp0(value, "waypoints"))
74                 return &ns->waypoints_storage;
75
76         return NULL;
77 }
78
79 static void navigation_subscribe_unsubscribe(afb_req_t request,
80                 gboolean unsub)
81 {
82         struct navigation_state *ns = navigation_get_userdata();
83         json_object *jresp = NULL;
84         const char *value;
85         afb_event_t event;
86         int rc;
87
88         value = afb_req_value(request, "value");
89         if (!value) {
90                 afb_req_fail_f(request, "failed", "Missing \"value\" event");
91                 return;
92         }
93
94         event = get_event_from_value(ns, value);
95         if (!event) {
96                 afb_req_fail_f(request, "failed", "Bad \"value\" event \"%s\"",
97                                 value);
98                 return;
99         }
100
101         if (!unsub) {
102                 json_object *storage;
103                 rc = afb_req_subscribe(request, event);
104
105                 g_rw_lock_reader_lock(&ns->rw_lock);
106                 storage = *get_storage_from_value(ns, value);
107                 if (storage) {
108                         // increment reference counter, and send out cached value
109                         json_object_get(storage);
110                         afb_event_push(event, storage);
111                 }
112                 g_rw_lock_reader_unlock(&ns->rw_lock);
113         } else {
114                 rc = afb_req_unsubscribe(request, event);
115         }
116         if (rc != 0) {
117                 afb_req_fail_f(request, "failed",
118                                         "%s error on \"value\" event \"%s\"",
119                                         !unsub ? "subscribe" : "unsubscribe",
120                                         value);
121                 return;
122         }
123
124         jresp = json_object_new_object();
125         afb_req_success_f(request, jresp, "Navigation %s to event \"%s\"",
126                         !unsub ? "subscribed" : "unsubscribed",
127                         value);
128 }
129
130 static void subscribe(afb_req_t request)
131 {
132         navigation_subscribe_unsubscribe(request, FALSE);
133 }
134
135 static void unsubscribe(afb_req_t request)
136 {
137         navigation_subscribe_unsubscribe(request, TRUE);
138 }
139
140 static void broadcast(json_object *jresp, const char *name, gboolean cache)
141 {
142         struct navigation_state *ns = navigation_get_userdata();
143         afb_event_t event = get_event_from_value(ns, name);
144         json_object *tmp = NULL;
145
146         if (json_object_deep_copy(jresp, (json_object **) &tmp, NULL))
147                 return;
148
149         if (cache) {
150                 json_object **storage;
151
152                 g_rw_lock_writer_lock(&ns->rw_lock);
153
154                 storage = get_storage_from_value(ns, name);
155
156                 if (*storage)
157                         json_object_put(*storage);
158                 *storage = NULL;
159
160                 // increment reference for storage
161                 json_object_get(tmp);
162                 *storage = tmp;
163
164                 // increment reference for event
165                 json_object_get(tmp);
166                 afb_event_push(event, tmp);
167
168                 g_rw_lock_writer_unlock(&ns->rw_lock);
169
170                 return;
171         }
172
173         afb_event_push(event, tmp);
174 }
175
176 static void broadcast_status(afb_req_t request)
177 {
178         json_object *jresp = afb_req_json(request);
179         broadcast(jresp, "status", TRUE);
180
181         afb_req_success(request, NULL, "Broadcast status send");
182
183         // NOTE: If the Alexa SDK API for pushing local navigation
184         //       updates gets exposed, send update to vshl-capabilities
185         //       here.
186 }
187
188 static void broadcast_position(afb_req_t request)
189 {
190         const char *position = afb_req_value(request, "position");
191         gboolean cache = FALSE;
192
193         // only send out a car position event on subscribe
194         if (position && !g_strcmp0(position, "car"))
195                 cache = TRUE;
196
197         json_object *jresp = afb_req_json(request);
198         broadcast(jresp, "position", cache);
199
200         afb_req_success(request, NULL, "Broadcast position send");
201
202         // NOTE: If the Alexa SDK API for pushing local navigation
203         //       updates gets exposed, send update to vshl-capabilities
204         //       here.
205 }
206
207 static void broadcast_waypoints(afb_req_t request)
208 {
209         json_object *jresp = afb_req_json(request);
210         broadcast(jresp, "waypoints", TRUE);
211
212         afb_req_success(request, NULL, "Broadcast waypoints send");
213
214         // NOTE: If the Alexa SDK API for pushing local navigation
215         //       updates gets exposed, send update to vshl-capabilities
216         //       here.
217 }
218
219 static void handle_setDestination_event(struct json_object *object)
220 {
221         json_object *jdest = NULL;
222         json_object_object_get_ex(object, "destination", &jdest);
223         if(!jdest) {
224                 AFB_WARNING("setDestination event missing destination element");
225                 return;
226         }
227
228         json_object *jcoord = NULL;
229         json_object_object_get_ex(jdest, "coordinate", &jcoord);
230         if(!jcoord) {
231                 AFB_WARNING("setDestination event missing coordinate element");
232                 return;
233         }
234
235         json_object *jlat = NULL;
236         json_object_object_get_ex(jcoord, "latitudeInDegrees", &jlat);
237         if(!jlat)
238                 return;
239         errno = 0;
240         double lat = json_object_get_double(jlat);
241         if(errno != 0)
242                 return;
243
244         json_object *jlon = NULL;
245         json_object_object_get_ex(jcoord, "longitudeInDegrees", &jlon);
246         if(!jlon)
247                 return;
248         double lon = json_object_get_double(jlon);
249         if(errno != 0)
250                 return;
251
252         json_object *jobj = json_object_new_object();
253         json_object *jpoints = json_object_new_array();
254         json_object *jpoint = json_object_new_object();
255         jlat = json_object_new_double(lat);
256         jlon = json_object_new_double(lon);
257         json_object_object_add(jpoint, "latitude", jlat);
258         json_object_object_add(jpoint, "longitude", jlon);
259         json_object_array_add(jpoints, jpoint);
260         json_object_object_add(jobj, "points", jpoints);
261         broadcast(jobj, "waypoints", TRUE);
262 }
263
264 static void handle_cancelNavigation_event(struct json_object *object)
265 {
266         json_object *jobj = json_object_new_object();
267         json_object *jstate = json_object_new_string("stop");
268         json_object_object_add(jobj, "state", jstate);
269         broadcast(jobj, "status", TRUE);
270 }
271
272 static void onevent(afb_api_t api, const char *event, struct json_object *object)
273 {
274         if(!event)
275                 return;
276
277         if(strcmp(event, "vshl-capabilities/setDestination") == 0) {
278                 handle_setDestination_event(object);
279         } else if(strcmp(event, "vshl-capabilities/cancelNavigation") == 0) {
280                 handle_cancelNavigation_event(object);
281         } else {
282                 AFB_WARNING("Unhandled vshl-capabilities event");
283         }
284 }
285
286 static int init(afb_api_t api)
287 {
288         struct navigation_state *ns;
289         int rc;
290
291         ns = g_try_malloc0(sizeof(*ns));
292         if (!ns) {
293                 AFB_ERROR("out of memory allocating navigation state");
294                 return -ENOMEM;
295         }
296
297         rc = afb_daemon_require_api("vshl-capabilities", 1);
298         if (!rc) {
299                 const char **tmp = vshl_capabilities_events;
300                 json_object *args = json_object_new_object();
301                 json_object *actions = json_object_new_array();
302
303                 while (*tmp) {
304                         json_object_array_add(actions, json_object_new_string(*tmp++));
305                 }
306                 json_object_object_add(args, "actions", actions);
307                 if(json_object_array_length(actions)) {
308                         rc = afb_api_call_sync(api, "vshl-capabilities", "navigation/subscribe",
309                                                args, NULL, NULL, NULL);
310                         if(rc != 0)
311                                 AFB_WARNING("afb_api_call_sync returned %d", rc);
312                 } else {
313                         json_object_put(args);
314                 }
315         } else {
316                 AFB_WARNING("unable to initialize vshl-capabilities binding");
317         }
318
319         ns->status_event = afb_daemon_make_event("status");
320         ns->position_event = afb_daemon_make_event("position");
321         ns->waypoints_event = afb_daemon_make_event("waypoints");
322
323         if (!afb_event_is_valid(ns->status_event) ||
324             !afb_event_is_valid(ns->position_event) ||
325             !afb_event_is_valid(ns->waypoints_event)) {
326                 AFB_ERROR("Cannot create events");
327                 return -EINVAL;
328         }
329
330         afb_api_set_userdata(api, ns);
331         g_api = api;
332
333         g_rw_lock_init(&ns->rw_lock);
334
335         return 0;
336 }
337
338 static const afb_verb_t binding_verbs[] = {
339         {
340                 .verb = "subscribe",
341                 .callback = subscribe,
342                 .info = "Subscribe to event"
343         }, {
344                 .verb = "unsubscribe",
345                 .callback = unsubscribe,
346                 .info = "Unsubscribe to event"
347         }, {
348                 .verb = "broadcast_status",
349                 .callback = broadcast_status,
350                 .info = "Allows clients to broadcast status events"
351         }, {
352                 .verb = "broadcast_position",
353                 .callback = broadcast_position,
354                 .info = "Broadcast out position event"
355         }, {
356                 .verb = "broadcast_waypoints",
357                 .callback = broadcast_waypoints,
358                 .info = "Broadcast out waypoint event"
359         },
360         {}
361 };
362
363 /*
364  * description of the binding for afb-daemon
365  */
366 const afb_binding_t afbBindingV3 = {
367         .api = "navigation",
368         .verbs = binding_verbs,
369         .onevent = onevent,
370         .init = init,
371 };