Fix warnings due to json-c evolution
[src/app-framework-binder.git] / src / devtools / genskel.c
1 /*
2  * Copyright (C) 2016, 2017, 2018 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
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  * This simple program expands the object { "$ref": "#/path/to/a/target" }
19  *
20  * For example:
21  *
22  *  {
23  *    "type":{
24  *      "a": "int",
25  *      "b": { "$ref": "#/type/a" }
26  *    }
27  *  }
28  *
29  * will be exapanded to
30  *
31  *  {
32  *    "type":{
33  *      "a": "int",
34  *      "b": "int"
35  *    }
36  *  }
37  *
38  * Invocation:   program  [file|-]...
39  *
40  * without arguments, it reads the input.
41  */
42
43 #define _GNU_SOURCE
44 #include <string.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <ctype.h>
48
49 #include <json-c/json.h>
50
51 #define oom(x) do{if(!(x)){fprintf(stderr,"out of memory\n");exit(1);}}while(0)
52
53 /**
54  * records path to the expanded node
55  */
56 struct path
57 {
58         struct json_object *object;     /**< node being expanded */
59         struct path *upper;             /**< link to upper expanded nodes */
60 };
61
62 /**
63  * root of the JSON being parsed
64  */
65 int version = 3;
66 struct json_object *root = NULL;
67 struct json_object *d_perms = NULL;
68 struct json_object *a_perms = NULL;
69 const char *preinit = NULL;
70 const char *init = NULL;
71 const char *onevent = NULL;
72 const char *api = NULL;
73 const char *scope = NULL;
74 const char *prefix = NULL;
75 const char *postfix = NULL;
76 char *capi = NULL;
77 int priv = -1;
78 int noconc = -1;
79 int cpp = 0;
80
81 /**
82  * Search for a reference of type "#/a/b/c" in the
83  * parsed JSON object (root)
84  */
85 struct json_object *search(const char *path)
86 {
87         char *d;
88         struct json_object *i;
89
90         /* does it match #/ at the beginning? */
91         if (path[0] != '#' || (path[0] && path[1] != '/'))
92                 return NULL;
93
94         /* search from root to target */
95         i = root;
96         d = strdupa(path+2);
97         d = strtok(d, "/");
98         while(i && d) {
99                 if (!json_object_object_get_ex(i, d, &i))
100                         return NULL;
101                 d = strtok(NULL, "/");
102         }
103         return i;
104 }
105
106 /**
107  * Expands the node designated by path and returns its expanded form
108  */
109 struct json_object *expand_$ref(struct path path)
110 {
111         struct path *p;
112         struct json_object *o, *x;
113         int n, i;
114         struct json_object_iterator ji, jn;
115
116         /* expansion depends of the type of the node */
117         switch (json_object_get_type(path.object)) {
118         case json_type_object:
119                 /* for object, look if it contains a property "$ref" */
120                 if (json_object_object_get_ex(path.object, "$ref", &o)) {
121                         /* yes, reference, try to substitute its target */
122                         if (!json_object_is_type(o, json_type_string)) {
123                                 fprintf(stderr, "found a $ref not being string. Is: %s\n", json_object_get_string(o));
124                                 exit(1);
125                         }
126                         x = search(json_object_get_string(o));
127                         if (!x) {
128                                 fprintf(stderr, "$ref not found. Was: %s\n", json_object_get_string(o));
129                                 exit(1);
130                         }
131                         p = &path;
132                         while(p) {
133                                 if (x == p->object) {
134                                         fprintf(stderr, "$ref recursive. Was: %s\n", json_object_get_string(o));
135                                         exit(1);
136                                 }
137                                 p = p->upper;
138                         }
139                         /* cool found, return a new instance of the target */
140                         return json_object_get(x);
141                 }
142                 /* no, expand the values */
143                 ji = json_object_iter_begin(path.object);
144                 jn = json_object_iter_end(path.object);
145                 while (!json_object_iter_equal(&ji, &jn)) {
146                         o = json_object_iter_peek_value(&ji);
147                         x = expand_$ref((struct path){ .object = o, .upper = &path });
148                         if (x != o)
149                                 json_object_object_add(path.object, json_object_iter_peek_name(&ji), x);
150                         json_object_iter_next(&ji);
151                 }
152                 break;
153         case json_type_array:
154                 /* expand the values of arrays */
155                 i = 0;
156                 n = (int)json_object_array_length(path.object);
157                 while (i != n) {
158                         o = json_object_array_get_idx(path.object, i);
159                         x = expand_$ref((struct path){ .object = o, .upper = &path });
160                         if (x != o)
161                                 json_object_array_put_idx(path.object, i, x);
162                         i++;
163                 }
164                 break;
165         default:
166                 /* otherwise no expansion */
167                 break;
168         }
169
170         /* return the given node */
171         return path.object;
172 }
173
174 /* create c name by replacing non alpha numeric characters with underscores */
175 char *cify(const char *str)
176 {
177         char *r = strdup(str);
178         int i = 0;
179         while (r && r[i]) {
180                 if (!isalnum(r[i]))
181                         r[i] = '_';
182                 i++;
183         }
184         return r;
185 }
186
187 /* format the specification as a C string */
188 char *make_info(const char *text, int split)
189 {
190         const char *a, *b;
191         char *desc, c, buf[3];
192         size_t len;
193         int i, pos, e;
194
195         /* estimated length */
196         a = b = text;
197         len = 1;
198         while((c = *b++)) {
199                 len += 1 + ('"' == c);
200         }
201
202         len += 7 * (1 + len / 72);
203         desc = malloc(len);
204         oom(desc);
205
206         len = pos = 0;
207         if (!split)
208                 desc[len++] = '"';
209         b = a;
210         while((c = *b++)) {
211                 if (c == '"') {
212                         buf[0] = '\\';
213                         buf[1] = '"';
214                         buf[2] = 0;
215                 }
216                 else if (c == '\\') {
217                         switch ((c = *b++)) {
218                         case 0:
219                                 b--;
220                                 break;
221                         case '/':
222                                 buf[0] = '/';
223                                 buf[1] = 0;
224                                 break;
225                         default:
226                                 buf[0] = '\\';
227                                 buf[1] = c;
228                                 buf[2] = 0;
229                                 break;
230                         }
231                 }
232                 else {
233                         buf[0] = c;
234                         buf[1] = 0;
235                 }
236                 i = e = 0;
237                 while (buf[i]) {
238                         if (split) {
239                                 if (pos >= 77 && !e) {
240                                         desc[len++] = '"';
241                                         desc[len++] = '\n';
242                                         pos = 0;
243                                 }
244                                 if (pos == 0) {
245                                         desc[len++] = ' ';
246                                         desc[len++] = ' ';
247                                         desc[len++] = ' ';
248                                         desc[len++] = ' ';
249                                         desc[len++] = '"';
250                                         pos = 5;
251                                 }
252                         }
253                         c = buf[i++];
254                         desc[len++] = c;
255                         e = !e && c == '\\';
256                         pos++;
257                 }
258         }
259         desc[len++] = '"';
260         if (split)
261                 desc[len++] = '\n';
262         desc[len] = 0;
263         return desc;
264 }
265
266 /* make the description of the object */
267 char *make_desc(struct json_object *o)
268 {
269         return make_info(json_object_to_json_string_ext(o, 0), 1);
270 }
271
272 /* get the permission odescription if set */
273 struct json_object *permissions_of_verb(struct json_object *obj)
274 {
275         struct json_object *x, *y;
276
277         if (json_object_object_get_ex(obj, "x-permissions", &x))
278                 return x;
279
280         if (json_object_object_get_ex(obj, "get", &x))
281                 if (json_object_object_get_ex(x, "x-permissions", &y))
282                         return y;
283
284         return NULL;
285 }
286
287 /* output the array of permissions */
288 void print_perms()
289 {
290         int i, n;
291         const char *fmtstr = cpp ? "\t%s" : "\t{ %s }";
292
293         n = a_perms ? (int)json_object_array_length(a_perms) : 0;
294         if (n) {
295                 printf("static const struct afb_auth _afb_auths_%s[] = {\n" , capi);
296                 i = 0;
297                 while (i < n) {
298                         printf(fmtstr, json_object_get_string(json_object_array_get_idx(a_perms, i)));
299                         printf(",\n"+(++i == n));
300                 }
301                 printf("};\n\n");
302         }
303 }
304
305 /*
306  * search in the global object 'd_perm' the computed representation
307  * of the permission described either by 'obj' or 'desc'
308  */
309 struct json_object *new_perm(struct json_object *obj, const char *desc)
310 {
311         const char *tag;
312         char *b;
313         struct json_object *x, *y;
314
315         tag = obj ? json_object_to_json_string_ext(obj, 0) : desc;
316         if (!json_object_object_get_ex(d_perms, tag, &y)) {
317
318                 /* creates the d_perms dico and the a_perms array */
319                 if (!d_perms) {
320                         d_perms = json_object_new_object();
321                         a_perms = json_object_new_array();
322                 }
323
324                 /* creates the reference in the structure */
325                 asprintf(&b, "&_afb_auths_%s[%d]", capi, (int)json_object_array_length(a_perms));
326                 x = json_object_new_string(desc);
327                 y = json_object_new_string(b);
328                 json_object_array_add(a_perms, x);
329                 json_object_object_add(d_perms, tag, y);
330                 free(b);
331         }
332         return y;
333 }
334
335 struct json_object *decl_perm(struct json_object *obj);
336
337 enum optype { And, Or };
338
339 /* recursive declare and/or permissions */
340 struct json_object *decl_perm_a(enum optype op, struct json_object *obj)
341 {
342         int i, n;
343         char *a;
344         const char *opstr, *fmtstr;
345         struct json_object *x, *y;
346
347         if (cpp) {
348                 fmtstr = "afb::auth_%s(%s, %s)";
349                 opstr = op==And ? "and" : "or";
350         } else {
351                 fmtstr = ".type = afb_auth_%s, .first = %s, .next = %s";
352                 opstr = op==And ? "And" : "Or";
353         }
354         x = NULL;
355         i = n = obj ? (int)json_object_array_length(obj) : 0;
356         while (i) {
357                 y = decl_perm(json_object_array_get_idx(obj, --i));
358                 if (!y)
359                         ;
360                 else if (!x)
361                         x = y;
362                 else if (x != y) {
363                         asprintf(&a, fmtstr, opstr, json_object_get_string(y), json_object_get_string(x));
364                         x = new_perm(NULL, a);
365                         free(a);
366                 }
367         }
368         return x;
369 }
370
371 /* declare the permission for obj */
372 struct json_object *decl_perm(struct json_object *obj)
373 {
374         char *a;
375         const char *fmt;
376         struct json_object *x, *y;
377
378         if (json_object_object_get_ex(d_perms, json_object_to_json_string_ext(obj, 0), &x))
379                 return x;
380
381         if (json_object_object_get_ex(obj, "permission", &x)) {
382                 if (cpp)
383                         fmt = "afb::auth_permission(\"%s\")";
384                 else
385                         fmt = ".type = afb_auth_Permission, .text = \"%s\"";
386                 asprintf(&a, fmt, json_object_get_string(x));
387                 y = new_perm(obj, a);
388                 free(a);
389         }
390         else if (json_object_object_get_ex(obj, "anyOf", &x)) {
391                 y = decl_perm_a(Or, x);
392         }
393         else if (json_object_object_get_ex(obj, "allOf", &x)) {
394                 y = decl_perm_a(And, x);
395         }
396         else if (json_object_object_get_ex(obj, "not", &x)) {
397                 x = decl_perm(x);
398                 if (cpp)
399                         fmt = "afb::auth_not(%s)";
400                 else
401                         fmt = ".type = afb_auth_Not, .first = %s";
402                 asprintf(&a, fmt, json_object_get_string(x));
403                 y = new_perm(obj, a);
404                 free(a);
405         }
406         else if (json_object_object_get_ex(obj, "LOA", &x))
407                 y = NULL;
408         else if (json_object_object_get_ex(obj, "session", &x))
409                 y = NULL;
410         else
411                 y = NULL;
412
413         return y;
414 }
415
416 void declare_permissions(const char *name, struct json_object *obj)
417 {
418         struct json_object *p;
419
420         p = permissions_of_verb(obj);
421         if (p)
422                 decl_perm(p);
423 }
424
425
426 #define SESSION_CLOSE  0x000001
427 #define SESSION_RENEW  0x000010
428 #define SESSION_CHECK  0x000100
429 #define SESSION_LOA_1  0x001000
430 #define SESSION_LOA_2  0x011000
431 #define SESSION_LOA_3  0x111000
432 #define SESSION_MASK   0x111111
433
434
435 int get_session(struct json_object *obj);
436
437 int get_session_a(int and, struct json_object *obj)
438 {
439         int i, n, x, y;
440
441         n = obj ? (int)json_object_array_length(obj) : 0;
442         if (n == 0)
443                 return 0;
444
445         i = n;
446         x = get_session(json_object_array_get_idx(obj, --i));
447         while (i) {
448                 y = get_session(json_object_array_get_idx(obj, --i));
449                 if (and)
450                         x &= y;
451                 else
452                         x |= y;
453         }
454         return x;
455 }
456
457 int get_session(struct json_object *obj)
458 {
459         int y;
460         const char *a;
461         struct json_object *x;
462
463         y = 0;
464         if (json_object_object_get_ex(obj, "anyOf", &x)) {
465                 y = get_session_a(1, x);
466         }
467         else if (json_object_object_get_ex(obj, "allOf", &x)) {
468                 y = get_session_a(0, x);
469         }
470         else if (json_object_object_get_ex(obj, "not", &x)) {
471                 y = ~get_session(x) & SESSION_MASK;
472         }
473         else if (json_object_object_get_ex(obj, "LOA", &x)) {
474                 switch (json_object_get_int(x)) {
475                 case 3: y = SESSION_LOA_3; break;
476                 case 2: y = SESSION_LOA_2; break;
477                 case 1: y = SESSION_LOA_1; break;
478                 default: break;
479                 }
480         }
481         else if (json_object_object_get_ex(obj, "session", &x)) {
482                 a = json_object_get_string(x);
483                 if (!strcmp(a, "check"))
484                         y = SESSION_CHECK;
485                 else if (!strcmp(a, "close"))
486                         y = SESSION_CLOSE;
487         }
488         else if (json_object_object_get_ex(obj, "token", &x)) {
489                 a = json_object_get_string(x);
490                 if (!strcmp(a, "refresh"))
491                         y = SESSION_RENEW;
492         }
493
494         return y;
495 }
496
497 void print_session(struct json_object *p)
498 {
499         int s, c, l;
500
501         s = p ? get_session(p) : 0;
502         c = 1;
503         if (s & SESSION_CHECK) {
504                 printf("%s", "|AFB_SESSION_CHECK" + c);
505                 c = 0;
506         }
507         if (s & SESSION_LOA_3 & ~SESSION_LOA_2)
508                 l = 3;
509         else if (s & SESSION_LOA_2 & ~SESSION_LOA_1)
510                 l = 2;
511         else if (s & SESSION_LOA_1)
512                 l = 1;
513         else
514                 l = 0;
515         if (l) {
516                 printf("%s%d", "|AFB_SESSION_LOA_" + c, l);
517                 c = 0;
518         }
519         if (s & SESSION_CLOSE) {
520                 printf("%s", "|AFB_SESSION_CLOSE" + c);
521                 c = 0;
522         }
523         if (s & SESSION_RENEW) {
524                 printf("%s", "|AFB_SESSION_REFRESH" + c);
525                 c = 0;
526         }
527         if (c)
528                 printf("AFB_SESSION_NONE");
529 }
530
531 void print_verb(const char *name)
532 {
533         printf("%s%s%s" , prefix, name, postfix);
534 }
535
536 void print_declare_verb(const char *name, struct json_object *obj)
537 {
538         printf("%s void ", scope);
539         print_verb(name);
540         printf("(afb_req req);\n");
541 }
542
543 void print_struct_verb(const char *name, struct json_object *obj)
544 {
545         struct json_object *p, *i;
546         const char *info;
547
548         info = NULL;
549         if (json_object_object_get_ex(obj, "description", &i))
550                 info = json_object_get_string(i);
551
552         p = permissions_of_verb(obj);
553         printf(
554                 "    {\n"
555                 "        .verb = \"%s\",\n"
556                 "        .callback = "
557                 , name
558         );
559         print_verb(name);
560         printf(
561                 ",\n"
562                 "        .auth = %s,\n"
563                 "        .info = %s,\n"
564                 "        .session = "
565                 , p && decl_perm(p) ? json_object_get_string(decl_perm(p)) : "NULL"
566                 , info ? make_info(info, 0) : "NULL"
567         );
568         print_session(p);
569         if (version == 3)
570                 printf(
571                         ",\n"
572                         "        .vcbdata = NULL,\n"
573                         "        .glob = 0"
574                 );
575         printf(
576                 "\n"
577                 "    },\n"
578         );
579 }
580
581 void enum_verbs(void (*func)(const char *name, struct json_object *obj))
582 {
583         struct json_object_iterator ji, jn;
584         struct json_object *paths, *obj;
585         const char *name;
586
587         /* search the verbs */
588         paths = search("#/paths");
589         if (!paths)
590                 return;
591
592         /* list the verbs and sort it */
593         ji = json_object_iter_begin(paths);
594         jn = json_object_iter_end(paths);
595         while (!json_object_iter_equal(&ji, &jn)) {
596                 name = json_object_iter_peek_name(&ji);
597                 obj = json_object_iter_peek_value(&ji);
598                 name += (*name == '/');
599                 func(name, obj);
600                 json_object_iter_next(&ji);
601         }
602 }
603
604 void getvarbool(int *var, const char *path, int defval)
605 {
606         struct json_object *o;
607
608         if (*var != 0 && *var != 1) {
609                 o = search(path);
610                 if (o && json_object_is_type(o, json_type_boolean))
611                         *var = json_object_get_boolean(o);
612                 else
613                         *var = !!defval;
614         }
615 }
616
617 void getvar(const char **var, const char *path, const char *defval)
618 {
619         struct json_object *o;
620
621         if (!*var) {
622                 o = search(path);
623                 if (o && json_object_is_type(o, json_type_string))
624                         *var = json_object_get_string(o);
625                 else
626                         *var = defval;
627         }
628 }
629
630 /**
631  * process a file and prints its expansion on stdout
632  */
633 void process(char *filename)
634 {
635         char *desc;
636         const char *info;
637
638         /* translate - */
639         if (!strcmp(filename, "-"))
640                 filename = "/dev/stdin";
641
642         /* check access */
643         if (access(filename, R_OK)) {
644                 fprintf(stderr, "can't access file %s\n", filename);
645                 exit(1);
646         }
647
648         /* read the file */
649         root = json_object_from_file(filename);
650         if (!root) {
651                 fprintf(stderr, "reading file %s produced null\n", filename);
652                 exit(1);
653         }
654
655         /* create the description */
656         desc = make_desc(root);
657
658         /* expand references */
659         root = expand_$ref((struct path){ .object = root, .upper = NULL });
660
661         /* get some names */
662         getvar(&api, "#/info/x-binding-c-generator/api", NULL);
663         getvar(&preinit, "#/info/x-binding-c-generator/preinit", NULL);
664         getvar(&init, "#/info/x-binding-c-generator/init", NULL);
665         getvar(&onevent, "#/info/x-binding-c-generator/onevent", NULL);
666         getvar(&scope, "#/info/x-binding-c-generator/scope", "static");
667         getvar(&prefix, "#/info/x-binding-c-generator/prefix", "afb_verb_");
668         getvar(&postfix, "#/info/x-binding-c-generator/postfix", "_cb");
669         getvarbool(&priv, "#/info/x-binding-c-generator/private", 0);
670         getvarbool(&noconc, "#/info/x-binding-c-generator/noconcurrency", 0);
671         getvar(&api, "#/info/title", "?");
672         info = NULL;
673         getvar(&info, "#/info/description", NULL);
674         capi = cify(api);
675
676         /* get the API name */
677         printf(
678                 "\n"
679                 "static const char _afb_description_%s[] =\n"
680                 "%s"
681                 ";\n"
682                 "\n"
683                 , capi, desc
684         );
685         enum_verbs(declare_permissions);
686         print_perms();
687         enum_verbs(print_declare_verb);
688         printf(
689                 "\n"
690                 "static const struct afb_verb_v%d _afb_verbs_%s[] = {\n"
691                 , version, capi
692         );
693         enum_verbs(print_struct_verb);
694         printf(
695                 "    {\n"
696                 "        .verb = NULL,\n"
697                 "        .callback = NULL,\n"
698                 "        .auth = NULL,\n"
699                 "        .info = NULL,\n"
700                 "        .session = 0"
701         );
702         if (version == 3)
703                 printf(
704                         ",\n"
705                         "        .vcbdata = NULL,\n"
706                         "        .glob = 0"
707                 );
708         printf(
709                 "\n"
710                 "       }\n"
711                 "};\n"
712         );
713         printf(
714                 "\n"
715                 "%sconst struct afb_binding_v%d %s%s = {\n"
716                 "    .api = \"%s\",\n"
717                 "    .specification = _afb_description_%s,\n"
718                 "    .info = %s,\n"
719                 "    .verbs = _afb_verbs_%s,\n"
720                 "    .preinit = %s,\n"
721                 "    .init = %s,\n"
722                 "    .onevent = %s,\n"
723                 "%s"
724                 "    .noconcurrency = %d\n"
725                 "};\n"
726                 "\n"
727                 , priv ? "static " : ""
728                 , version
729                 , priv ? "_afb_binding_" : version==3 ? "afbBindingV3" : "afbBindingV2"
730                 , priv ? capi : ""
731                 , api
732                 , capi
733                 , info ? make_info(info, 0) : "NULL"
734                 , capi
735                 , preinit ?: "NULL"
736                 , init ?: "NULL"
737                 , onevent ?: "NULL"
738                 , version==3 ? "    .userdata = NULL,\n" : ""
739                 , !!noconc
740         );
741
742         /* clean up */
743         json_object_put(root);
744         free(desc);
745 }
746
747 /** process the list of files or stdin if none */
748 int main(int ac, char **av)
749 {
750         int r, w;
751         av++;
752
753         r = w = 0;
754         while (av[r]) {
755                 if (!(strcmp(av[r], "-x") && strcmp(av[r], "--cpp"))) {
756                         cpp = 1;
757                         r++;
758                 } else if (!strcmp(av[r], "-2")) {
759                         version = 2;
760                         r++;
761                 } else if (!strcmp(av[r], "-3")) {
762                         version = 3;
763                         r++;
764                 } else {
765                         av[w++] = av[r++];
766                 }
767         }
768         av[w] = NULL;
769         if (!*av)
770                 process("-");
771         else {
772                 do { process(*av++); } while(*av);
773         }
774         return 0;
775 }
776