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