aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 08:15:05 +0100
committereunhhu <[email protected]>2026-09-27 08:15:05 +0100
commit25397ebb79fb554f4fa795603ee84d2aede83594 (patch)
tree3e473e9dafdbfa07ede3e07ea6bba199a62ab0e4 /prototypes
parent24a07a0bbfe98924b339f4ffeeaef093e74f73fb (diff)
downloadpure-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')
-rw-r--r--prototypes/connections/Makefile4
-rw-r--r--prototypes/connections/message-json.c281
-rw-r--r--prototypes/connections/message-json.h26
-rw-r--r--prototypes/connections/serve.c230
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;
}