Payload bound and chunking -- FABRIC-3.6.md task 3.5
sk_hermes_publish() now enforces SK_HERMES_CHUNK_MAX_PAYLOAD (1024) on every message's payload_len uniformly, chunked or not -- closing the gap task 3.3 explicitly parked. A chunk carrier is [SkHermesChunkHeader][content slice], slice capped at 1024 - sizeof(header) rather than 1024 itself, so every message on the wire satisfies the same one-block bound vm_interpret()'s own drain limit already requires -- a future chunk-aware drain never has to special-case a carrier that can't be handed to vm_interpret() as-is. Deliberately no chunking-sender API: building one would need kernel-Hermes to own chunk-buffer memory with a real lifetime it has no way to track (kept alive until every subscriber drains it). Sending is a loop pattern a caller writes with sk_hermes_chunk_count() + sk_hermes_publish(), demonstrated by this task's own self-test. sk_hermes_reassemble() is pure and memory-agnostic: validates msg_id agreement, exact seq coverage, and per-chunk slice sizes before a single memcpy, with the total length computed once and checked against the caller's buffer once -- never order-dependent on which chunk happens to overflow. Verified live on all three architectures: a 1024-byte payload as one message, a 1025-byte send refused outright with the ledger untouched, and a 3000-byte payload split into 3 chunks, drained, and reassembled byte-exact against the original. dict_hash unmoved and identical across architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2b1ba031a5
commit
8a2ee0fdad
@@ -488,6 +488,11 @@ int sk_hermes_publish(VMUuid from, int channel_id, uint32_t type,
|
||||
int delivered = 0;
|
||||
|
||||
if (!channel_valid(channel_id)) return -1;
|
||||
/* Task 3.5: the one-block bound, enforced uniformly here whether or
|
||||
* not payload_addr happens to be a chunk carrier -- see
|
||||
* kernel_hermes.h's own sizing note on why SK_HERMES_CHUNK_MAX_SLICE
|
||||
* (not SK_HERMES_CHUNK_MAX_PAYLOAD) is a chunk's own content size. */
|
||||
if (payload_len > SK_HERMES_CHUNK_MAX_PAYLOAD) return -1;
|
||||
|
||||
m = &sk_hermes_channels[channel_id].membership;
|
||||
for (i = 0; i < m->count; i++) {
|
||||
@@ -587,4 +592,71 @@ int sk_hermes_drain_checkpoint(VM *vm) {
|
||||
return drain_error ? -1 : 1;
|
||||
}
|
||||
|
||||
uint32_t sk_hermes_chunk_count(uint32_t len) {
|
||||
if (len == 0) return 0;
|
||||
return (len + SK_HERMES_CHUNK_MAX_SLICE - 1) / SK_HERMES_CHUNK_MAX_SLICE;
|
||||
}
|
||||
|
||||
int sk_hermes_reassemble(SkHermesMessage **chunks, int n_chunks,
|
||||
uint8_t *out_buf, uint32_t out_buf_cap,
|
||||
uint32_t *out_len) {
|
||||
int present[SK_HERMES_MSG_MAX];
|
||||
int i;
|
||||
uint32_t msg_id;
|
||||
uint32_t total;
|
||||
uint32_t last_slice_len = 0;
|
||||
|
||||
if (!chunks || n_chunks <= 0 || !out_buf || !out_len) return -1;
|
||||
if (n_chunks > SK_HERMES_MSG_MAX) return -1; /* guard before indexing present[] */
|
||||
|
||||
for (i = 0; i < n_chunks; i++) present[i] = 0;
|
||||
|
||||
if (!chunks[0] || !chunks[0]->payload_addr ||
|
||||
chunks[0]->payload_len < (uint32_t)sizeof(SkHermesChunkHeader)) return -1;
|
||||
msg_id = ((SkHermesChunkHeader *)chunks[0]->payload_addr)->msg_id;
|
||||
|
||||
for (i = 0; i < n_chunks; i++) {
|
||||
SkHermesMessage *m = chunks[i];
|
||||
SkHermesChunkHeader *h;
|
||||
uint32_t slice_len;
|
||||
|
||||
if (!m || !m->payload_addr || m->payload_len < (uint32_t)sizeof(SkHermesChunkHeader)) return -1;
|
||||
h = (SkHermesChunkHeader *)m->payload_addr;
|
||||
slice_len = m->payload_len - (uint32_t)sizeof(SkHermesChunkHeader);
|
||||
|
||||
if (h->msg_id != msg_id) return -1;
|
||||
if (h->seq >= (uint32_t)n_chunks) return -1;
|
||||
if (present[h->seq]) return -1; /* duplicate seq */
|
||||
present[h->seq] = 1;
|
||||
|
||||
if (h->seq == (uint32_t)(n_chunks - 1)) {
|
||||
if (!h->is_last) return -1;
|
||||
if (slice_len == 0 || slice_len > SK_HERMES_CHUNK_MAX_SLICE) return -1;
|
||||
last_slice_len = slice_len;
|
||||
} else {
|
||||
if (h->is_last) return -1;
|
||||
if (slice_len != SK_HERMES_CHUNK_MAX_SLICE) return -1;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < n_chunks; i++) if (!present[i]) return -1; /* gap */
|
||||
|
||||
/* Total computed once from validated seq completeness, checked once
|
||||
* against out_buf_cap, before any memcpy -- never order-dependent on
|
||||
* which chunk happens to overflow first. */
|
||||
total = (uint32_t)(n_chunks - 1) * SK_HERMES_CHUNK_MAX_SLICE + last_slice_len;
|
||||
if (total > out_buf_cap) return -1;
|
||||
|
||||
for (i = 0; i < n_chunks; i++) {
|
||||
SkHermesMessage *m = chunks[i];
|
||||
SkHermesChunkHeader *h = (SkHermesChunkHeader *)m->payload_addr;
|
||||
uint32_t slice_len = m->payload_len - (uint32_t)sizeof(SkHermesChunkHeader);
|
||||
uint32_t offset = h->seq * SK_HERMES_CHUNK_MAX_SLICE;
|
||||
|
||||
memcpy(out_buf + offset, (uint8_t *)m->payload_addr + sizeof(SkHermesChunkHeader), slice_len);
|
||||
}
|
||||
|
||||
*out_len = total;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
Reference in New Issue
Block a user