diff options
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/deliver.c | 11 | ||||
| -rw-r--r-- | serve/message-json.c | 42 | ||||
| -rw-r--r-- | serve/serve.c | 27 | ||||
| -rw-r--r-- | serve/test-message-json.c | 21 |
4 files changed, 80 insertions, 21 deletions
diff --git a/serve/deliver.c b/serve/deliver.c index 6e402a5..9ed4529 100644 --- a/serve/deliver.c +++ b/serve/deliver.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "deliver.h" +#include <inttypes.h> #include "wait.h" #include "seal.h" #include "message-json.h" @@ -151,6 +152,9 @@ static bool encode(const PureWire *w, PureSpan payload, Text *t) { put(t, digits, (size_t)snprintf(digits, sizeof(digits), "%u", (unsigned)read_u32(payload.data + field->offset))); } else if (field->type == PURE_FIELD_BOOL) { put(t, payload.data[field->offset] ? "true" : "false", payload.data[field->offset] ? 4 : 5); + } else if (field->type == PURE_FIELD_I64) { /* every digit */ + uint64_t bits = read_u32(payload.data + field->offset) | (uint64_t)read_u32(payload.data + field->offset + 4) << 32; + put(t, digits, (size_t)snprintf(digits, sizeof(digits), "%" PRId64, (int64_t)bits)); } else if (field->type == PURE_FIELD_NUMBER) { uint64_t bits = read_u32(payload.data + field->offset) | (uint64_t)read_u32(payload.data + field->offset + 4) << 32; double value; @@ -428,10 +432,11 @@ static void drop_connection(PureWire *w) { pthread_mutex_unlock(&w->lock); } -/* A fault a Pure receiver answers when its app refused the message. */ +/* A fault a Pure receiver answers when its app refused the message: any + * but memory (its budget: it may take the message later). */ static bool refusal(const char *error) { - static const char *const faults[] = {"divide", "non_finite", "clamp", "limit", "not_found", "encoding", "exists"}; - for (size_t i = 0; i < sizeof(faults) / sizeof(*faults); ++i) if (!strcmp(error, faults[i])) return true; + for (unsigned f = PURE_FAULT_DIVIDE; f < PURE_FAULT_COUNT; ++f) + if (f != PURE_FAULT_MEMORY && !strcmp(error, pure_fault_name((PureFault)f))) return true; return false; } diff --git a/serve/message-json.c b/serve/message-json.c index afbd541..0905b34 100644 --- a/serve/message-json.c +++ b/serve/message-json.c @@ -83,6 +83,20 @@ static bool json_double(const unsigned char *text, size_t size, double *value) { return isfinite(*value); } +/* A JSON integer in an i64's range (no point, no exponent), exactly: false for any other text. */ +static bool json_integer(const unsigned char *text, size_t size, int64_t *value) { + bool integer, negative = size && text[0] == '-'; + if (!size || json_number(text, size, &integer) != size || !integer) return false; + uint64_t n = 0, most = negative ? (uint64_t)INT64_MAX + 1 : (uint64_t)INT64_MAX; + for (size_t i = negative; i < size; ++i) { + unsigned digit = (unsigned)(text[i] - '0'); + if (n > (most - digit) / 10) return false; + n = n * 10 + digit; + } + *value = !negative ? (int64_t)n : n ? -(int64_t)(n - 1) - 1 : 0; /* -2^63 is; -0 is 0 */ + return true; +} + /* A JSON true or false at text: its length (4 or 5), or 0. */ static size_t json_bool(const unsigned char *text, size_t size, bool *value) { if (size >= 4 && !memcmp(text, "true", 4)) return *value = true, 4; @@ -91,7 +105,8 @@ static size_t json_bool(const unsigned char *text, size_t size, bool *value) { } /* A fixed field's value from its text (a query value or a JSON value) into - * the prefix: a u32 in range, a finite number, or true or false for a bool. */ + * the prefix: a u32 in range, a finite number, true or false for a bool, an + * integer in range for an i64. */ bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix) { if (field->type == PURE_FIELD_BOOL) { /* true or false, as a JSON value says it */ bool value; @@ -99,6 +114,12 @@ bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t s prefix[field->offset] = value; return true; } + if (field->type == PURE_FIELD_I64) { + int64_t value; + if (!json_integer(text, size, &value)) return false; + for (unsigned i = 0; i < 8; ++i) prefix[field->offset + i] = (unsigned char)((uint64_t)value >> (8 * i)); + return true; + } bool integer; if (!size || json_number(text, size, &integer) != size || size > 40) return false; if (field->type == PURE_FIELD_U32) { @@ -289,7 +310,7 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, *at = pure_json_space(json, *at, size); } if (*at == size || json[(*at)++] != '{') return "json_type"; - double numbers[PURE_RECORD_FIELDS]; + PureValue numbers[PURE_RECORD_FIELDS]; /* a number's, a bool's or an i64's */ PureSpan texts[PURE_RECORD_FIELDS]; bool given[PURE_RECORD_FIELDS] = {false}; *at = pure_json_space(json, *at, size); @@ -317,11 +338,12 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, } if (given[k]) return "json_field"; given[k] = true; - bool text = m->record_field_text[record->first + k], truth; - if (m->record_field_bool[record->first + k]) { /* true or false, nothing else */ + uint8_t type = m->record_field_type[record->first + k]; + bool text = type == PURE_RECORD_TEXT, truth; + if (type == PURE_RECORD_BOOL) { /* true or false, nothing else */ size_t word = json_bool(json + *at, size - *at, &truth); if (!word) return "json_type"; - numbers[k] = truth; + numbers[k].num = truth; *at = pure_json_space(json, *at + word, size); continue; } @@ -341,14 +363,16 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size, 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"; + if (text || !(type == PURE_RECORD_I64 ? json_integer(json + *at, length, &numbers[k].i64) + : json_double(json + *at, length, &numbers[k].num))) + 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]) { + if (m->record_field_type[at_field] == PURE_RECORD_TEXT) { uint32_t constant = (uint32_t)m->record_field_default[at_field].num; PureSpan text = given[k] ? texts[k] : (PureSpan){m->constant_data + m->constant_offset[constant], m->constant_length[constant]}; @@ -357,9 +381,7 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t 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].num; - uint64_t bits; - memcpy(&bits, &value, 8); + uint64_t bits = (uint64_t)(given[k] ? numbers[k] : m->record_field_default[at_field]).i64; /* its bits */ 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; diff --git a/serve/serve.c b/serve/serve.c index 43297c1..6d9d5d6 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -525,15 +525,12 @@ static void answer(Connection *c, PureOutcome result) { if (!result.body.size) release_ticket(c); } -static const char *const fault_names[] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", "encoding", - "exists", "memory"}; - /* An app route's answer: a view's value, or what an event replied. A failed * event names its fault: 404 not_found, 409 exists, 503 memory (the app's * budget: again later), 422 for the others; a view without a value is 404 * no_value. */ static void answer_app(Connection *c, PureOutcome result, bool view, PureFault fault, const char *type) { - const char *named = fault != PURE_FAULT_NONE ? fault_names[fault] : NULL; + const char *named = fault != PURE_FAULT_NONE ? pure_fault_name(fault) : NULL; if (result.status == PURE_OK) { struct iovec extent = {(void *)result.body.data, result.body.size}; respond_typed(c, 200, "OK", "none", 0, type ? type : "application/octet-stream", &extent, @@ -1225,6 +1222,12 @@ static void put_number(Output *o, double value) { put_bytes(o, digits, pure_format_number(value, digits)); } +/* An i64's every digit (a JSON number too: a reader keeping binary64s loses those past 2^53). */ +static void put_integer(Output *o, int64_t value) { + char digits[24]; + put_bytes(o, digits, (size_t)snprintf(digits, sizeof(digits), "%" PRId64, value)); +} + /* A view's arguments from a query's name=value pairs (values %XX decoded; * from and count page a table): a payload of their input's shape, the * fixed fields in its prefix, the texts and bytes after it in order, each's @@ -1303,7 +1306,12 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available, PureResult status = PURE_OK; double value; if (m->view_type[view] == PURE_VIEW_TABLE && !(o.bytes = malloc(o.capacity = 1024))) return PURE_ALLOCATION; - if (m->view_type[view] == PURE_VIEW_NUM || m->view_type[view] == PURE_VIEW_BOOL) { + if (m->view_type[view] == PURE_VIEW_I64) { + int64_t integer; + *type = "text/plain; charset=utf-8"; + if (!pure_app_view_i64(app, view, &integer)) status = PURE_NOT_FOUND; + else put_integer(&o, integer); + } else if (m->view_type[view] == PURE_VIEW_NUM || m->view_type[view] == PURE_VIEW_BOOL) { *type = "text/plain; charset=utf-8"; if (!pure_app_view(app, view, &value)) status = PURE_NOT_FOUND; else if (m->view_type[view] == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5); @@ -1343,6 +1351,12 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available, for (unsigned k = 0; k < count; ++k) { put_bytes(&o, columns[k].key + !k, columns[k].length - !k); uint8_t kind = m->view_type[columns[k].view]; + if (kind == PURE_VIEW_I64) { + int64_t integer; + if (!pure_app_cell_i64(app, columns[k].view, row, &integer)) put_bytes(&o, "null", 4); + else put_integer(&o, integer); + continue; + } if (kind == PURE_VIEW_NUM || kind == PURE_VIEW_BOOL) { if (!pure_app_cell(app, columns[k].view, row, &value)) put_bytes(&o, "null", 4); else if (kind == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5); @@ -1858,8 +1872,7 @@ static bool same_states(const PureModule *a, const PureModule *b) { a->record[i].count != b->record[i].count) return false; for (uint32_t i = 0; i < a->record_fields; ++i) - if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_text[i] != b->record_field_text[i] || - a->record_field_bool[i] != b->record_field_bool[i]) + if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_type[i] != b->record_field_type[i]) return false; for (uint32_t i = 0; i < a->collections; ++i) if (strcmp(a->collection_names[i], b->collection_names[i])) return false; diff --git a/serve/test-message-json.c b/serve/test-message-json.c index b2bad37..eb98f47 100644 --- a/serve/test-message-json.c +++ b/serve/test-message-json.c @@ -1,7 +1,8 @@ /* JSON numbers are read as strtod reads them, bit for bit: in the forms a * client writes (a program's shortest digits, fixed decimals, exponents), at * the edges of the exact path (2^53, 10^22) and past them, and refused when - * not finite or not JSON. A bool is true or false, nothing else. */ + * not finite or not JSON. A bool is true or false, nothing else; an i64 an + * integer's digits, exact in its range. */ #include "message-json.h" #include <math.h> #include <stdint.h> @@ -52,6 +53,24 @@ int main(void) { CHECK(!pure_json_fixed(&truth, (const unsigned char *)not_bools[i], strlen(not_bools[i]), prefix) && prefix[1] == 0); double number; CHECK(!read_number("true", &number) && !read_number("false", &number)); + /* An i64 is an integer's digits, exactly, in its range. */ + static const PureField whole = {.type = PURE_FIELD_I64, .offset = 0}; + static const struct { const char *text; int64_t value; } integers[] = { + {"0", 0}, {"-0", 0}, {"9007199254740993", INT64_C(9007199254740993)}, {"9223372036854775807", INT64_MAX}, + {"-9223372036854775808", INT64_MIN}, {"-1", -1}}; + for (size_t i = 0; i < sizeof(integers) / sizeof(*integers); ++i) { + unsigned char bytes[8]; + int64_t value; + CHECK(pure_json_fixed(&whole, (const unsigned char *)integers[i].text, strlen(integers[i].text), bytes)); + memcpy(&value, bytes, 8); + CHECK(value == integers[i].value); + } + static const char *const not_integers[] = {"9223372036854775808", "-9223372036854775809", "1.0", "1e3", "+1", "01", "\"1\"", + "", "-", "1.5", "true", "99999999999999999999"}; + for (size_t i = 0; i < sizeof(not_integers) / sizeof(*not_integers); ++i) { + unsigned char bytes[8]; + CHECK(!pure_json_fixed(&whole, (const unsigned char *)not_integers[i], strlen(not_integers[i]), bytes)); + } uint64_t seed = 0x6a736f6eu; size_t read = 0; for (unsigned i = 0; i < 400000; ++i) { |
