xref: /freebsd/contrib/libxo/encoder/cbor/enc_cbor.c (revision c697fb7f)
1 /*
2  * Copyright (c) 2015, Juniper Networks, Inc.
3  * All rights reserved.
4  * This SOFTWARE is licensed under the LICENSE provided in the
5  * ../Copyright file. By downloading, installing, copying, or otherwise
6  * using the SOFTWARE, you agree to be bound by the terms of that
7  * LICENSE.
8  * Phil Shafer, August 2015
9  */
10 
11 /*
12  * CBOR (RFC 7049) mades a suitable test case for libxo's external
13  * encoder API.  It's simple, streaming, well documented, and an
14  * IETF standard.
15  *
16  * This encoder uses the "pretty" flag for diagnostics, which isn't
17  * really kosher, but it's example code.
18  */
19 
20 #include <string.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <stdint.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 
28 #include "xo.h"
29 #include "xo_encoder.h"
30 #include "xo_buf.h"
31 
32 /*
33  * memdump(): dump memory contents in hex/ascii
34 0         1         2         3         4         5         6         7
35 0123456789012345678901234567890123456789012345678901234567890123456789012345
36 XX XX XX XX  XX XX XX XX - XX XX XX XX  XX XX XX XX abcdefghijklmnop
37  */
38 static void
39 cbor_memdump (FILE *fp, const char *title, const char *data,
40          size_t len, const char *tag, int indent)
41 {
42     enum { MAX_PER_LINE = 16 };
43     char buf[ 80 ];
44     char text[ 80 ];
45     char *bp, *tp;
46     size_t i;
47 #if 0
48     static const int ends[ MAX_PER_LINE ] = { 2, 5, 8, 11, 15, 18, 21, 24,
49                                               29, 32, 35, 38, 42, 45, 48, 51 };
50 #endif
51 
52     if (fp == NULL)
53 	fp = stdout;
54     if (tag == NULL)
55 	tag = "";
56 
57     fprintf(fp, "%*s[%s] @ %p (%lx/%lu)\n", indent + 1, tag,
58             title, data, (unsigned long) len, (unsigned long) len);
59 
60     while (len > 0) {
61         bp = buf;
62         tp = text;
63 
64         for (i = 0; i < MAX_PER_LINE && i < len; i++) {
65             if (i && (i % 4) == 0) *bp++ = ' ';
66             if (i == 8) {
67                 *bp++ = '-';
68                 *bp++ = ' ';
69             }
70             sprintf(bp, "%02x ", (unsigned char) *data);
71             bp += strlen(bp);
72             *tp++ = (isprint((int) *data) && *data >= ' ') ? *data : '.';
73             data += 1;
74         }
75 
76         *tp = 0;
77         *bp = 0;
78         fprintf(fp, "%*s%-54s%s\n", indent + 1, tag, buf, text);
79         len -= i;
80     }
81 }
82 
83 /*
84  * CBOR breaks the first byte into two pieces, the major type in the
85  * top 3 bits and the minor value in the low 5 bits.  The value can be
86  * a small value (0 .. 23), an 8-bit value (24), a 16-bit value (25),
87  * a 32-bit value (26), or a 64-bit value (27).  A value of 31
88  * represents an unknown length, which we'll use extensively for
89  * streaming our content.
90  */
91 #define CBOR_MAJOR_MASK	0xE0
92 #define CBOR_MINOR_MASK	0x1F
93 #define CBOR_MAJOR_SHIFT	5
94 
95 #define CBOR_MAJOR(_x)	  ((_x) & CBOR_MAJOR_MASK)
96 #define CBOR_MAJOR_VAL(_x) ((_x) << CBOR_MAJOR_SHIFT)
97 #define CBOR_MINOR_VAL(_x) ((_x) & CBOR_MINOR_MASK)
98 
99 /* Major type codes */
100 #define CBOR_UNSIGNED	CBOR_MAJOR_VAL(0) /* 0x00 */
101 #define CBOR_NEGATIVE	CBOR_MAJOR_VAL(1) /* 0x20 */
102 #define CBOR_BYTES	CBOR_MAJOR_VAL(2) /* 0x40 */
103 #define CBOR_STRING	CBOR_MAJOR_VAL(3) /* 0x60 */
104 #define CBOR_ARRAY	CBOR_MAJOR_VAL(4) /* 0x80 */
105 #define CBOR_MAP	CBOR_MAJOR_VAL(5) /* 0xa0 */
106 #define CBOR_SEMANTIC	CBOR_MAJOR_VAL(6) /* 0xc0 */
107 #define CBOR_SPECIAL	CBOR_MAJOR_VAL(7) /* 0xe0 */
108 
109 #define CBOR_ULIMIT	24	/* Largest unsigned value */
110 #define CBOR_NLIMIT	23	/* Largest negative value */
111 
112 #define CBOR_BREAK	0xFF
113 #define CBOR_INDEF	0x1F
114 
115 #define CBOR_FALSE	0xF4
116 #define CBOR_TRUE	0xF5
117 #define CBOR_NULL	0xF6
118 #define CBOR_UNDEF	0xF7
119 
120 #define CBOR_LEN8	0x18	/* 24 - 8-bit value */
121 #define CBOR_LEN16	0x19	/* 25 - 16-bit value */
122 #define CBOR_LEN32	0x1a	/* 26 - 32-bit value */
123 #define CBOR_LEN64	0x1b	/* 27 - 64-bit value */
124 #define CBOR_LEN128	0x1c	/* 28 - 128-bit value */
125 
126 typedef struct cbor_private_s {
127     xo_buffer_t c_data;		/* Our data buffer */
128     unsigned c_indent;		/* Indent level */
129     unsigned c_open_leaf_list;	/* Open leaf list construct? */
130 } cbor_private_t;
131 
132 static void
133 cbor_encode_uint (xo_buffer_t *xbp, uint64_t minor, unsigned limit)
134 {
135     char *bp = xbp->xb_curp;
136     int i, m;
137 
138     if (minor > (1ULL << 32)) {
139 	*bp++ |= CBOR_LEN64;
140 	m = 64;
141 
142     } else if (minor > (1<<16)) {
143 	*bp++ |= CBOR_LEN32;
144 	m = 32;
145 
146     } else if (minor > (1<<8)) {
147 	*bp++ |= CBOR_LEN16;
148 	m = 16;
149 
150     } else if (minor > limit) {
151 	*bp++ |= CBOR_LEN8;
152 	m = 8;
153     } else {
154 	*bp++ |= minor & CBOR_MINOR_MASK;
155 	m = 0;
156     }
157 
158     if (m) {
159 	for (i = m - 8; i >= 0; i -= 8)
160 	    *bp++ = minor >> i;
161     }
162 
163     xbp->xb_curp = bp;
164 }
165 
166 static void
167 cbor_append (xo_handle_t *xop, cbor_private_t *cbor, xo_buffer_t *xbp,
168 	     unsigned major, unsigned minor, const char *data)
169 {
170     if (!xo_buf_has_room(xbp, minor + 2))
171 	return;
172 
173     unsigned offset = xo_buf_offset(xbp);
174 
175     *xbp->xb_curp = major;
176     cbor_encode_uint(xbp, minor, CBOR_ULIMIT);
177     if (data)
178 	xo_buf_append(xbp, data, minor);
179 
180     if (xo_get_flags(xop) & XOF_PRETTY)
181 	cbor_memdump(stdout, "append", xo_buf_data(xbp, offset),
182 		     xbp->xb_curp - xbp->xb_bufp - offset, "",
183 		     cbor->c_indent * 2);
184 }
185 
186 static int
187 cbor_create (xo_handle_t *xop)
188 {
189     cbor_private_t *cbor = xo_realloc(NULL, sizeof(*cbor));
190     if (cbor == NULL)
191 	return -1;
192 
193     bzero(cbor, sizeof(*cbor));
194     xo_buf_init(&cbor->c_data);
195 
196     xo_set_private(xop, cbor);
197 
198     cbor_append(xop, cbor, &cbor->c_data, CBOR_MAP | CBOR_INDEF, 0, NULL);
199 
200     return 0;
201 }
202 
203 static int
204 cbor_content (xo_handle_t *xop, cbor_private_t *cbor, xo_buffer_t *xbp,
205 	      const char *value)
206 {
207     int rc = 0;
208 
209     unsigned offset = xo_buf_offset(xbp);
210 
211     if (value == NULL || *value == '\0' || xo_streq(value, "true"))
212 	cbor_append(xop, cbor, &cbor->c_data, CBOR_TRUE, 0, NULL);
213     else if (xo_streq(value, "false"))
214 	cbor_append(xop, cbor, &cbor->c_data, CBOR_FALSE, 0, NULL);
215     else {
216 	int negative = 0;
217 	if (*value == '-') {
218 	    value += 1;
219 	    negative = 1;
220 	}
221 
222 	char *ep;
223 	unsigned long long ival;
224 	ival = strtoull(value, &ep, 0);
225 	if (ival == ULLONG_MAX)	/* Sometimes a string is just a string */
226 	    cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(value), value);
227 	else {
228 	    *xbp->xb_curp = negative ? CBOR_NEGATIVE : CBOR_UNSIGNED;
229 	    if (negative)
230 		ival -= 1;	/* Don't waste a negative zero */
231 	    cbor_encode_uint(xbp, ival, negative ? CBOR_NLIMIT : CBOR_ULIMIT);
232 	}
233     }
234 
235     if (xo_get_flags(xop) & XOF_PRETTY)
236 	cbor_memdump(stdout, "content", xo_buf_data(xbp, offset),
237 		     xbp->xb_curp - xbp->xb_bufp - offset, "",
238 		     cbor->c_indent * 2);
239 
240     return rc;
241 }
242 
243 static int
244 cbor_handler (XO_ENCODER_HANDLER_ARGS)
245 {
246     int rc = 0;
247     cbor_private_t *cbor = private;
248     xo_buffer_t *xbp = cbor ? &cbor->c_data : NULL;
249 
250     if (xo_get_flags(xop) & XOF_PRETTY) {
251 	printf("%*sop %s: [%s] [%s]\n", cbor ? cbor->c_indent * 2 + 4 : 0, "",
252 	       xo_encoder_op_name(op), name, value);
253 	fflush(stdout);
254     }
255 
256     /* If we don't have private data, we're sunk */
257     if (cbor == NULL && op != XO_OP_CREATE)
258 	return -1;
259 
260     switch (op) {
261     case XO_OP_CREATE:		/* Called when the handle is init'd */
262 	rc = cbor_create(xop);
263 	break;
264 
265     case XO_OP_OPEN_CONTAINER:
266 	cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
267 	cbor_append(xop, cbor, xbp, CBOR_MAP | CBOR_INDEF, 0, NULL);
268 	cbor->c_indent += 1;
269 	break;
270 
271     case XO_OP_CLOSE_CONTAINER:
272 	cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
273 	cbor->c_indent -= 1;
274 	break;
275 
276     case XO_OP_OPEN_LIST:
277 	cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
278 	cbor_append(xop, cbor, xbp, CBOR_ARRAY | CBOR_INDEF, 0, NULL);
279 	cbor->c_indent += 1;
280 	break;
281 
282     case XO_OP_CLOSE_LIST:
283 	cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
284 	cbor->c_indent -= 1;
285 	break;
286 
287     case XO_OP_OPEN_LEAF_LIST:
288 	cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
289 	cbor_append(xop, cbor, xbp, CBOR_ARRAY | CBOR_INDEF, 0, NULL);
290 	cbor->c_indent += 1;
291 	cbor->c_open_leaf_list = 1;
292 	break;
293 
294     case XO_OP_CLOSE_LEAF_LIST:
295 	cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
296 	cbor->c_indent -= 1;
297 	cbor->c_open_leaf_list = 0;
298 	break;
299 
300     case XO_OP_OPEN_INSTANCE:
301 	cbor_append(xop, cbor, xbp, CBOR_MAP | CBOR_INDEF, 0, NULL);
302 	cbor->c_indent += 1;
303 	break;
304 
305     case XO_OP_CLOSE_INSTANCE:
306 	cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
307 	cbor->c_indent -= 1;
308 	break;
309 
310     case XO_OP_STRING:		   /* Quoted UTF-8 string */
311 	if (!cbor->c_open_leaf_list)
312 	    cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
313 	cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(value), value);
314 	break;
315 
316     case XO_OP_CONTENT:		   /* Other content */
317 	if (!cbor->c_open_leaf_list)
318 	    cbor_append(xop, cbor, xbp, CBOR_STRING, strlen(name), name);
319 
320 	/*
321 	 * It's content, not string, so we need to look at the
322 	 * string and build some content.  Turns out we only
323 	 * care about true, false, null, and numbers.
324 	 */
325 	cbor_content(xop, cbor, xbp, value);
326 	break;
327 
328     case XO_OP_FINISH:		   /* Clean up function */
329 	cbor_append(xop, cbor, xbp, CBOR_BREAK, 0, NULL);
330 	cbor->c_indent -= 1;
331 	break;
332 
333     case XO_OP_FLUSH:		   /* Clean up function */
334 	if (xo_get_flags(xop) & XOF_PRETTY)
335 	    cbor_memdump(stdout, "cbor",
336 			xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp,
337 			">", 0);
338 	else {
339 	    rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
340 	    if (rc > 0)
341 		rc = 0;
342 	}
343 	break;
344 
345     case XO_OP_DESTROY:		   /* Clean up function */
346 	break;
347 
348     case XO_OP_ATTRIBUTE:	   /* Attribute name/value */
349 	break;
350 
351     case XO_OP_VERSION:		/* Version string */
352 	break;
353 
354     }
355 
356     return rc;
357 }
358 
359 int
360 xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
361 {
362     arg->xei_handler = cbor_handler;
363     arg->xei_version = XO_ENCODER_VERSION;
364 
365     return 0;
366 }
367