diff options
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/deliver.c | 24 | ||||
| -rw-r--r-- | serve/message-json.c | 139 | ||||
| -rw-r--r-- | serve/message-json.h | 8 | ||||
| -rw-r--r-- | serve/serve.c | 55 |
4 files changed, 200 insertions, 26 deletions
diff --git a/serve/deliver.c b/serve/deliver.c index 36b39f4..05e7f21 100644 --- a/serve/deliver.c +++ b/serve/deliver.c @@ -482,15 +482,37 @@ static bool form_answer(const PureWire *w, int status, const Text *body, const P unsigned char *prefix = bytes + table, *text = prefix + in->prefix, *copy = text + body->size; if (body->size) memcpy(text, body->bytes, body->size); if (p->sent_size) memcpy(copy, p->sent, p->sent_size); - PureJsonMessage message = {.module = m, .shape = in, .payload = prefix, .table = bytes, .lenient = true}; + /* Rows the answer gives go after the send's copy. */ + PureJsonMessage message = {.module = m, .shape = in, .payload = prefix, .table = bytes, .lenient = true, + .rows_base = in->prefix + body->size + p->sent_size}; size_t at = pure_json_space(text, 0, body->size); if (at < body->size && text[at] == '{' && pure_json_message(&message, text, body->size, &at)) { + free(message.rows); free(bytes); return false; } + if (message.rows_size) { + unsigned char *grown = realloc(bytes, total + message.rows_size); + if (!grown) { + free(message.rows); + free(bytes); + return false; + } + memcpy(grown + total, message.rows, message.rows_size); + bytes = grown; + prefix = bytes + table; + copy = prefix + in->prefix + body->size; + total += message.rows_size; + } + free(message.rows); for (uint32_t f = 0; f < in->field_count; ++f) { if (message.seen[f]) continue; const PureField *field = &m->field[in->field_start + f]; + if (field->type == PURE_FIELD_ROWS) { /* none given: none */ + write_u32(bytes + 8 * f, 0); + write_u32(bytes + 8 * f + 4, 0); + continue; + } if (field->type == PURE_FIELD_U32 && !strcmp(field->name, "http_status")) { write_u32(prefix + field->offset, (uint32_t)status); continue; diff --git a/serve/message-json.c b/serve/message-json.c index 66c812d..8c288ce 100644 --- a/serve/message-json.c +++ b/serve/message-json.c @@ -42,22 +42,33 @@ static size_t json_number(const unsigned char *p, size_t size, bool *integer) { return i; } -/* A fixed field's value from its text (a query value or a JSON number) into - * the prefix: a u32 in range, or a finite number. */ -bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix) { +/* A JSON number's finite value. */ +static bool json_double(const unsigned char *text, size_t size, double *value) { bool integer; if (!size || json_number(text, size, &integer) != size || size > 40) return false; char digits[48]; memcpy(digits, text, size); digits[size] = 0; + *value = strtod(digits, NULL); + return isfinite(*value); +} + +/* A fixed field's value from its text (a query value or a JSON number) into + * the prefix: a u32 in range, or a finite number. */ +bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix) { + bool integer; + if (!size || json_number(text, size, &integer) != size || size > 40) return false; if (field->type == PURE_FIELD_U32) { + char digits[48]; uint64_t n; + memcpy(digits, text, size); + digits[size] = 0; if (!integer || !decimal(digits, UINT32_MAX, &n)) return false; write_u32(prefix + field->offset, (uint32_t)n); return true; } - double value = strtod(digits, NULL); - if (!isfinite(value)) return false; + double value; + if (!json_double(text, size, &value)) return false; memcpy(prefix + field->offset, &value, 8); return true; } @@ -203,6 +214,116 @@ static uint32_t field_named(const PureJsonMessage *b, const char *path, size_t p return b->shape->field_count; } +/* Room for more bytes of rows. */ +static bool rows_room(PureJsonMessage *b, size_t more) { + if (more <= b->rows_capacity - b->rows_size) return true; + size_t capacity = b->rows_capacity ? b->rows_capacity : 4096; + while (capacity - b->rows_size < more) { + if (capacity > SIZE_MAX / 2) return false; + capacity *= 2; + } + unsigned char *grown = realloc(b->rows, capacity); + if (!grown) return false; + b->rows = grown; + b->rows_capacity = capacity; + return true; +} + +/* A rows field's array (at its opening bracket): each an object of its + * record's fields by name, a number or a string, a field left out taking its + * default; encoded one after another as the list's stored form (a number's + * binary64 bits, a text's u32le length and bytes). */ +static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, size_t *at, uint32_t f, unsigned depth) { + const PureModule *m = b->module; + const PureField *field = &m->field[b->shape->field_start + f]; + const PureRecord *record = &m->record[m->state_ref[field->rows]]; + size_t start = b->rows_size; + if (*at == size || json[(*at)++] != '[') return "json_type"; + *at = pure_json_space(json, *at, size); + for (bool first = true; *at < size && json[*at] != ']'; first = false) { + if (!first) { + if (json[(*at)++] != ',') return "json"; + *at = pure_json_space(json, *at, size); + } + if (*at == size || json[(*at)++] != '{') return "json_type"; + double numbers[PURE_RECORD_FIELDS]; + PureSpan texts[PURE_RECORD_FIELDS]; + bool given[PURE_RECORD_FIELDS] = {false}; + *at = pure_json_space(json, *at, size); + for (bool member = true; *at < size && json[*at] != '}'; member = false) { + if (!member) { + if (json[(*at)++] != ',') return "json"; + *at = pure_json_space(json, *at, size); + } + size_t key_end = *at < size && json[*at] == '"' ? json_string_end(json + *at + 1, size - *at - 1) : 0; + if (!key_end) return "json"; + const unsigned char *key = json + *at + 1; + size_t key_size = key_end - 1; + *at = pure_json_space(json, *at + 1 + key_end, size); + if (*at == size || json[(*at)++] != ':') return "json"; + *at = pure_json_space(json, *at, size); + unsigned k = 0; + while (k < record->count && (strlen(m->record_field_names[record->first + k]) != key_size || + memcmp(m->record_field_names[record->first + k], key, key_size))) + ++k; + if (k == record->count) { + if (!b->lenient) return "json_field"; + if (!skip_value(json, size, at, depth + 1)) return "json"; + *at = pure_json_space(json, *at, size); + continue; + } + if (given[k]) return "json_field"; + given[k] = true; + bool text = m->record_field_text[record->first + k]; + if (*at < size && json[*at] == '"') { + size_t end = json_string_end(json + *at + 1, size - *at - 1), written; + if (!end) return "json"; + if (!text) return "json_type"; + unsigned char *value = json + *at + 1; + if (!memchr(value, '\\', end - 1)) written = end - 1; + else if (!json_unescape(value, end - 1, value, &written)) return "json"; + if (!pure_utf8_valid((PureSpan){value, written})) return "json"; + texts[k] = (PureSpan){value, written}; + *at = pure_json_space(json, *at + 1 + end, size); + continue; + } + bool integer; + size_t length = json_number(json + *at, size - *at, &integer); + if (!length) return *at < size && (json[*at] == '[' || json[*at] == '{' || json[*at] == 't' || json[*at] == 'f' || + json[*at] == 'n') ? "json_type" : "json"; + if (text || !json_double(json + *at, length, &numbers[k])) return "json_type"; + *at = pure_json_space(json, *at + length, size); + } + if (*at == size) return "json"; + ++*at; + for (unsigned k = 0; k < record->count; ++k) { + uint32_t at_field = record->first + k; + if (m->record_field_text[at_field]) { + uint32_t constant = (uint32_t)m->record_field_default[at_field]; + PureSpan text = given[k] ? texts[k] + : (PureSpan){m->constant_data + m->constant_offset[constant], m->constant_length[constant]}; + if (!rows_room(b, 4 + text.size)) return "memory"; + write_u32(b->rows + b->rows_size, (uint32_t)text.size); + if (text.size) memcpy(b->rows + b->rows_size + 4, text.data, text.size); + b->rows_size += 4 + text.size; + } else { + double value = given[k] ? numbers[k] : m->record_field_default[at_field]; + uint64_t bits; + memcpy(&bits, &value, 8); + if (!rows_room(b, 8)) return "memory"; + for (unsigned i = 0; i < 8; ++i) b->rows[b->rows_size + i] = (unsigned char)(bits >> (8 * i)); + b->rows_size += 8; + } + } + } + if (*at == size) return "json"; + ++*at; + if (b->rows_base + b->rows_size > UINT32_MAX >> 2) return "limit"; + write_u32(b->table + 8 * f, (uint32_t)(b->rows_base + start)); + write_u32(b->table + 8 * f + 4, (uint32_t)(b->rows_size - start)); + return NULL; +} + /* An object's members from json[*at] (its opening brace), in the group named * path: a field's value, or a group's object. Strings stay where they are * (an escaped one is unescaped over itself, which never makes it longer). @@ -226,6 +347,14 @@ static const char *members(PureJsonMessage *b, unsigned char *json, size_t size, *at = pure_json_space(json, *at, size); bool group; uint32_t f = field_named(b, path, path_size, key, key_size, &group); + if (f < b->shape->field_count && m->field[b->shape->field_start + f].type == PURE_FIELD_ROWS) { + if (b->seen[f] || (b->given && b->given[f])) return "json_field"; + b->seen[f] = true; + const char *why = rows_of(b, json, size, at, f, depth); + if (why) return why; + *at = pure_json_space(json, *at, size); + continue; + } if (f == b->shape->field_count && !group && b->lenient) { /* not the app's: skipped */ if (!skip_value(json, size, at, depth)) return "json"; *at = pure_json_space(json, *at, size); diff --git a/serve/message-json.h b/serve/message-json.h index 61211d6..c5332b4 100644 --- a/serve/message-json.h +++ b/serve/message-json.h @@ -14,10 +14,16 @@ typedef struct { const bool *given; /* fields given elsewhere (a request's query), or NULL */ bool lenient; /* a member naming no field is skipped, value and all (an API's answer) */ bool seen[PURE_MODULE_FIELDS]; + /* A rows field's array of objects is encoded in the stored form of its + * list into rows (grown with malloc: the caller frees it), which the + * caller places from rows_base on in the payload, as the table says. */ + unsigned char *rows; + size_t rows_size, rows_capacity, rows_base; } PureJsonMessage; /* The object at json[*at] (its opening brace), *at moved past it: NULL, or - * what is wrong ("json", "json_field": unknown or given twice, "json_type"). */ + * what is wrong ("json", "json_field": unknown or given twice, "json_type", + * "limit": rows past what a payload holds, "memory"). */ const char *pure_json_message(PureJsonMessage *, unsigned char *json, size_t size, size_t *at); size_t pure_json_space(const unsigned char *json, size_t at, size_t size); /* A fixed field's value from its text (a query value or a JSON number) into diff --git a/serve/serve.c b/serve/serve.c index 588feec..7fb3768 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -643,12 +643,14 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * if (*c->source && !message) return "delivery"; if (!host->owner) c->source[0] = 0; /* an app without states or outputs applies it as a message */ if (shape) { - /* A body is a text input's text, a message's one variable field, or a - * JSON object of a message's fields (two variable fields need one). */ + /* A body is a text input's text, a message's one variable field (rows + * in their list's stored form), or a JSON object of a message's fields + * (two variable fields need one). */ unsigned variables = 0; for (uint32_t f = 0; message && f < shape->field_count; ++f) variables += m->field[shape->field_start + f].type == PURE_FIELD_BYTES || - m->field[shape->field_start + f].type == PURE_FIELD_TEXT; + m->field[shape->field_start + f].type == PURE_FIELD_TEXT || + m->field[shape->field_start + f].type == PURE_FIELD_ROWS; if (c->json && !message) return "json"; if (!c->json && (variables > 1 || (c->body_size && shape->kind == PURE_KIND_KEY) || (c->body_size && message && !variables))) return variables > 1 ? "json" : "body"; @@ -706,31 +708,45 @@ enum { DELIVERY = 80 }; static const char *convert(Connection *c) { const PureInputShape *shape = &c->host->module->input[c->input]; uint32_t delivered = get_u32(c->ingress) & DELIVERED; - size_t table = 8 * (size_t)shape->field_count; + size_t table = 8 * (size_t)shape->field_count, payload = c->batch * c->field_bytes + c->body_size; unsigned char *tables = c->ingress + 4 + (delivered ? DELIVERY : 0), *prefixes = tables + c->batch * table; unsigned char *json = c->body; size_t size = c->body_size, at = pure_json_space(json, 0, size); + /* A message's rows go after the body: they are encoded, then placed. */ + PureJsonMessage b = {.module = c->host->module, .shape = shape, .given = c->given, .rows_base = payload}; + const char *why = NULL; /* A batch is an array of its messages' objects. */ if (c->batch > 1) { if (at == size || json[at++] != '[') return "json"; at = pure_json_space(json, at, size); } - for (unsigned i = 0; i < c->batch; ++i) { - if (i) { - if (at == size || json[at++] != ',') return "json"; - at = pure_json_space(json, at, size); - } - PureJsonMessage b = {.module = c->host->module, .shape = shape, .table = tables + i * table, - .payload = prefixes + i * c->field_bytes, .given = c->given}; - const char *why = pure_json_message(&b, json, size, &at); - if (why) return why; + for (unsigned i = 0; i < c->batch && !why; ++i) { + if (i && (at == size || json[at++] != ',')) why = "json"; + at = pure_json_space(json, at, size); + b.table = tables + i * table; + b.payload = prefixes + i * c->field_bytes; + memset(b.seen, 0, sizeof(b.seen)); + if (!why) why = pure_json_message(&b, json, size, &at); at = pure_json_space(json, at, size); - for (uint32_t f = 0; f < shape->field_count; ++f) - if (!b.seen[f] && !c->given[f]) return "missing_field"; + for (uint32_t f = 0; !why && f < shape->field_count; ++f) + if (!b.seen[f] && !c->given[f]) why = "missing_field"; + if (!why && c->batch > 1 && b.rows_size) why = "delivery"; /* an output sends no rows */ + } + if (!why && c->batch > 1 && (at == size || json[at++] != ']')) why = "json"; + if (!why && pure_json_space(json, at, size) != size) why = "json"; + if (!why && b.rows_size) { + size_t body = (size_t)(c->body - c->ingress); + PureResult grown = pure_runtime_grow(c->ticket, body + c->body_size + b.rows_size); + if (grown != PURE_OK) why = grown == PURE_BUDGET ? "payload_budget" : "memory"; + else { + c->ingress = pure_runtime_ingress(c->ticket, NULL); + c->body = c->ingress + body; + memcpy(c->body + c->body_size, b.rows, b.rows_size); + } } - if (c->batch > 1 && (at == size || json[at++] != ']')) return "json"; - if (pure_json_space(json, at, size) != size) return "json"; - put_u32(c->ingress, (uint32_t)(c->batch * c->field_bytes + c->body_size) | PLACED | delivered); + free(b.rows); + if (why) return why; + put_u32(c->ingress, (uint32_t)(payload + b.rows_size) | PLACED | delivered); return NULL; } @@ -784,7 +800,8 @@ static void body_done(Connection *c) { const char *why = c->route == ROUTE_INPUT && c->json ? convert(c) : NULL; if (why) { release_ticket(c); /* never made ready: it never runs */ - respond(c, 400, "Bad Request", why, 0, (PureSpan){0}); + if (!strcmp(why, "payload_budget") || !strcmp(why, "memory")) respond(c, 503, "Service Unavailable", why, 0, (PureSpan){0}); + else respond(c, 400, "Bad Request", why, 0, (PureSpan){0}); return; } if (!pure_runtime_ready(c->ticket)) abort(); |
