Reimplement output-json.c using json.c from bgpctl.
authorclaudio <claudio@openbsd.org>
Thu, 27 Apr 2023 07:57:25 +0000 (07:57 +0000)
committerclaudio <claudio@openbsd.org>
Thu, 27 Apr 2023 07:57:25 +0000 (07:57 +0000)
Much rejoice from tb@ and job@
OK tb@

usr.sbin/rpki-client/Makefile
usr.sbin/rpki-client/json.c [new file with mode: 0644]
usr.sbin/rpki-client/json.h [new file with mode: 0644]
usr.sbin/rpki-client/output-json.c

index 153f1bf..13a5a71 100644 (file)
@@ -1,8 +1,8 @@
-#      $OpenBSD: Makefile,v 1.29 2022/12/15 12:02:29 claudio Exp $
+#      $OpenBSD: Makefile,v 1.30 2023/04/27 07:57:25 claudio Exp $
 
 PROG=  rpki-client
 SRCS=  as.c aspa.c cert.c cms.c crl.c encoding.c filemode.c gbr.c geofeed.c \
-       http.c io.c ip.c log.c main.c mft.c mkdir.c ometric.c output.c \
+       http.c io.c ip.c json.c log.c main.c mft.c mkdir.c ometric.c output.c \
        output-bgpd.c output-bird.c output-csv.c output-json.c \
        output-ometric.c parser.c print.c repo.c roa.c rrdp.c rrdp_delta.c \
        rrdp_notification.c rrdp_snapshot.c rrdp_util.c rsc.c rsync.c tak.c \
diff --git a/usr.sbin/rpki-client/json.c b/usr.sbin/rpki-client/json.c
new file mode 100644 (file)
index 0000000..6e06564
--- /dev/null
@@ -0,0 +1,252 @@
+/*     $OpenBSD: json.c,v 1.1 2023/04/27 07:57:25 claudio Exp $ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "json.h"
+
+#define JSON_MAX_STACK 16
+
+enum json_type {
+       NONE,
+       START,
+       ARRAY,
+       OBJECT
+};
+
+static struct json_stack {
+       const char      *name;
+       unsigned int    count;
+       enum json_type  type;
+} stack[JSON_MAX_STACK];
+
+static char indent[JSON_MAX_STACK + 1];
+static int level;
+static int eb;
+static FILE *jsonfh;
+
+static void
+do_comma_indent(void)
+{
+       if (stack[level].count++ > 0)
+               if (!eb)
+                       eb = fprintf(jsonfh, ",\n") < 0;
+       if (!eb)
+               eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0;
+}
+
+static void
+do_name(const char *name)
+{
+       if (stack[level].type == ARRAY)
+               return;
+       if (!eb)
+               eb = fprintf(jsonfh, "\"%s\": ", name) < 0;
+}
+
+static int
+do_find(enum json_type type, const char *name)
+{
+       int i;
+
+       for (i = level; i > 0; i--)
+               if (type == stack[i].type &&
+                   strcmp(name, stack[i].name) == 0)
+                       return i;
+
+       /* not found */
+       return -1;
+}
+
+void
+json_do_start(FILE *fh)
+{
+       memset(indent, '\t', JSON_MAX_STACK);
+       memset(stack, 0, sizeof(stack));
+       level = 0;
+       stack[level].type = START;
+       jsonfh = fh;
+       eb = 0;
+
+       eb = fprintf(jsonfh, "{\n") < 0;
+}
+
+int
+json_do_finish(void)
+{
+       while (level > 0)
+               json_do_end();
+       if (!eb)
+               eb = fprintf(jsonfh, "\n}\n") < 0;
+
+       return -eb;
+}
+
+void
+json_do_array(const char *name)
+{
+       int i, l;
+
+       if ((l = do_find(ARRAY, name)) > 0) {
+               /* array already in use, close element and move on */
+               for (i = level - l; i > 0; i--)
+                       json_do_end();
+               return;
+       }
+       /* Do not stack arrays, while allowed this is not needed */
+       if (stack[level].type == ARRAY)
+               json_do_end();
+
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "[\n") < 0;
+
+       if (++level >= JSON_MAX_STACK)
+               errx(1, "json stack too deep");
+
+       stack[level].name = name;
+       stack[level].type = ARRAY;
+       stack[level].count = 0;
+}
+
+void
+json_do_object(const char *name)
+{
+       int i, l;
+
+       if ((l = do_find(OBJECT, name)) > 0) {
+               /* roll back to that object and close it */
+               for (i = level - l; i >= 0; i--)
+                       json_do_end();
+       }
+
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "{\n") < 0;
+
+       if (++level >= JSON_MAX_STACK)
+               errx(1, "json stack too deep");
+
+       stack[level].name = name;
+       stack[level].type = OBJECT;
+       stack[level].count = 0;
+}
+
+void
+json_do_end(void)
+{
+       if (stack[level].type == ARRAY) {
+               if (!eb)
+                       eb = fprintf(jsonfh, "\n%.*s]", level, indent) < 0;
+       } else if (stack[level].type == OBJECT) {
+               if (!eb)
+                       eb = fprintf(jsonfh, "\n%.*s}", level, indent) < 0;
+       } else {
+               errx(1, "json bad stack state");
+       }
+       stack[level].name = NULL;
+       stack[level].type = NONE;
+       stack[level].count = 0;
+
+       if (level-- <= 0)
+               errx(1, "json stack underflow");
+
+       stack[level].count++;
+}
+
+void
+json_do_printf(const char *name, const char *fmt, ...)
+{
+       va_list ap;
+
+       do_comma_indent();
+
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "\"") < 0;
+       va_start(ap, fmt);
+       if (!eb)
+               eb = vfprintf(jsonfh, fmt, ap) < 0;
+       va_end(ap);
+       if (!eb)
+               eb = fprintf(jsonfh, "\"") < 0;
+}
+
+void
+json_do_hexdump(const char *name, void *buf, size_t len)
+{
+       uint8_t *data = buf;
+       size_t i;
+
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "\"") < 0;
+       for (i = 0; i < len; i++)
+               if (!eb)
+                       eb = fprintf(jsonfh, "%02x", *(data + i)) < 0;
+       if (!eb)
+               eb = fprintf(jsonfh, "\"") < 0;
+}
+
+void
+json_do_bool(const char *name, int v)
+{
+       do_comma_indent();
+       do_name(name);
+       if (v) {
+               if (!eb)
+                       eb = fprintf(jsonfh, "true") < 0;
+       } else {
+               if (!eb)
+                       eb = fprintf(jsonfh, "false") < 0;
+       }
+}
+
+void
+json_do_uint(const char *name, unsigned long long v)
+{
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "%llu", v) < 0;
+}
+
+void
+json_do_int(const char *name, long long v)
+{
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "%lld", v) < 0;
+}
+
+void
+json_do_double(const char *name, double v)
+{
+       do_comma_indent();
+       do_name(name);
+       if (!eb)
+               eb = fprintf(jsonfh, "%f", v) < 0;
+}
diff --git a/usr.sbin/rpki-client/json.h b/usr.sbin/rpki-client/json.h
new file mode 100644 (file)
index 0000000..bd7cbf2
--- /dev/null
@@ -0,0 +1,33 @@
+/*     $OpenBSD: json.h,v 1.1 2023/04/27 07:57:25 claudio Exp $ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+void   json_do_start(FILE *);
+int    json_do_finish(void);
+void   json_do_array(const char *);
+void   json_do_object(const char *);
+void   json_do_end(void);
+void   json_do_printf(const char *, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+void   json_do_hexdump(const char *, void *, size_t);
+void   json_do_bool(const char *, int);
+void   json_do_uint(const char *, unsigned long long);
+void   json_do_int(const char *, long long);
+void   json_do_double(const char *, double);
index 7dc9e46..f3a788c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: output-json.c,v 1.35 2023/04/26 18:34:40 job Exp $ */
+/*     $OpenBSD: output-json.c,v 1.36 2023/04/27 07:57:25 claudio Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  *
 #include <netdb.h>
 
 #include "extern.h"
+#include "json.h"
 
-static int
-outputheader_json(FILE *out, struct stats *st)
+static void
+outputheader_json(struct stats *st)
 {
        char             hn[NI_MAXHOST], tbuf[26];
        struct tm       *tp;
@@ -36,161 +37,90 @@ outputheader_json(FILE *out, struct stats *st)
 
        gethostname(hn, sizeof hn);
 
-       if (fprintf(out,
-           "{\n\t\"metadata\": {\n"
-           "\t\t\"buildmachine\": \"%s\",\n"
-           "\t\t\"buildtime\": \"%s\",\n"
-           "\t\t\"elapsedtime\": \"%lld\",\n"
-           "\t\t\"usertime\": \"%lld\",\n"
-           "\t\t\"systemtime\": \"%lld\",\n"
-           "\t\t\"roas\": %u,\n"
-           "\t\t\"failedroas\": %u,\n"
-           "\t\t\"invalidroas\": %u,\n"
-           "\t\t\"aspas\": %u,\n"
-           "\t\t\"failedaspas\": %u,\n"
-           "\t\t\"invalidaspas\": %u,\n"
-           "\t\t\"bgpsec_pubkeys\": %u,\n"
-           "\t\t\"certificates\": %u,\n"
-           "\t\t\"invalidcertificates\": %u,\n"
-           "\t\t\"taks\": %u,\n"
-           "\t\t\"tals\": %u,\n"
-           "\t\t\"invalidtals\": %u,\n"
-           "\t\t\"talfiles\": [\n",
-           hn, tbuf, (long long)st->elapsed_time.tv_sec,
-           (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
-           st->repo_tal_stats.roas,
-           st->repo_tal_stats.roas_fail,
-           st->repo_tal_stats.roas_invalid,
-           st->repo_tal_stats.aspas,
-           st->repo_tal_stats.aspas_fail,
-           st->repo_tal_stats.aspas_invalid,
-           st->repo_tal_stats.brks,
-           st->repo_tal_stats.certs,
-           st->repo_tal_stats.certs_fail,
-           st->repo_tal_stats.taks,
-           st->tals, talsz - st->tals) < 0)
-               return -1;
-
-       for (i = 0; i < talsz; i++) {
-               if (fprintf(out,
-                   "\t\t\t\"%s\"%s\n",
-                   tals[i], i == talsz - 1 ? "" : ",") < 0)
-                       return -1;
-       }
-
-       if (fprintf(out,
-           "\t\t],\n"
-           "\t\t\"manifests\": %u,\n"
-           "\t\t\"failedmanifests\": %u,\n"
-           "\t\t\"stalemanifests\": %u,\n"
-           "\t\t\"crls\": %u,\n"
-           "\t\t\"gbrs\": %u,\n"
-           "\t\t\"repositories\": %u,\n"
-           "\t\t\"vrps\": %u,\n"
-           "\t\t\"uniquevrps\": %u,\n"
-           "\t\t\"vaps\": %u,\n"
-           "\t\t\"uniquevaps\": %u,\n"
-           "\t\t\"cachedir_del_files\": %u,\n"
-           "\t\t\"cachedir_superfluous_files\": %u,\n"
-           "\t\t\"cachedir_del_dirs\": %u\n"
-           "\t},\n\n",
-           st->repo_tal_stats.mfts,
-           st->repo_tal_stats.mfts_fail,
-           st->repo_tal_stats.mfts_stale,
-           st->repo_tal_stats.crls,
-           st->repo_tal_stats.gbrs,
-           st->repos,
-           st->repo_tal_stats.vrps,
-           st->repo_tal_stats.vrps_uniqs,
-           st->repo_tal_stats.vaps,
-           st->repo_tal_stats.vaps_uniqs,
-           st->repo_stats.del_files,
-           st->repo_stats.extra_files,
-           st->repo_stats.del_dirs) < 0)
-               return -1;
-       return 0;
+       json_do_object("metadata");
+
+       json_do_printf("buildmachine", "%s", hn);
+       json_do_printf("buildtime", "%s", tbuf);
+       json_do_int("elapsedtime", st->elapsed_time.tv_sec);
+       json_do_int("usertime", st->user_time.tv_sec);
+       json_do_int("systemtime", st->system_time.tv_sec);
+       json_do_int("roas", st->repo_tal_stats.roas);
+       json_do_int("failedroas", st->repo_tal_stats.roas_fail);
+       json_do_int("invalidroas", st->repo_tal_stats.roas_invalid);
+       json_do_int("aspas", st->repo_tal_stats.aspas);
+       json_do_int("failedaspas", st->repo_tal_stats.aspas_fail);
+       json_do_int("invalidaspas", st->repo_tal_stats.aspas_invalid);
+       json_do_int("bgpsec_pubkeys", st->repo_tal_stats.brks);
+       json_do_int("certificates", st->repo_tal_stats.certs);
+       json_do_int("invalidcertificates", st->repo_tal_stats.certs_fail);
+       json_do_int("taks", st->repo_tal_stats.taks);
+       json_do_int("tals", st->tals);
+       json_do_int("invalidtals", talsz - st->tals);
+
+       json_do_array("talfiles");
+       for (i = 0; i < talsz; i++)
+               json_do_printf("name", "%s", tals[i]);
+       json_do_end();
+
+       json_do_int("manifests", st->repo_tal_stats.mfts);
+       json_do_int("failedmanifests", st->repo_tal_stats.mfts_fail);
+       json_do_int("stalemanifests", st->repo_tal_stats.mfts_stale);
+       json_do_int("crls", st->repo_tal_stats.crls);
+       json_do_int("gbrs", st->repo_tal_stats.gbrs);
+       json_do_int("repositories", st->repos);
+       json_do_int("vrps", st->repo_tal_stats.vrps);
+       json_do_int("uniquevrps", st->repo_tal_stats.vrps_uniqs);
+       json_do_int("vaps", st->repo_tal_stats.vaps);
+       json_do_int("uniquevaps", st->repo_tal_stats.vaps_uniqs);
+       json_do_int("cachedir_del_files", st->repo_stats.del_files);
+       json_do_int("cachedir_superfluous_files", st->repo_stats.extra_files);
+       json_do_int("cachedir_del_dirs", st->repo_stats.del_dirs);
+
+       json_do_end();
 }
 
-static int
-print_vap(FILE *out, struct vap *v, enum afi afi)
+static void
+print_vap(struct vap *v, enum afi afi)
 {
-       size_t i, rpas = 0;
-
-       if (fprintf(out, "\t\t\t{ \"customer_asid\": %u, \"providers\": [",
-           v->custasid) < 0)
-               return -1;
+       size_t i;
+       int found = 0;
 
-       for (i = 0; i < v->providersz; i++) {
-               if (v->providers[i].afi == 0 || v->providers[i].afi == afi)
-                       rpas++;
-       }
+       json_do_object("aspa");
+       json_do_int("customer_asid", v->custasid);
+       json_do_int("expires", v->expires);
 
+       json_do_array("providers");
        for (i = 0; i < v->providersz; i++) {
                if (v->providers[i].afi != 0 && v->providers[i].afi != afi)
                        continue;
-               if (fprintf(out, "%u", v->providers[i].as) < 0)
-                       return -1;
-               if (i + 1 < rpas)
-                       if (fprintf(out, ", ") < 0)
-                               return -1;
+               found = 1;
+               json_do_int("provider", v->providers[i].as);
        }
-
-       if (rpas == 0) {
-               if (fprintf(out, "0") < 0)
-                       return -1;
-       }
-
-       if (fprintf(out, "], \"expires\": %lld }", (long long)v->expires) < 0)
-               return -1;
-
-       return 0;
+       if (!found)
+               json_do_int("provider", 0);
+       json_do_end();
 }
 
-static int
-output_aspa(FILE *out, struct vap_tree *vaps)
+static void
+output_aspa(struct vap_tree *vaps)
 {
        struct vap      *v;
-       int              first;
-
-       if (excludeaspa)
-               return 0;
 
-       if (fprintf(out, ",\n\n\t\"provider_authorizations\": {\n"
-           "\t\t\"ipv4\": [\n") < 0)
-               return -1;
+       json_do_object("provider_authorizations");
 
-       first = 1;
+       json_do_array("ipv4");
        RB_FOREACH(v, vap_tree, vaps) {
-               if (!first) {
-                       if (fprintf(out, ",\n") < 0)
-                               return -1;
-               }
-               first = 0;
-               if (print_vap(out, v, AFI_IPV4))
-                       return -1;
+               print_vap(v, AFI_IPV4);
        }
+       json_do_end();
 
-       if (fprintf(out, "\n\t\t],\n\t\t\"ipv6\": [\n") < 0)
-               return -1;
-
-       first = 1;
+       json_do_array("ipv6");
        RB_FOREACH(v, vap_tree, vaps) {
-               if (!first) {
-                       if (fprintf(out, ",\n") < 0)
-                               return -1;
-               }
-               first = 0;
-               if (print_vap(out, v, AFI_IPV6))
-                       return -1;
+               print_vap(v, AFI_IPV6);
        }
-
-       if (fprintf(out, "\n\t\t]\n\t}\n") < 0)
-               return -1;
-
-       return 0;
+       json_do_end();
 }
 
-
 int
 output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
     struct vap_tree *vaps, struct stats *st)
@@ -198,57 +128,38 @@ output_json(FILE *out, struct vrp_tree *vrps, struct brk_tree *brks,
        char             buf[64];
        struct vrp      *v;
        struct brk      *b;
-       int              first = 1;
-
-       if (outputheader_json(out, st) < 0)
-               return -1;
 
-       if (fprintf(out, "\t\"roas\": [\n") < 0)
-               return -1;
+       json_do_start(out);
+       outputheader_json(st);
 
+       json_do_array("roas");
        RB_FOREACH(v, vrp_tree, vrps) {
-               if (!first) {
-                       if (fprintf(out, ",\n") < 0)
-                               return -1;
-               }
-               first = 0;
-
                ip_addr_print(&v->addr, v->afi, buf, sizeof(buf));
 
-               if (fprintf(out, "\t\t{ \"asn\": %u, \"prefix\": \"%s\", "
-                   "\"maxLength\": %u, \"ta\": \"%s\", \"expires\": %lld }",
-                   v->asid, buf, v->maxlength, taldescs[v->talid],
-                   (long long)v->expires)
-                   < 0)
-                       return -1;
+               json_do_object("roa");
+               json_do_int("asn", v->asid);
+               json_do_printf("prefix", "%s", buf);
+               json_do_int("maxLength", v->maxlength);
+               json_do_printf("ta", "%s", taldescs[v->talid]);
+               json_do_int("expires", v->expires);
+               json_do_end();
        }
+       json_do_end();
 
-       if (fprintf(out, "\n\t],\n\n\t\"bgpsec_keys\": [\n") < 0)
-               return -1;
-
-       first = 1;
+       json_do_array("bgpsec_keys");
        RB_FOREACH(b, brk_tree, brks) {
-               if (!first) {
-                       if (fprintf(out, ",\n") < 0)
-                               return -1;
-               }
-               first = 0;
-
-               if (fprintf(out, "\t\t{ \"asn\": %u, \"ski\": \"%s\", "
-                   "\"pubkey\": \"%s\", \"ta\": \"%s\", \"expires\": %lld }",
-                   b->asid, b->ski, b->pubkey, taldescs[b->talid],
-                   (long long)b->expires) < 0)
-                       return -1;
+               json_do_object("brks");
+               json_do_int("asn", b->asid);
+               json_do_printf("ski", "%s", b->ski);
+               json_do_printf("pubkey", "%s", b->pubkey);
+               json_do_printf("ta", "%s", taldescs[b->talid]);
+               json_do_int("expires", b->expires);
+               json_do_end();
        }
+       json_do_end();
 
-       if (fprintf(out, "\n\t]") < 0)
-               return -1;
-
-       if (output_aspa(out, vaps) < 0)
-               return -1;
-
-       if (fprintf(out, "\n}\n") < 0)
-               return -1;
+       if (!excludeaspa)
+               output_aspa(vaps);
 
-       return 0;
+       return json_do_finish();
 }