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
cbor_memdump(FILE * fp,const char * title,const char * data,size_t len,const char * tag,int indent)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
cbor_encode_uint(xo_buffer_t * xbp,uint64_t minor,unsigned limit)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
cbor_append(xo_handle_t * xop,cbor_private_t * cbor,xo_buffer_t * xbp,unsigned major,unsigned minor,const char * data)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
cbor_create(xo_handle_t * xop)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
cbor_content(xo_handle_t * xop,cbor_private_t * cbor,xo_buffer_t * xbp,const char * value)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
cbor_handler(XO_ENCODER_HANDLER_ARGS)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
xo_encoder_library_init(XO_ENCODER_INIT_ARGS)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