Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

508 lines (401 sloc) 12.545 kB
//
// tns_core.c: core code for a tnetstring parser in C
//
// This is code for parsing and rendering data in the provisional
// typed-netstring format proposed for inclusion in Mongrel2. You can
// think of it like a JSON library that uses a simpler wire format.
//
#include "dbg.h"
#include "tns_core.h"
#ifndef TNS_MAX_LENGTH
#define TNS_MAX_LENGTH 999999999
#endif
// Current outbuf implementation writes data starting at the back of
// the allocated buffer. When finished we simply memmove it to the front.
// Here *buffer points to the allocated buffer, while *head points to the
// last characer written to the buffer (and thus decreases as we write).
struct tns_outbuf_s {
char *buffer;
char *head;
size_t alloc_size;
};
// Helper function for parsing a dict; basically parses items in a loop.
static int tns_parse_dict(const tns_ops *ops, void *dict, const char *data, size_t len);
// Helper function for parsing a list; basically parses items in a loop.
static int tns_parse_list(const tns_ops *ops, void *list, const char *data, size_t len);
// Helper function for writing the length prefix onto a rendered value.
static int tns_outbuf_clamp(tns_outbuf *outbuf, size_t orig_size);
// Finalize an outbuf, turning the allocated buffer into a standard
// char* array. Can't use the outbuf once it has been finalized.
static char* tns_outbuf_finalize(tns_outbuf *outbuf, size_t *len);
// Free the memory allocated in an outbuf.
// Can't use the outbuf once it has been freed.
static void tns_outbuf_free(tns_outbuf *outbuf);
// Helper function to read a base-ten integer off a string.
// Due to additional constraints, we can do it faster than strtoi.
static size_t tns_strtosz(const char *data, size_t len, size_t *sz, char **end);
void* tns_parse(const tns_ops *ops, const char *data, size_t len, char **remain)
{
char *valstr = NULL;
tns_type_tag type = tns_tag_null;
size_t vallen = 0;
// Read the length of the value, and verify that it ends in a colon.
check(tns_strtosz(data, len, &vallen, &valstr) != -1,
"Not a tnetstring: invalid length prefix.");
check(*valstr == ':',
"Not a tnetstring: invalid length prefix.");
valstr++;
check((valstr+vallen) < (data+len),
"Not a tnetstring: invalid length prefix.");
// Grab the type tag from the end of the value.
type = valstr[vallen];
// Output the remainder of the string if necessary.
if(remain != NULL) {
*remain = valstr + vallen + 1;
}
// Now dispatch type parsing based on the type tag.
return tns_parse_payload(ops, type, valstr, vallen);
error:
return NULL;
}
// This appears to be faster than using strncmp to compare
// against a small string constant. Ugly but fast.
#define STR_EQ_TRUE(s) (s[0]=='t' && s[1]=='r' && s[2]=='u' && s[3]=='e')
#define STR_EQ_FALSE(s) (s[0]=='f' && s[1]=='a' && s[2]=='l' \
&& s[3]=='s' && s[4] == 'e')
void* tns_parse_payload(const tns_ops *ops,tns_type_tag type, const char *data, size_t len)
{
void *val = NULL;
assert(ops != NULL && "ops struct cannot be NULL");
switch(type) {
// Primitive type: a string blob.
case tns_tag_string:
val = ops->parse_string(ops, data, len);
check(val != NULL, "Not a tnetstring: invalid string literal.");
break;
// Primitive type: an integer.
case tns_tag_integer:
val = ops->parse_integer(ops, data, len);
check(val != NULL, "Not a tnetstring: invalid integer literal.");
break;
// Primitive type: a float.
case tns_tag_float:
val = ops->parse_float(ops, data, len);
check(val != NULL, "Not a tnetstring: invalid float literal.");
break;
// Primitive type: a boolean.
// The only acceptable values are "true" and "false".
case tns_tag_bool:
if(len == 4 && STR_EQ_TRUE(data)) {
val = ops->get_true(ops);
} else if(len == 5 && STR_EQ_FALSE(data)) {
val = ops->get_false(ops);
} else {
sentinel("Not a tnetstring: invalid boolean literal.");
val = NULL;
}
break;
// Primitive type: a null.
// This must be a zero-length string.
case tns_tag_null:
check(len == 0, "Not a tnetstring: invalid null literal.");
val = ops->get_null(ops);
break;
// Compound type: a dict.
// The data is written <key><value><key><value>
case tns_tag_dict:
val = ops->new_dict(ops);
check(val != NULL, "Could not create dict.");
check(tns_parse_dict(ops, val, data, len) != -1,
"Not a tnetstring: broken dict items.");
break;
// Compound type: a list.
// The data is written <item><item><item>
case tns_tag_list:
val = ops->new_list(ops);
check(val != NULL, "Could not create list.");
check(tns_parse_list(ops, val, data, len) != -1,
"Not a tnetstring: broken list items.");
break;
// Whoops, that ain't a tnetstring.
default:
sentinel("Not a tnetstring: invalid type tag.");
}
return val;
error:
if(val != NULL) {
ops->free_value(ops, val);
}
return NULL;
}
#undef STR_EQ_TRUE
#undef STR_EQ_FALSE
char* tns_render(const tns_ops *ops, void *val, size_t *len)
{
tns_outbuf outbuf;
check(tns_outbuf_init(&outbuf) != -1, "Failed to initialize outbuf.");
check(tns_render_value(ops, val, &outbuf) != -1, "Failed to render value.");
return tns_outbuf_finalize(&outbuf, len);
error:
tns_outbuf_free(&outbuf);
return NULL;
}
int tns_render_value(const tns_ops *ops, void *val, tns_outbuf *outbuf)
{
tns_type_tag type = tns_tag_null;
int res = -1;
size_t orig_size = 0;
assert(ops != NULL && "ops struct cannot be NULL");
// Find out the type tag for the given value.
type = ops->get_type(ops, val);
check(type != 0, "type not serializable.");
tns_outbuf_putc(outbuf, type);
orig_size = tns_outbuf_size(outbuf);
// Render it into the output buffer using callbacks.
switch(type) {
case tns_tag_string:
res = ops->render_string(ops, val, outbuf);
break;
case tns_tag_integer:
res = ops->render_integer(ops, val, outbuf);
break;
case tns_tag_float:
res = ops->render_float(ops, val, outbuf);
break;
case tns_tag_bool:
res = ops->render_bool(ops, val, outbuf);
break;
case tns_tag_null:
res = 0;
break;
case tns_tag_dict:
res = ops->render_dict(ops, val, outbuf);
break;
case tns_tag_list:
res = ops->render_list(ops, val, outbuf);
break;
default:
sentinel("unknown type tag: '%c'.", type);
}
check(res == 0, "Failed to render value of type '%c'.", type);
return tns_outbuf_clamp(outbuf, orig_size);
error:
return -1;
}
static int tns_parse_list(const tns_ops *ops, void *val, const char *data, size_t len)
{
void *item = NULL;
char *remain = NULL;
assert(val != NULL && "value cannot be NULL");
assert(data != NULL && "data cannot be NULL");
while(len > 0) {
item = tns_parse(ops, data, len, &remain);
check(item != NULL, "Failed to parse list.");
len = len - (remain - data);
data = remain;
check(ops->add_to_list(ops, val, item) != -1,
"Failed to add item to list.");
item = NULL;
}
return 0;
error:
if(item) {
ops->free_value(ops, item);
}
return -1;
}
static int tns_parse_dict(const tns_ops *ops, void *val, const char *data, size_t len)
{
void *key = NULL;
void *item = NULL;
char *remain = NULL;
assert(val != NULL && "value cannot be NULL");
assert(data != NULL && "data cannot be NULL");
while(len > 0) {
key = tns_parse(ops, data, len, &remain);
check(key != NULL, "Failed to parse dict key from tnetstring.");
len = len - (remain - data);
data = remain;
item = tns_parse(ops, data, len, &remain);
check(item != NULL, "Failed to parse dict item from tnetstring.");
len = len - (remain - data);
data = remain;
check(ops->add_to_dict(ops, val, key, item) != -1,
"Failed to add element to dict.");
key = NULL;
item = NULL;
}
return 0;
error:
if(key) {
ops->free_value(ops, key);
}
if(item) {
ops->free_value(ops, item);
}
return -1;
}
static inline size_t
tns_strtosz(const char *data, size_t len, size_t *sz, char **end)
{
char c;
const char *pos, *eod;
size_t value = 0;
pos = data;
eod = data + len;
// The first character must be a digit.
// The netstring spec explicitly forbits padding zeros.
// So if it's a zero, it must be the only char in the string.
c = *pos++;
switch(c) {
case '0':
*sz = 0;
*end = (char*) pos;
return 0;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = c - '0';
break;
default:
return -1;
}
// Consume the remaining digits, up to maximum value length.
while(pos < eod) {
c = *pos;
if(c < '0' || c > '9') {
*sz = value;
*end = (char*) pos;
return 0;
}
value = (value * 10) + (c - '0');
check(value <= TNS_MAX_LENGTH,
"Not a tnetstring: absurdly large length prefix");
pos++;
}
// If we consume the entire string, that's an error.
error:
return -1;
}
size_t tns_outbuf_size(tns_outbuf *outbuf)
{
return outbuf->alloc_size - (outbuf->head - outbuf->buffer);
}
static inline int tns_outbuf_itoa(tns_outbuf *outbuf, size_t n)
{
do {
check(tns_outbuf_putc(outbuf, n%10+'0') != -1,
"Failed to write int to tnetstring buffer.");
n = n / 10;
} while(n > 0);
return 0;
error:
return -1;
}
int tns_outbuf_init(tns_outbuf *outbuf)
{
outbuf->buffer = malloc(64);
check_mem(outbuf->buffer);
outbuf->head = outbuf->buffer + 64;
outbuf->alloc_size = 64;
return 0;
error:
outbuf->head = NULL;
outbuf->alloc_size = 0;
return -1;
}
static inline void tns_outbuf_free(tns_outbuf *outbuf)
{
if(outbuf) {
free(outbuf->buffer);
outbuf->buffer = NULL;
outbuf->head = 0;
outbuf->alloc_size = 0;
}
}
static inline int tns_outbuf_extend(tns_outbuf *outbuf, size_t free_size)
{
char *new_buf = NULL;
char *new_head = NULL;
size_t new_size = outbuf->alloc_size * 2;
size_t used_size;
used_size = tns_outbuf_size(outbuf);
while(new_size < free_size + used_size) {
new_size = new_size * 2;
}
new_buf = malloc(new_size);
check_mem(new_buf);
new_head = new_buf + new_size - used_size;
memmove(new_head, outbuf->head, used_size);
free(outbuf->buffer);
outbuf->buffer = new_buf;
outbuf->head = new_head;
outbuf->alloc_size = new_size;
return 0;
error:
return -1;
}
int tns_outbuf_putc(tns_outbuf *outbuf, char c)
{
if(outbuf->buffer == outbuf->head) {
check(tns_outbuf_extend(outbuf, 1) != -1, "Failed to extend buffer");
}
*(--outbuf->head) = c;
return 0;
error:
return -1;
}
int tns_outbuf_puts(tns_outbuf *outbuf, const char *data, size_t len)
{
if(outbuf->head - outbuf->buffer < len) {
check(tns_outbuf_extend(outbuf, len) != -1, "Failed to extend buffer");
}
outbuf->head -= len;
memmove(outbuf->head, data, len);
return 0;
error:
return -1;
}
static char* tns_outbuf_finalize(tns_outbuf *outbuf, size_t *len)
{
char *new_buf = NULL;
size_t used_size;
used_size = tns_outbuf_size(outbuf);
memmove(outbuf->buffer, outbuf->head, used_size);
if(len != NULL) {
*len = used_size;
} else {
if(outbuf->head == outbuf->buffer) {
new_buf = realloc(outbuf->buffer, outbuf->alloc_size*2);
check_mem(new_buf);
outbuf->buffer = new_buf;
outbuf->alloc_size = outbuf->alloc_size * 2;
}
outbuf->buffer[used_size] = '\0';
}
return outbuf->buffer;
error:
free(outbuf->buffer);
outbuf->buffer = NULL;
outbuf->alloc_size = 0;
return NULL;
}
static inline int tns_outbuf_clamp(tns_outbuf *outbuf, size_t orig_size)
{
size_t datalen = tns_outbuf_size(outbuf) - orig_size;
check(tns_outbuf_putc(outbuf, ':') != -1, "Failed to clamp outbuf");
check(tns_outbuf_itoa(outbuf, datalen) != -1, "Failed to clamp outbuf");
return 0;
error:
return -1;
}
void tns_outbuf_memmove(tns_outbuf *outbuf, char *dest)
{
memmove(dest, outbuf->head, tns_outbuf_size(outbuf));
}
Jump to Line
Something went wrong with that request. Please try again.