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