xref: /openbsd-src/usr.sbin/bgpctl/json.c (revision c28564e70de740bbdd5c247dd0405f90cf25b088)
1*c28564e7Sclaudio /*	$OpenBSD: json.c,v 1.10 2023/06/22 09:07:04 claudio Exp $ */
20c7a5c38Sclaudio 
314178ff0Sclaudio /*
414178ff0Sclaudio  * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
514178ff0Sclaudio  *
614178ff0Sclaudio  * Permission to use, copy, modify, and distribute this software for any
714178ff0Sclaudio  * purpose with or without fee is hereby granted, provided that the above
814178ff0Sclaudio  * copyright notice and this permission notice appear in all copies.
914178ff0Sclaudio  *
1014178ff0Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1114178ff0Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1214178ff0Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1314178ff0Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1414178ff0Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1514178ff0Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1614178ff0Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1714178ff0Sclaudio  */
1814178ff0Sclaudio 
19a32a1b0fSclaudio #include <ctype.h>
2014178ff0Sclaudio #include <err.h>
2114178ff0Sclaudio #include <stdarg.h>
2214178ff0Sclaudio #include <stdint.h>
2314178ff0Sclaudio #include <stdio.h>
24a32a1b0fSclaudio #include <stdlib.h>
2514178ff0Sclaudio #include <string.h>
2614178ff0Sclaudio 
2714178ff0Sclaudio #include "json.h"
2814178ff0Sclaudio 
2914178ff0Sclaudio #define JSON_MAX_STACK	16
3014178ff0Sclaudio 
3114178ff0Sclaudio enum json_type {
3214178ff0Sclaudio 	NONE,
3314178ff0Sclaudio 	START,
3414178ff0Sclaudio 	ARRAY,
3514178ff0Sclaudio 	OBJECT
3614178ff0Sclaudio };
3714178ff0Sclaudio 
38644f9c42Sclaudio static struct json_stack {
3914178ff0Sclaudio 	const char	*name;
4014178ff0Sclaudio 	unsigned int	count;
4156bc7cf3Sclaudio 	int		compact;
4214178ff0Sclaudio 	enum json_type	type;
4314178ff0Sclaudio } stack[JSON_MAX_STACK];
4414178ff0Sclaudio 
45644f9c42Sclaudio static char indent[JSON_MAX_STACK + 1];
46644f9c42Sclaudio static int level;
47db68842dSclaudio static int eb;
48644f9c42Sclaudio static FILE *jsonfh;
4914178ff0Sclaudio 
5014178ff0Sclaudio static void
do_comma_indent(void)5114178ff0Sclaudio do_comma_indent(void)
5214178ff0Sclaudio {
5356bc7cf3Sclaudio 	char sp = '\n';
5456bc7cf3Sclaudio 
5556bc7cf3Sclaudio 	if (stack[level].compact)
5656bc7cf3Sclaudio 		sp = ' ';
5756bc7cf3Sclaudio 
5856bc7cf3Sclaudio 	if (stack[level].count++ > 0) {
59db68842dSclaudio 		if (!eb)
6056bc7cf3Sclaudio 			eb = fprintf(jsonfh, ",%c", sp) < 0;
6156bc7cf3Sclaudio 	}
6256bc7cf3Sclaudio 
6356bc7cf3Sclaudio 	if (stack[level].compact)
6456bc7cf3Sclaudio 		return;
65db68842dSclaudio 	if (!eb)
6617c3a11cSclaudio 		eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0;
6714178ff0Sclaudio }
6814178ff0Sclaudio 
6914178ff0Sclaudio static void
do_name(const char * name)7014178ff0Sclaudio do_name(const char *name)
7114178ff0Sclaudio {
7214178ff0Sclaudio 	if (stack[level].type == ARRAY)
7314178ff0Sclaudio 		return;
74db68842dSclaudio 	if (!eb)
7517c3a11cSclaudio 		eb = fprintf(jsonfh, "\"%s\": ", name) < 0;
7614178ff0Sclaudio }
7714178ff0Sclaudio 
7814178ff0Sclaudio static int
do_find(enum json_type type,const char * name)7914178ff0Sclaudio do_find(enum json_type type, const char *name)
8014178ff0Sclaudio {
8114178ff0Sclaudio 	int i;
8214178ff0Sclaudio 
8314178ff0Sclaudio 	for (i = level; i > 0; i--)
8414178ff0Sclaudio 		if (type == stack[i].type &&
8514178ff0Sclaudio 		    strcmp(name, stack[i].name) == 0)
8614178ff0Sclaudio 			return i;
8714178ff0Sclaudio 
8814178ff0Sclaudio 	/* not found */
8914178ff0Sclaudio 	return -1;
9014178ff0Sclaudio }
9114178ff0Sclaudio 
9214178ff0Sclaudio void
json_do_start(FILE * fh)93644f9c42Sclaudio json_do_start(FILE *fh)
9414178ff0Sclaudio {
9514178ff0Sclaudio 	memset(indent, '\t', JSON_MAX_STACK);
9614178ff0Sclaudio 	memset(stack, 0, sizeof(stack));
9714178ff0Sclaudio 	level = 0;
9814178ff0Sclaudio 	stack[level].type = START;
99644f9c42Sclaudio 	jsonfh = fh;
100db68842dSclaudio 	eb = 0;
10114178ff0Sclaudio 
10217c3a11cSclaudio 	eb = fprintf(jsonfh, "{\n") < 0;
10314178ff0Sclaudio }
10414178ff0Sclaudio 
105db68842dSclaudio int
json_do_finish(void)10614178ff0Sclaudio json_do_finish(void)
10714178ff0Sclaudio {
10814178ff0Sclaudio 	while (level > 0)
10914178ff0Sclaudio 		json_do_end();
110db68842dSclaudio 	if (!eb)
11117c3a11cSclaudio 		eb = fprintf(jsonfh, "\n}\n") < 0;
112db68842dSclaudio 
113db68842dSclaudio 	return -eb;
11414178ff0Sclaudio }
11514178ff0Sclaudio 
11614178ff0Sclaudio void
json_do_array(const char * name)11714178ff0Sclaudio json_do_array(const char *name)
11814178ff0Sclaudio {
11914178ff0Sclaudio 	int i, l;
12056bc7cf3Sclaudio 	char sp = '\n';
12114178ff0Sclaudio 
12214178ff0Sclaudio 	if ((l = do_find(ARRAY, name)) > 0) {
12314178ff0Sclaudio 		/* array already in use, close element and move on */
12414178ff0Sclaudio 		for (i = level - l; i > 0; i--)
12514178ff0Sclaudio 			json_do_end();
12614178ff0Sclaudio 		return;
12714178ff0Sclaudio 	}
12814178ff0Sclaudio 	/* Do not stack arrays, while allowed this is not needed */
12914178ff0Sclaudio 	if (stack[level].type == ARRAY)
13014178ff0Sclaudio 		json_do_end();
13114178ff0Sclaudio 
13256bc7cf3Sclaudio 	if (stack[level].compact)
13356bc7cf3Sclaudio 		sp = ' ';
13414178ff0Sclaudio 	do_comma_indent();
13514178ff0Sclaudio 	do_name(name);
136db68842dSclaudio 	if (!eb)
13756bc7cf3Sclaudio 		eb = fprintf(jsonfh, "[%c", sp) < 0;
13814178ff0Sclaudio 
13914178ff0Sclaudio 	if (++level >= JSON_MAX_STACK)
14014178ff0Sclaudio 		errx(1, "json stack too deep");
14114178ff0Sclaudio 
14214178ff0Sclaudio 	stack[level].name = name;
14314178ff0Sclaudio 	stack[level].type = ARRAY;
14414178ff0Sclaudio 	stack[level].count = 0;
14556bc7cf3Sclaudio 	/* inherit compact setting from above level */
14656bc7cf3Sclaudio 	stack[level].compact = stack[level - 1].compact;
14714178ff0Sclaudio }
14814178ff0Sclaudio 
14914178ff0Sclaudio void
json_do_object(const char * name,int compact)15056bc7cf3Sclaudio json_do_object(const char *name, int compact)
15114178ff0Sclaudio {
15214178ff0Sclaudio 	int i, l;
15356bc7cf3Sclaudio 	char sp = '\n';
15414178ff0Sclaudio 
15514178ff0Sclaudio 	if ((l = do_find(OBJECT, name)) > 0) {
15614178ff0Sclaudio 		/* roll back to that object and close it */
15714178ff0Sclaudio 		for (i = level - l; i >= 0; i--)
15814178ff0Sclaudio 			json_do_end();
15914178ff0Sclaudio 	}
16014178ff0Sclaudio 
16156bc7cf3Sclaudio 	if (compact)
16256bc7cf3Sclaudio 		sp = ' ';
16314178ff0Sclaudio 	do_comma_indent();
16414178ff0Sclaudio 	do_name(name);
165db68842dSclaudio 	if (!eb)
16656bc7cf3Sclaudio 		eb = fprintf(jsonfh, "{%c", sp) < 0;
16714178ff0Sclaudio 
16814178ff0Sclaudio 	if (++level >= JSON_MAX_STACK)
16914178ff0Sclaudio 		errx(1, "json stack too deep");
17014178ff0Sclaudio 
17114178ff0Sclaudio 	stack[level].name = name;
17214178ff0Sclaudio 	stack[level].type = OBJECT;
17314178ff0Sclaudio 	stack[level].count = 0;
17456bc7cf3Sclaudio 	stack[level].compact = compact;
17514178ff0Sclaudio }
17614178ff0Sclaudio 
17714178ff0Sclaudio void
json_do_end(void)17814178ff0Sclaudio json_do_end(void)
17914178ff0Sclaudio {
18056bc7cf3Sclaudio 	char c;
18156bc7cf3Sclaudio 
18256bc7cf3Sclaudio 	if (stack[level].type == ARRAY)
18356bc7cf3Sclaudio 		c = ']';
18456bc7cf3Sclaudio 	else if (stack[level].type == OBJECT)
18556bc7cf3Sclaudio 		c = '}';
18656bc7cf3Sclaudio 	else
18714178ff0Sclaudio 		errx(1, "json bad stack state");
18856bc7cf3Sclaudio 
18956bc7cf3Sclaudio 	if (!stack[level].compact) {
19056bc7cf3Sclaudio 		if (!eb)
19156bc7cf3Sclaudio 			eb = fprintf(jsonfh, "\n%.*s%c", level, indent, c) < 0;
19256bc7cf3Sclaudio 	} else {
19356bc7cf3Sclaudio 		if (!eb)
19456bc7cf3Sclaudio 			eb = fprintf(jsonfh, " %c", c) < 0;
195db68842dSclaudio 	}
19656bc7cf3Sclaudio 
19714178ff0Sclaudio 	stack[level].name = NULL;
19814178ff0Sclaudio 	stack[level].type = NONE;
19914178ff0Sclaudio 	stack[level].count = 0;
20056bc7cf3Sclaudio 	stack[level].compact = 0;
20114178ff0Sclaudio 
20214178ff0Sclaudio 	if (level-- <= 0)
20314178ff0Sclaudio 		errx(1, "json stack underflow");
20414178ff0Sclaudio 
20514178ff0Sclaudio 	stack[level].count++;
20614178ff0Sclaudio }
20714178ff0Sclaudio 
20814178ff0Sclaudio void
json_do_printf(const char * name,const char * fmt,...)20914178ff0Sclaudio json_do_printf(const char *name, const char *fmt, ...)
21014178ff0Sclaudio {
21114178ff0Sclaudio 	va_list ap;
212a32a1b0fSclaudio 	char *str;
213a32a1b0fSclaudio 
214a32a1b0fSclaudio 	va_start(ap, fmt);
215a32a1b0fSclaudio 	if (!eb) {
216a32a1b0fSclaudio 		if (vasprintf(&str, fmt, ap) == -1)
217a32a1b0fSclaudio 			errx(1, "json printf failed");
218a32a1b0fSclaudio 		json_do_string(name, str);
219a32a1b0fSclaudio 		free(str);
220a32a1b0fSclaudio 	}
221a32a1b0fSclaudio 	va_end(ap);
222a32a1b0fSclaudio }
223a32a1b0fSclaudio 
224a32a1b0fSclaudio void
json_do_string(const char * name,const char * v)225a32a1b0fSclaudio json_do_string(const char *name, const char *v)
226a32a1b0fSclaudio {
227a32a1b0fSclaudio 	unsigned char c;
22814178ff0Sclaudio 
22914178ff0Sclaudio 	do_comma_indent();
23014178ff0Sclaudio 	do_name(name);
231db68842dSclaudio 	if (!eb)
23217c3a11cSclaudio 		eb = fprintf(jsonfh, "\"") < 0;
233a32a1b0fSclaudio 	while ((c = *v++) != '\0' && !eb) {
234a32a1b0fSclaudio 		/* skip escaping '/' since our use case does not require it */
235a32a1b0fSclaudio 		switch (c) {
236a32a1b0fSclaudio 		case '"':
237a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\\"") < 0;
238a32a1b0fSclaudio 			break;
239a32a1b0fSclaudio 		case '\\':
240a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\\\") < 0;
241a32a1b0fSclaudio 			break;
242a32a1b0fSclaudio 		case '\b':
243a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\b") < 0;
244a32a1b0fSclaudio 			break;
245a32a1b0fSclaudio 		case '\f':
246a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\f") < 0;
247a32a1b0fSclaudio 			break;
248a32a1b0fSclaudio 		case '\n':
249a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\n") < 0;
250a32a1b0fSclaudio 			break;
251a32a1b0fSclaudio 		case '\r':
252a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\r") < 0;
253a32a1b0fSclaudio 			break;
254a32a1b0fSclaudio 		case '\t':
255a32a1b0fSclaudio 			eb = fprintf(jsonfh, "\\t") < 0;
256a32a1b0fSclaudio 			break;
257a32a1b0fSclaudio 		default:
258a32a1b0fSclaudio 			if (iscntrl(c))
259a32a1b0fSclaudio 				errx(1, "bad control character in string");
260a32a1b0fSclaudio 			eb = putc(c, jsonfh) == EOF;
261a32a1b0fSclaudio 			break;
262a32a1b0fSclaudio 		}
263a32a1b0fSclaudio 	}
264db68842dSclaudio 	if (!eb)
26517c3a11cSclaudio 		eb = fprintf(jsonfh, "\"") < 0;
26614178ff0Sclaudio }
26714178ff0Sclaudio 
26814178ff0Sclaudio void
json_do_hexdump(const char * name,void * buf,size_t len)26914178ff0Sclaudio json_do_hexdump(const char *name, void *buf, size_t len)
27014178ff0Sclaudio {
27114178ff0Sclaudio 	uint8_t *data = buf;
27214178ff0Sclaudio 	size_t i;
27314178ff0Sclaudio 
27414178ff0Sclaudio 	do_comma_indent();
27514178ff0Sclaudio 	do_name(name);
276db68842dSclaudio 	if (!eb)
27717c3a11cSclaudio 		eb = fprintf(jsonfh, "\"") < 0;
27814178ff0Sclaudio 	for (i = 0; i < len; i++)
279db68842dSclaudio 		if (!eb)
28017c3a11cSclaudio 			eb = fprintf(jsonfh, "%02x", *(data + i)) < 0;
281db68842dSclaudio 	if (!eb)
28217c3a11cSclaudio 		eb = fprintf(jsonfh, "\"") < 0;
28314178ff0Sclaudio }
28414178ff0Sclaudio 
28514178ff0Sclaudio void
json_do_bool(const char * name,int v)28614178ff0Sclaudio json_do_bool(const char *name, int v)
28714178ff0Sclaudio {
28814178ff0Sclaudio 	do_comma_indent();
28914178ff0Sclaudio 	do_name(name);
290db68842dSclaudio 	if (v) {
291db68842dSclaudio 		if (!eb)
29217c3a11cSclaudio 			eb = fprintf(jsonfh, "true") < 0;
293db68842dSclaudio 	} else {
294db68842dSclaudio 		if (!eb)
29517c3a11cSclaudio 			eb = fprintf(jsonfh, "false") < 0;
296db68842dSclaudio 	}
29714178ff0Sclaudio }
29814178ff0Sclaudio 
29914178ff0Sclaudio void
json_do_uint(const char * name,unsigned long long v)300df3045caSclaudio json_do_uint(const char *name, unsigned long long v)
30114178ff0Sclaudio {
30214178ff0Sclaudio 	do_comma_indent();
30314178ff0Sclaudio 	do_name(name);
304db68842dSclaudio 	if (!eb)
30517c3a11cSclaudio 		eb = fprintf(jsonfh, "%llu", v) < 0;
30614178ff0Sclaudio }
30714178ff0Sclaudio 
30814178ff0Sclaudio void
json_do_int(const char * name,long long v)309df3045caSclaudio json_do_int(const char *name, long long v)
31014178ff0Sclaudio {
31114178ff0Sclaudio 	do_comma_indent();
31214178ff0Sclaudio 	do_name(name);
313db68842dSclaudio 	if (!eb)
31417c3a11cSclaudio 		eb = fprintf(jsonfh, "%lld", v) < 0;
31514178ff0Sclaudio }
31614178ff0Sclaudio 
31714178ff0Sclaudio void
json_do_double(const char * name,double v)31814178ff0Sclaudio json_do_double(const char *name, double v)
31914178ff0Sclaudio {
32014178ff0Sclaudio 	do_comma_indent();
32114178ff0Sclaudio 	do_name(name);
322db68842dSclaudio 	if (!eb)
32317c3a11cSclaudio 		eb = fprintf(jsonfh, "%f", v) < 0;
32414178ff0Sclaudio }
325