diff options
| author | eunhhu <[email protected]> | 2026-09-27 08:15:05 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-27 08:15:05 +0100 |
| commit | 25397ebb79fb554f4fa795603ee84d2aede83594 (patch) | |
| tree | 3e473e9dafdbfa07ede3e07ea6bba199a62ab0e4 /prototypes/connections | |
| parent | 24a07a0bbfe98924b339f4ffeeaef093e74f73fb (diff) | |
| download | pure-25397ebb79fb554f4fa795603ee84d2aede83594.tar.gz pure-25397ebb79fb554f4fa795603ee84d2aede83594.zip | |
Read JSON messages in message-json.c, for requests and answers alike
The in-place JSON reader of pure-serve's request bodies moves into its
own file with a small interface (pure_json_message, pure_json_fixed,
pure_json_space), so the delivery client can read an API's answers
with it. It gains a lenient mode for answers: a member that names no
field is skipped, value and all. Requests read as before.
Diffstat (limited to 'prototypes/connections')
| -rw-r--r-- | prototypes/connections/Makefile | 4 | ||||
| -rw-r--r-- | prototypes/connections/message-json.c | 281 | ||||
| -rw-r--r-- | prototypes/connections/message-json.h | 26 | ||||
| -rw-r--r-- | prototypes/connections/serve.c | 230 |
4 files changed, 319 insertions, 222 deletions
diff --git a/prototypes/connections/Makefile b/prototypes/connections/Makefile index fe575b5..2dd85c7 100644 --- a/prototypes/connections/Makefile +++ b/prototypes/connections/Makefile @@ -44,8 +44,8 @@ $(BUILD)/pure-connections-async-gated: server.c $(HOST) flow.c program.c $(CORE) $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_RUNTIME_METRICS -DPURE_ASYNC_EFFECTS -DPURE_HTTP_ASYNC -DPURE_STORE_TEST server.c $(HOST) flow.c program.c $(CORE) test-async-server-gate.c $(LDFLAGS) -pthread $(LDLIBS) -o $@ # The HTTP host for aup modules: persistent connections, epoll, group commits. -$(BUILD)/pure-serve: serve.c deliver.c flow.c module-store.c $(MODULE) $(CORE) deliver.h module-store.h $(MODULE_HEADERS) | $(BUILD) - $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_ASYNC_EFFECTS serve.c deliver.c flow.c module-store.c $(MODULE) $(CORE) $(LDFLAGS) -lssl -lcrypto -lm $(LDLIBS) -o $@ +$(BUILD)/pure-serve: serve.c deliver.c message-json.c flow.c module-store.c $(MODULE) $(CORE) deliver.h message-json.h module-store.h $(MODULE_HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_ASYNC_EFFECTS serve.c deliver.c message-json.c flow.c module-store.c $(MODULE) $(CORE) $(LDFLAGS) -lssl -lcrypto -lm $(LDLIBS) -o $@ $(BUILD)/pure-connections-fixed: server.c $(HOST) flow.c $(CORE) $(HEADERS) $(BUILD)/embedded-plan.h | $(BUILD) $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_RUNTIME_METRICS -DPURE_EMBEDDED_PROGRAM -I$(BUILD) server.c $(HOST) flow.c $(CORE) $(LDFLAGS) $(LDLIBS) -o $@ diff --git a/prototypes/connections/message-json.c b/prototypes/connections/message-json.c new file mode 100644 index 0000000..66c812d --- /dev/null +++ b/prototypes/connections/message-json.c @@ -0,0 +1,281 @@ +#include "message-json.h" +#include "module-text.h" +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void write_u32(unsigned char *p, uint32_t v) { for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i)); } + +static bool decimal(const char *text, uint64_t maximum, uint64_t *value) { + if (!*text) return false; + uint64_t n = 0; + for (; *text; ++text) { + if (*text < '0' || *text > '9') return false; + unsigned digit = (unsigned)(*text - '0'); + if (n > (maximum - digit) / 10) return false; + n = n * 10 + digit; + } + *value = n; + return true; +} + +/* A JSON number's text: -?(0|[1-9][0-9]*)(.[0-9]+)?([eE][+-]?[0-9]+)?, its + * length (0 if there is none). An integer has neither part after it. */ +static size_t json_number(const unsigned char *p, size_t size, bool *integer) { + size_t i = 0; + *integer = true; + if (i < size && p[i] == '-') ++i; + if (i == size || p[i] < '0' || p[i] > '9') return 0; + if (p[i++] != '0') while (i < size && p[i] >= '0' && p[i] <= '9') ++i; + if (i < size && p[i] == '.') { + *integer = false; + if (++i == size || p[i] < '0' || p[i] > '9') return 0; + while (i < size && p[i] >= '0' && p[i] <= '9') ++i; + } + if (i < size && (p[i] == 'e' || p[i] == 'E')) { + *integer = false; + if (++i < size && (p[i] == '+' || p[i] == '-')) ++i; + if (i == size || p[i] < '0' || p[i] > '9') return 0; + while (i < size && p[i] >= '0' && p[i] <= '9') ++i; + } + 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) { + bool integer; + if (!size || json_number(text, size, &integer) != size || size > 40) return false; + char digits[48]; + memcpy(digits, text, size); + digits[size] = 0; + if (field->type == PURE_FIELD_U32) { + uint64_t n; + 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; + memcpy(prefix + field->offset, &value, 8); + return true; +} + +/* A JSON string's contents (after its opening quote) up to the closing one: + * its length, or 0 when it does not close or holds a control byte. */ +static size_t json_string_end(const unsigned char *p, size_t size) { + for (size_t i = 0; i < size; ++i) { + /* Eight bytes at a time while none is a quote, a backslash or a control byte. */ + const uint64_t ones = UINT64_C(0x0101010101010101), highs = UINT64_C(0x8080808080808080); + for (uint64_t w, q, s; size - i >= 8; i += 8) { + memcpy(&w, p + i, 8); + q = w ^ (ones * '"'); + s = w ^ (ones * '\\'); + if (((q - ones) & ~q & highs) | ((s - ones) & ~s & highs) | ((w - ones * 0x20) & ~w & highs)) break; + } + if (i == size) break; + if (p[i] == '"') return i + 1; + if (p[i] < 0x20) return 0; + if (p[i] == '\\' && ++i == size) return 0; + } + return 0; +} + +static int hex_digit(unsigned char c) { + return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; +} + +static bool json_code(const unsigned char *p, size_t left, uint32_t *code) { + if (left < 4) return false; + *code = 0; + for (int i = 0; i < 4; ++i) { + int d = hex_digit(p[i]); + if (d < 0) return false; + *code = *code << 4 | (uint32_t)d; + } + return true; +} + +/* Unescape a JSON string's contents into out (never longer); false for a + * bad escape or a lone surrogate. The result is checked as UTF-8 after. */ +static bool json_unescape(const unsigned char *in, size_t size, unsigned char *out, size_t *written) { + size_t o = 0; + for (size_t i = 0; i < size;) { + unsigned char b = in[i++]; + if (b != '\\') { out[o++] = b; continue; } + if (i == size) return false; + b = in[i++]; + const char *plain = strchr("\"\\/bfnrt", b); + if (b && plain) { out[o++] = (unsigned char)"\"\\/\b\f\n\r\t"[plain - "\"\\/bfnrt"]; continue; } + uint32_t code, low; + if (b != 'u' || !json_code(in + i, size - i, &code)) return false; + i += 4; + if (code >= 0xDC00 && code <= 0xDFFF) return false; + if (code >= 0xD800 && code <= 0xDBFF) { + if (size - i < 6 || in[i] != '\\' || in[i + 1] != 'u' || !json_code(in + i + 2, size - i - 2, &low) || + low < 0xDC00 || low > 0xDFFF) return false; + i += 6; + code = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00); + } + if (code < 0x80) out[o++] = (unsigned char)code; + else if (code < 0x800) { out[o++] = (unsigned char)(0xC0 | code >> 6); out[o++] = (unsigned char)(0x80 | (code & 0x3F)); } + else if (code < 0x10000) { + out[o++] = (unsigned char)(0xE0 | code >> 12); + out[o++] = (unsigned char)(0x80 | (code >> 6 & 0x3F)); + out[o++] = (unsigned char)(0x80 | (code & 0x3F)); + } else { + out[o++] = (unsigned char)(0xF0 | code >> 18); + out[o++] = (unsigned char)(0x80 | (code >> 12 & 0x3F)); + out[o++] = (unsigned char)(0x80 | (code >> 6 & 0x3F)); + out[o++] = (unsigned char)(0x80 | (code & 0x3F)); + } + } + *written = o; + return true; +} + +size_t pure_json_space(const unsigned char *p, size_t at, size_t size) { + while (at < size && (p[at] == ' ' || p[at] == '\t' || p[at] == '\n' || p[at] == '\r')) ++at; + return at; +} + +/* Skip any JSON value at json[*at] (at most 64 deep): a member of an + * answer that names no field. */ +static bool skip_value(const unsigned char *json, size_t size, size_t *at, unsigned depth) { + if (*at == size || depth > 64) return false; + unsigned char b = json[*at]; + bool integer; + if (b == '"') { + size_t end = json_string_end(json + *at + 1, size - *at - 1); + if (!end) return false; + *at += 1 + end; + return true; + } + if (b == '{' || b == '[') { + unsigned char close = b == '{' ? '}' : ']'; + *at = pure_json_space(json, *at + 1, size); + for (bool first = true; *at < size && json[*at] != close; first = false) { + if (!first) { + if (json[(*at)++] != ',') return false; + *at = pure_json_space(json, *at, size); + } + if (close == '}') { + size_t key_end = *at < size && json[*at] == '"' ? json_string_end(json + *at + 1, size - *at - 1) : 0; + if (!key_end) return false; + *at = pure_json_space(json, *at + 1 + key_end, size); + if (*at == size || json[(*at)++] != ':') return false; + *at = pure_json_space(json, *at, size); + } + if (!skip_value(json, size, at, depth + 1)) return false; + *at = pure_json_space(json, *at, size); + } + if (*at == size) return false; + ++*at; + return true; + } + static const char *const words[] = {"true", "false", "null"}; + for (size_t w = 0; w < 3; ++w) + if (size - *at >= strlen(words[w]) && !memcmp(json + *at, words[w], strlen(words[w]))) { + *at += strlen(words[w]); + return true; + } + size_t length = json_number(json + *at, size - *at, &integer); + *at += length; + return length > 0; +} + +/* The field named path.key (key alone at the top), or the field count when + * none is; group says whether fields are named inside it instead. */ +static uint32_t field_named(const PureJsonMessage *b, const char *path, size_t path_size, const unsigned char *key, + size_t key_size, bool *group) { + const PureModule *m = b->module; + size_t size = path_size + !!path_size + key_size; + *group = false; + for (uint32_t f = 0; f < b->shape->field_count; ++f) { + const char *name = m->field[b->shape->field_start + f].name; + size_t length = strlen(name); + if (length < size || memcmp(name, path, path_size) || (path_size && name[path_size] != '.') || + memcmp(name + path_size + !!path_size, key, key_size)) continue; + if (length == size) return f; + if (name[size] == '.') *group = true; + } + return b->shape->field_count; +} + +/* 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). + * NULL, or the error to answer. */ +static const char *members(PureJsonMessage *b, unsigned char *json, size_t size, size_t *at, const char *path, size_t path_size, + unsigned depth) { + const PureModule *m = b->module; + if (*at == size || json[(*at)++] != '{') return "json"; + *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); + } + 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); + bool group; + uint32_t f = field_named(b, path, path_size, key, key_size, &group); + 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); + continue; + } + if (*at < size && json[*at] == '{') { + if (!group) return f < b->shape->field_count ? "json_type" : "json_field"; + char inner[PURE_MODULE_NAME]; + size_t inner_size = path_size + !!path_size + key_size; + memcpy(inner, path, path_size); + if (path_size) inner[path_size] = '.'; + memcpy(inner + path_size + !!path_size, key, key_size); + const char *why = members(b, json, size, at, inner, inner_size, depth + 1); + if (why) return why; + *at = pure_json_space(json, *at, size); + continue; + } + if (f == b->shape->field_count) return group ? "json_type" : "json_field"; + if (b->seen[f] || (b->given && b->given[f])) return "json_field"; + b->seen[f] = true; + const PureField *field = &m->field[b->shape->field_start + f]; + bool variable = field->type == PURE_FIELD_BYTES || field->type == PURE_FIELD_TEXT, integer; + if (*at < size && json[*at] == '"') { + size_t end = json_string_end(json + *at + 1, size - *at - 1), written; + if (!end) return "json"; + if (!variable) 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 (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){value, written})) return "json"; + write_u32(b->table + 8 * f, (uint32_t)(value - b->payload)); + write_u32(b->table + 8 * f + 4, (uint32_t)written); + *at = pure_json_space(json, *at + 1 + end, size); + continue; + } + size_t length = json_number(json + *at, size - *at, &integer); + if (!length) { + static const char *const words[] = {"true", "false", "null"}; + for (size_t w = 0; w < 3 && !length; ++w) + if (size - *at >= strlen(words[w]) && !memcmp(json + *at, words[w], strlen(words[w]))) return "json_type"; + return *at < size && json[*at] == '[' ? "json_type" : "json"; + } + if (variable || !pure_json_fixed(field, json + *at, length, b->payload)) return "json_type"; + *at = pure_json_space(json, *at + length, size); + } + if (*at == size) return "json"; + ++*at; + return NULL; +} + +const char *pure_json_message(PureJsonMessage *message, unsigned char *json, size_t size, size_t *at) { + return members(message, json, size, at, "", 0, 0); +} diff --git a/prototypes/connections/message-json.h b/prototypes/connections/message-json.h new file mode 100644 index 0000000..61211d6 --- /dev/null +++ b/prototypes/connections/message-json.h @@ -0,0 +1,26 @@ +#ifndef PURE_MESSAGE_JSON_H +#define PURE_MESSAGE_JSON_H +#include "module.h" + +/* A message's fields from a JSON object read in place (docs/research/082-json-in-place.md): + * a u32 or a number for a fixed field (put into the prefix), a string for + * bytes or text (left where it is: an escaped one is unescaped over itself, + * which never makes it longer), a group's fields as an object inside. */ +typedef struct { + const PureModule *module; + const PureInputShape *shape; + unsigned char *payload; /* the prefix; a string's start is counted from here */ + unsigned char *table; /* for each field, its start and size (u32le each) */ + 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]; +} 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"). */ +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 + * 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); +#endif diff --git a/prototypes/connections/serve.c b/prototypes/connections/serve.c index d175e29..88a9a6f 100644 --- a/prototypes/connections/serve.c +++ b/prototypes/connections/serve.c @@ -30,6 +30,7 @@ * threads hand it their ready requests and answer what it hands back. Its * bursts take the requests of every thread, one save each. */ #include "deliver.h" +#include "message-json.h" #include "module-store.h" #include "module-text.h" #include "runtime.h" @@ -482,48 +483,6 @@ static void put_u64(unsigned char *p, uint64_t v) { for (unsigned i = 0; i < 8; static uint32_t get_u32(const unsigned char *p) { return p[0] | p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; } static uint64_t get_u64(const unsigned char *p) { return get_u32(p) | (uint64_t)get_u32(p + 4) << 32; } -/* A JSON number's text: -?(0|[1-9][0-9]*)(.[0-9]+)?([eE][+-]?[0-9]+)?, its - * length (0 if there is none). An integer has neither part after it. */ -static size_t json_number(const unsigned char *p, size_t size, bool *integer) { - size_t i = 0; - *integer = true; - if (i < size && p[i] == '-') ++i; - if (i == size || p[i] < '0' || p[i] > '9') return 0; - if (p[i++] != '0') while (i < size && p[i] >= '0' && p[i] <= '9') ++i; - if (i < size && p[i] == '.') { - *integer = false; - if (++i == size || p[i] < '0' || p[i] > '9') return 0; - while (i < size && p[i] >= '0' && p[i] <= '9') ++i; - } - if (i < size && (p[i] == 'e' || p[i] == 'E')) { - *integer = false; - if (++i < size && (p[i] == '+' || p[i] == '-')) ++i; - if (i == size || p[i] < '0' || p[i] > '9') return 0; - while (i < size && p[i] >= '0' && p[i] <= '9') ++i; - } - 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. */ -static bool fixed_field(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; - char digits[48]; - memcpy(digits, text, size); - digits[size] = 0; - if (field->type == PURE_FIELD_U32) { - uint64_t n; - if (!integer || !number(digits, UINT32_MAX, &n)) return false; - put_u32(prefix + field->offset, (uint32_t)n); - return true; - } - double value = strtod(digits, NULL); - if (!isfinite(value)) return false; - memcpy(prefix + field->offset, &value, 8); - return true; -} - /* What an app route names in the module, with a message's fixed fields * taken from the query into c->fields. NULL, or the error to answer (404 * for a name the module lacks, 400 otherwise). */ @@ -579,7 +538,7 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * while (f < (message ? shape->field_count : 0) && strcmp(m->field[shape->field_start + f].name, pair)) ++f; const PureField *field = message && f < shape->field_count ? &m->field[shape->field_start + f] : NULL; if (!field || c->given[f] || (field->type != PURE_FIELD_U32 && field->type != PURE_FIELD_NUMBER) || - !fixed_field(field, (const unsigned char *)value, strlen(value), c->fields)) return "query"; + !pure_json_fixed(field, (const unsigned char *)value, strlen(value), c->fields)) return "query"; c->given[f] = true; } pair = end ? end + 1 : NULL; @@ -595,84 +554,6 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * return NULL; } -/* A JSON string's contents (after its opening quote) up to the closing one: - * its length, or 0 when it does not close or holds a control byte. */ -static size_t json_string_end(const unsigned char *p, size_t size) { - for (size_t i = 0; i < size; ++i) { - /* Eight bytes at a time while none is a quote, a backslash or a control byte. */ - const uint64_t ones = UINT64_C(0x0101010101010101), highs = UINT64_C(0x8080808080808080); - for (uint64_t w, q, s; size - i >= 8; i += 8) { - memcpy(&w, p + i, 8); - q = w ^ (ones * '"'); - s = w ^ (ones * '\\'); - if (((q - ones) & ~q & highs) | ((s - ones) & ~s & highs) | ((w - ones * 0x20) & ~w & highs)) break; - } - if (i == size) break; - if (p[i] == '"') return i + 1; - if (p[i] < 0x20) return 0; - if (p[i] == '\\' && ++i == size) return 0; - } - return 0; -} - -static int hex_digit(unsigned char c) { - return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; -} - -static bool json_code(const unsigned char *p, size_t left, uint32_t *code) { - if (left < 4) return false; - *code = 0; - for (int i = 0; i < 4; ++i) { - int d = hex_digit(p[i]); - if (d < 0) return false; - *code = *code << 4 | (uint32_t)d; - } - return true; -} - -/* Unescape a JSON string's contents into out (never longer); false for a - * bad escape or a lone surrogate. The result is checked as UTF-8 after. */ -static bool json_unescape(const unsigned char *in, size_t size, unsigned char *out, size_t *written) { - size_t o = 0; - for (size_t i = 0; i < size;) { - unsigned char b = in[i++]; - if (b != '\\') { out[o++] = b; continue; } - if (i == size) return false; - b = in[i++]; - const char *plain = strchr("\"\\/bfnrt", b); - if (b && plain) { out[o++] = (unsigned char)"\"\\/\b\f\n\r\t"[plain - "\"\\/bfnrt"]; continue; } - uint32_t code, low; - if (b != 'u' || !json_code(in + i, size - i, &code)) return false; - i += 4; - if (code >= 0xDC00 && code <= 0xDFFF) return false; - if (code >= 0xD800 && code <= 0xDBFF) { - if (size - i < 6 || in[i] != '\\' || in[i + 1] != 'u' || !json_code(in + i + 2, size - i - 2, &low) || - low < 0xDC00 || low > 0xDFFF) return false; - i += 6; - code = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00); - } - if (code < 0x80) out[o++] = (unsigned char)code; - else if (code < 0x800) { out[o++] = (unsigned char)(0xC0 | code >> 6); out[o++] = (unsigned char)(0x80 | (code & 0x3F)); } - else if (code < 0x10000) { - out[o++] = (unsigned char)(0xE0 | code >> 12); - out[o++] = (unsigned char)(0x80 | (code >> 6 & 0x3F)); - out[o++] = (unsigned char)(0x80 | (code & 0x3F)); - } else { - out[o++] = (unsigned char)(0xF0 | code >> 18); - out[o++] = (unsigned char)(0x80 | (code >> 12 & 0x3F)); - out[o++] = (unsigned char)(0x80 | (code >> 6 & 0x3F)); - out[o++] = (unsigned char)(0x80 | (code & 0x3F)); - } - } - *written = o; - return true; -} - -static size_t json_space(const unsigned char *p, size_t at, size_t size) { - while (at < size && (p[at] == ' ' || p[at] == '\t' || p[at] == '\n' || p[at] == '\r')) ++at; - return at; -} - /* Parsing a JSON body in place: the payload it is in, the placed fields' * table, and which fields it gave. */ #define PLACED (UINT32_C(1) << 31) @@ -682,98 +563,6 @@ static size_t json_space(const unsigned char *p, size_t at, size_t size) { * and the JSON. */ #define DELIVERED (UINT32_C(1) << 30) enum { DELIVERY = 80 }; -typedef struct { - Connection *c; - const PureInputShape *shape; - unsigned char *payload, *table; - bool seen[PURE_MODULE_FIELDS]; -} Body; - -/* The field named path.key (key alone at the top), or the field count when - * none is; group says whether fields are named inside it instead. */ -static uint32_t field_named(const Body *b, const char *path, size_t path_size, const unsigned char *key, - size_t key_size, bool *group) { - const PureModule *m = b->c->host->module; - size_t size = path_size + !!path_size + key_size; - *group = false; - for (uint32_t f = 0; f < b->shape->field_count; ++f) { - const char *name = m->field[b->shape->field_start + f].name; - size_t length = strlen(name); - if (length < size || memcmp(name, path, path_size) || (path_size && name[path_size] != '.') || - memcmp(name + path_size + !!path_size, key, key_size)) continue; - if (length == size) return f; - if (name[size] == '.') *group = true; - } - return b->shape->field_count; -} - -/* 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). - * NULL, or the error to answer. */ -static const char *members(Body *b, unsigned char *json, size_t size, size_t *at, const char *path, size_t path_size) { - const PureModule *m = b->c->host->module; - if (*at == size || json[(*at)++] != '{') return "json"; - *at = json_space(json, *at, size); - for (bool first = true; *at < size && json[*at] != '}'; first = false) { - if (!first) { - if (json[(*at)++] != ',') return "json"; - *at = 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 = json_space(json, *at + 1 + key_end, size); - if (*at == size || json[(*at)++] != ':') return "json"; - *at = json_space(json, *at, size); - bool group; - uint32_t f = field_named(b, path, path_size, key, key_size, &group); - if (*at < size && json[*at] == '{') { - if (!group) return f < b->shape->field_count ? "json_type" : "json_field"; - char inner[PURE_MODULE_NAME]; - size_t inner_size = path_size + !!path_size + key_size; - memcpy(inner, path, path_size); - if (path_size) inner[path_size] = '.'; - memcpy(inner + path_size + !!path_size, key, key_size); - const char *why = members(b, json, size, at, inner, inner_size); - if (why) return why; - *at = json_space(json, *at, size); - continue; - } - if (f == b->shape->field_count) return group ? "json_type" : "json_field"; - if (b->seen[f] || b->c->given[f]) return "json_field"; - b->seen[f] = true; - const PureField *field = &m->field[b->shape->field_start + f]; - bool variable = field->type == PURE_FIELD_BYTES || field->type == PURE_FIELD_TEXT, integer; - if (*at < size && json[*at] == '"') { - size_t end = json_string_end(json + *at + 1, size - *at - 1), written; - if (!end) return "json"; - if (!variable) 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 (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){value, written})) return "json"; - put_u32(b->table + 8 * f, (uint32_t)(value - b->payload)); - put_u32(b->table + 8 * f + 4, (uint32_t)written); - *at = json_space(json, *at + 1 + end, size); - continue; - } - size_t length = json_number(json + *at, size - *at, &integer); - if (!length) { - static const char *const words[] = {"true", "false", "null"}; - for (size_t w = 0; w < 3 && !length; ++w) - if (size - *at >= strlen(words[w]) && !memcmp(json + *at, words[w], strlen(words[w]))) return "json_type"; - return *at < size && json[*at] == '[' ? "json_type" : "json"; - } - if (variable || !fixed_field(field, json + *at, length, b->payload)) return "json_type"; - *at = json_space(json, *at + length, size); - } - if (*at == size) return "json"; - ++*at; - return NULL; -} - /* An app input's JSON body becomes its message: an object with a member for * each field the query did not give (a u32, a number, or a string for bytes * and text), groups as objects inside it, and nothing else. Fixed fields go @@ -785,26 +574,27 @@ static const char *convert(Connection *c) { size_t table = 8 * (size_t)shape->field_count; 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 = json_space(json, 0, size); + size_t size = c->body_size, at = pure_json_space(json, 0, size); /* A batch is an array of its messages' objects. */ if (c->batch > 1) { if (at == size || json[at++] != '[') return "json"; - at = json_space(json, at, size); + 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 = json_space(json, at, size); + at = pure_json_space(json, at, size); } - Body b = {.c = c, .shape = shape, .table = tables + i * table, .payload = prefixes + i * c->field_bytes}; - const char *why = members(&b, json, size, &at, "", 0); + 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; - at = json_space(json, at, size); + 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"; } if (c->batch > 1 && (at == size || json[at++] != ']')) return "json"; - if (json_space(json, at, size) != size) 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); return NULL; } |
