aboutsummaryrefslogtreecommitdiffstats
path: root/serve
diff options
context:
space:
mode:
Diffstat (limited to 'serve')
-rw-r--r--serve/deliver.c11
-rw-r--r--serve/message-json.c42
-rw-r--r--serve/serve.c27
-rw-r--r--serve/test-message-json.c21
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) {