aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/module.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 07:30:38 +0100
committereunhhu <[email protected]>2026-09-27 07:30:38 +0100
commit194d243165c2b78d64e0263f39f7a63dbe4a1310 (patch)
treef05e5cf09eb43ddd44e5ef721675920f64519fe1 /prototypes/connections/module.c
parent2e7ad9f8488d4f02c688ec24d6d0b571b3fa61ec (diff)
downloadpure-194d243165c2b78d64e0263f39f7a63dbe4a1310.tar.gz
pure-194d243165c2b78d64e0263f39f7a63dbe4a1310.zip
Name message fields by paths and take variable fields where they are
A message field's name may now be a path of identifiers joined by dots (user.name, a field inside the group user); a module that names both a field and a group (user and user.name) is refused, as are empty, leading, trailing or digit-first segments. A module may have 64 message fields instead of 32 (PureModule 11.9 to 13.2 KiB). pure_app_message_fields takes each variable field's start and size in the payload, so a host that parsed a request in place passes the fields' bytes where they are instead of laying them out after the prefix. The runtime checks them as before: a field past the payload fails with limit, a text that is not UTF-8 with encoding.
Diffstat (limited to 'prototypes/connections/module.c')
-rw-r--r--prototypes/connections/module.c66
1 files changed, 46 insertions, 20 deletions
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index c1a71d5..b1f58fe 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -138,16 +138,21 @@ static bool document_key(const char *key) {
return true;
}
-/* Strings are [u8 length][bytes]; a name is an ASCII identifier of 1..31 bytes. */
+/* Strings are [u8 length][bytes]; a name is 1..31 bytes: an ASCII identifier,
+ * a path of identifiers joined by dots (a message field inside a group, as
+ * user.name), or any printable ASCII (a key's name). */
+enum { NAME_IDENTIFIER, NAME_PATH, NAME_PRINTABLE };
PURE_COLD static bool name_at(const unsigned char *strings, uint32_t size, uint32_t offset,
- bool identifier, char out[PURE_MODULE_NAME]) {
+ int kind, char out[PURE_MODULE_NAME]) {
if (offset >= size) return false;
uint32_t length = strings[offset];
if (!length || length >= PURE_MODULE_NAME || length > size - offset - 1) return false;
- for (uint32_t i = 0; i < length; ++i) {
+ for (uint32_t i = 0, start = 0; i < length; ++i) {
unsigned char c = strings[offset + 1 + i];
bool letter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
- if (!letter && !(i && c >= '0' && c <= '9') && (identifier || c < 32 || c > 126)) return false;
+ if (kind == NAME_PATH && c == '.' && i > start && i + 1 < length) { start = i + 1; continue; }
+ if (!letter && !(i > start && c >= '0' && c <= '9') && (kind != NAME_PRINTABLE || c < 32 || c > 126))
+ return false;
}
memcpy(out, strings + offset + 1, length);
out[length] = 0;
@@ -461,7 +466,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (constant_bytes) memcpy(out->constant_data, p + size - constant_bytes, constant_bytes);
for (uint32_t i = 0; i < out->states; ++i, at += STATE_BYTES) {
out->state_type[i] = (uint8_t)read32(at + 4);
- if (!name_at(text, strings, read32(at), true, out->state_names[i])) REFUSE("state name", i);
+ if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->state_names[i])) REFUSE("state name", i);
if (read32(at + 4) > PURE_STATE_INDEX) REFUSE("state type", i);
if (out->state_type[i] == PURE_STATE_NUM) {
out->initial[i] = read_double(at + 8);
@@ -478,7 +483,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
}
for (uint32_t i = 0; i < out->collections; ++i, at += COLLECTION_BYTES) {
- if (!name_at(text, strings, read32(at), true, out->collection_names[i])) REFUSE("collection name", i);
+ if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->collection_names[i])) REFUSE("collection name", i);
if (read32(at + 4) != 1 || read32(at + 8) != 1) REFUSE("collection must hold bytes by key", i);
}
uint32_t next_field = 0;
@@ -489,11 +494,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
shape->keyed = at[5];
shape->field_start = (uint16_t)read16(at + 12);
shape->field_count = (uint16_t)read16(at + 14);
- if (!name_at(text, strings, read32(at), true, out->input_names[i])) REFUSE("input name", i);
+ if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->input_names[i])) REFUSE("input name", i);
if (read16(at + 6) || at[5] > 1) REFUSE("input flags", i);
if (shape->kind == PURE_KIND_KEY) {
if (shape->keyed || shape->field_start || shape->field_count ||
- !name_at(text, strings, read32(at + 8), false, out->input_keys[i]) ||
+ !name_at(text, strings, read32(at + 8), NAME_PRINTABLE, out->input_keys[i]) ||
!key_name(out->input_keys[i])) REFUSE("input key", i);
for (uint32_t k = 0; k < i; ++k)
if (out->input[k].kind == PURE_KIND_KEY && !strcmp(out->input_keys[k], out->input_keys[i]))
@@ -524,11 +529,17 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
const unsigned char *entry = fields + (size_t)(next_field + f) * FIELD_BYTES;
PureField *field = &out->field[next_field + f];
field->type = (uint8_t)read32(entry + 4);
- if (!name_at(text, strings, read32(entry), true, field->name) || field->type < PURE_FIELD_U32 ||
+ if (!name_at(text, strings, read32(entry), NAME_PATH, field->name) || field->type < PURE_FIELD_U32 ||
read32(entry + 4) > PURE_FIELD_TEXT)
REFUSE("message field", next_field + f);
- for (uint32_t k = 0; k < f; ++k)
- if (!strcmp(out->field[next_field + k].name, field->name)) REFUSE("duplicate field", next_field + f);
+ /* A name is a field or a group, never both (user and user.name). */
+ for (uint32_t k = 0; k < f; ++k) {
+ const char *a = out->field[next_field + k].name, *b = field->name;
+ size_t la = strlen(a), lb = strlen(b);
+ if (!strcmp(a, b)) REFUSE("duplicate field", next_field + f);
+ if ((la < lb && !memcmp(a, b, la) && b[la] == '.') || (lb < la && !memcmp(a, b, lb) && a[lb] == '.'))
+ REFUSE("field and group", next_field + f);
+ }
field->offset = f == last ? PURE_FIELD_REST : shape->prefix;
if (f != last) shape->prefix += field->type == PURE_FIELD_NUMBER ? 8 : 4;
}
@@ -550,14 +561,14 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
PureRecord *record = &out->record[i];
record->first = (uint16_t)read16(at + 4);
record->count = (uint16_t)read16(at + 6);
- if (!name_at(text, strings, read32(at), true, out->record_names[i]) || record->first != next_record_field ||
+ if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->record_names[i]) || record->first != next_record_field ||
!record->count || record->count > PURE_RECORD_FIELDS || record->count > out->record_fields - next_record_field)
REFUSE("record", i);
for (uint32_t f = record->first; f < record->first + record->count; ++f) {
const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES;
uint32_t type = read32(entry + 4);
out->record_field_text[f] = type == 1;
- if (!name_at(text, strings, read32(entry), true, out->record_field_names[f]) || type > 1)
+ if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > 1)
REFUSE("record field", f);
for (uint32_t k = record->first; k < f; ++k)
if (!strcmp(out->record_field_names[k], out->record_field_names[f])) REFUSE("duplicate record field", f);
@@ -578,7 +589,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
REFUSE("state reference", i);
/* A table names its list in place of code; its columns follow it. */
for (uint32_t i = 0; i < out->views; ++i, at += VIEW_BYTES) {
- if (!name_at(text, strings, read32(at), true, out->view_names[i])) REFUSE("view name", i);
+ if (!name_at(text, strings, read32(at), NAME_IDENTIFIER, out->view_names[i])) REFUSE("view name", i);
out->view_type[i] = at[4];
out->view_table[i] = (uint16_t)read16(at + 6);
out->view_code[i] = (PureBlock){read32(at + 8), read32(at + 12)};
@@ -1435,7 +1446,10 @@ static PureResult keep_reply(PureApp *app) {
return PURE_OK;
}
-PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload) {
+/* A message whose variable fields are where placed says (start and size in
+ * the payload), or, without it, one after another after the prefix. */
+static PureResult message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload,
+ const uint32_t *placed) {
pure_app_finish(app);
const PureModule *m = app->module;
app->fault = PURE_FAULT_NONE;
@@ -1455,13 +1469,16 @@ PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64
if (field->type == PURE_FIELD_NUMBER && !isfinite(read_double(payload.data + field->offset)))
return fault(app, PURE_FAULT_NON_FINITE, UINT32_MAX);
if (field->type != PURE_FIELD_BYTES && field->type != PURE_FIELD_TEXT) continue;
- size_t size = field->offset == PURE_FIELD_REST ? payload.size - at : read32(payload.data + field->offset);
- if (size > payload.size - at) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
- if (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){size ? payload.data + at : NULL, size}))
+ size_t start = placed ? placed[2 * f] : at;
+ if (start > payload.size) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
+ size_t size = placed ? placed[2 * f + 1] : field->offset == PURE_FIELD_REST ? payload.size - at :
+ read32(payload.data + field->offset);
+ if (size > payload.size - start) return fault(app, PURE_FAULT_LIMIT, UINT32_MAX);
+ if (field->type == PURE_FIELD_TEXT && !pure_utf8_valid((PureSpan){size ? payload.data + start : NULL, size}))
return fault(app, PURE_FAULT_ENCODING, UINT32_MAX);
- spans[f][0] = (uint32_t)at;
+ spans[f][0] = (uint32_t)start;
spans[f][1] = (uint32_t)size;
- at += size;
+ at = start + size;
}
if (shape->keyed && m->collections) {
if (!app->data.open) return PURE_INVALID;
@@ -1491,6 +1508,15 @@ PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64
return result;
}
+PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload) {
+ return message(app, input, key, base, payload, NULL);
+}
+
+PureResult pure_app_message_fields(PureApp *app, uint32_t input, const char *key, int64_t base, PureSpan payload,
+ const uint32_t *placed) {
+ return message(app, input, key, base, payload, placed);
+}
+
PureResult pure_app_reply(const PureApp *app, bool (*visit)(void *, PureSpan), void *context) {
if (!app->has_reply) return PURE_OK;
return visit_value(app, &app->arena, app->reply, visit, context);