#include "semq_internal.h"
#include "semq_sha256.h"
semq_status_t semqi_encoding_finish(semq_encoding_t* enc, semq_error_t* err) {
memcpy(enc->header, "SEMQ", 4u);
semqi_wr16(enc->header + 4, (uint16_t)SEMQ_FILE_VERSION);
semq_config_to_bytes(&enc->config, enc->header + 6);
enc->header[19] = (uint8_t)enc->id_kind;
semqi_wr64(enc->header + 20, enc->n);
if (enc->pairs == NULL && enc->n_pairs == 0u) {
const semq_status_t s = semqi_manifest_index(enc->manifest, enc->manifest_len,
&enc->n_pairs, &enc->pairs, err);
if (s != SEMQ_OK) return s;
}
semq_sha256_ctx_t h;
semq_sha256_init(&h);
semq_sha256_update(&h, enc->header + SEMQI_S_OFFSET, SEMQI_HEADER_BYTES - SEMQI_S_OFFSET);
semq_sha256_update(&h, enc->ids, (size_t)enc->ids_len);
semq_sha256_update(&h, enc->rows, (size_t)enc->rows_len);
semq_sha256_final(&h, enc->footer);
semq_sha256_init(&h);
semq_sha256_update(&h, enc->footer, 32u);
semq_sha256_update(&h, enc->manifest, (size_t)enc->manifest_len);
semq_sha256_final(&h, enc->footer + 32);
return SEMQ_OK;
}
SEMQ_API uint64_t semq_encoding_file_size(const semq_encoding_t* enc) {
if (enc == NULL) return 0u;
return SEMQI_HEADER_BYTES + enc->ids_len + enc->rows_len + enc->manifest_len + SEMQI_FOOTER_BYTES;
}
SEMQ_API uint32_t semq_encoding_save_parts(const semq_encoding_t* enc, semq_part_t parts[5]) {
parts[0].ptr = enc->header; parts[0].len = SEMQI_HEADER_BYTES;
parts[1].ptr = enc->ids; parts[1].len = enc->ids_len;
parts[2].ptr = enc->rows; parts[2].len = enc->rows_len;
parts[3].ptr = enc->manifest; parts[3].len = enc->manifest_len;
parts[4].ptr = enc->footer; parts[4].len = SEMQI_FOOTER_BYTES;
return 5u;
}
SEMQ_API semq_status_t semq_encoding_save(const semq_encoding_t* enc, uint8_t* out, uint64_t cap,
semq_error_t* err) {
if (enc == NULL || out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "NULL argument");
if (cap < semq_encoding_file_size(enc)) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output buffer is too small");
semq_part_t parts[5];
semq_encoding_save_parts(enc, parts);
uint64_t pos = 0u;
for (uint32_t i = 0u; i < 5u; i++) {
if (parts[i].len > 0u) memcpy(out + pos, parts[i].ptr, (size_t)parts[i].len);
pos += parts[i].len;
}
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_encoding_load(const uint8_t* buf, uint64_t len, semq_encoding_t** out,
semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
const uint64_t min_len = SEMQI_HEADER_BYTES + 4u + SEMQI_FOOTER_BYTES;
if (len < min_len) return SEMQI_FORMAT(err, SEMQ_SECTION_FRAMING, "buffer is shorter than the smallest image");
if (buf == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "buffer is NULL");
if (memcmp(buf, "SEMQ", 4u) != 0) return SEMQI_FORMAT(err, SEMQ_SECTION_FRAMING, "magic bytes are not SEMQ");
const uint16_t version = semqi_rd16(buf + 4);
if (version == 1u) return SEMQI_FORMAT(err, SEMQ_SECTION_FRAMING, "unsupported version 1");
if (version != SEMQ_FILE_VERSION) return SEMQI_FORMAT(err, SEMQ_SECTION_FRAMING, "unsupported file version");
semq_config_t cfg;
semq_status_t s = semqi_config_from_file(buf + 6, &cfg, err);
if (s != SEMQ_OK) return s;
const uint32_t bpv = semq_config_bytes_per_vector(&cfg);
const uint8_t kind = buf[19];
const uint64_t n = semqi_rd64(buf + 20);
if (kind != SEMQ_ID_U64 && kind != SEMQ_ID_UTF8) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "unknown id kind");
const uint64_t body = len - SEMQI_HEADER_BYTES - SEMQI_FOOTER_BYTES;
uint64_t ids_len, rows_len;
if (semqi_mul_ovf(n, bpv, &rows_len)) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "row section overflows");
if (kind == SEMQ_ID_U64) {
if (semqi_mul_ovf(n, 8u, &ids_len)) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "id section overflows");
} else {
uint64_t off_bytes;
if (semqi_mul_ovf(n + 1u, 8u, &off_bytes) || n == UINT64_MAX) {
return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "id section overflows");
}
if (off_bytes > body) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "id offsets exceed the buffer");
const uint64_t total = semqi_rd64(buf + SEMQI_HEADER_BYTES + n * 8u);
if (semqi_add_ovf(off_bytes, total, &ids_len)) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "id section overflows");
}
uint64_t used;
if (semqi_add_ovf(ids_len, rows_len, &used) || used > body) {
return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "sections exceed the buffer");
}
const uint64_t manifest_len = body - used;
if (manifest_len < 4u) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "manifest section is missing");
if (manifest_len > (uint64_t)SEMQ_MANIFEST_SECTION_MAX) return SEMQI_FORMAT(err, SEMQ_SECTION_SIZES, "manifest section exceeds 16 MiB");
if (semqi_too_big(len)) return SEMQI_NOMEM(err);
const uint8_t* ids_p = buf + SEMQI_HEADER_BYTES;
const uint8_t* rows_p = ids_p + ids_len;
const uint8_t* man_p = rows_p + rows_len;
const uint8_t* foot_p = man_p + manifest_len;
if (kind == SEMQ_ID_U64) {
for (uint64_t i = 1u; i < n; i++) {
if (semqi_rd64(ids_p + (i - 1u) * 8u) >= semqi_rd64(ids_p + i * 8u)) {
return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "u64 ids are not strictly increasing");
}
}
} else {
const uint8_t* bytes = ids_p + (n + 1u) * 8u;
const uint64_t total = ids_len - (n + 1u) * 8u;
if (semqi_rd64(ids_p) != 0u) return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 offsets must start at 0");
const uint8_t* prev = NULL;
uint64_t prev_len = 0u;
for (uint64_t i = 0u; i < n; i++) {
const uint64_t a = semqi_rd64(ids_p + i * 8u), b = semqi_rd64(ids_p + (i + 1u) * 8u);
if (b <= a || b > total) return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 offsets are not strictly increasing");
const uint64_t l = b - a;
if (l > SEMQ_ID_MAX_BYTES) return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 id exceeds 4096 bytes");
if (!semqi_utf8_valid(bytes + a, l)) return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 id is not valid UTF-8");
if (prev != NULL && semqi_cmp_bytes(prev, prev_len, bytes + a, l) >= 0) {
return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 ids are not strictly increasing");
}
prev = bytes + a;
prev_len = l;
}
if (n == 0u && total != 0u) return SEMQI_FORMAT(err, SEMQ_SECTION_IDS, "utf8 bytes without ids");
}
uint32_t n_pairs = 0u;
semqi_pair_index_t* pairs = NULL;
s = semqi_manifest_index(man_p, manifest_len, &n_pairs, &pairs, err);
if (s != SEMQ_OK) return s;
uint8_t digest[64];
{
semq_sha256_ctx_t h;
semq_sha256_init(&h);
semq_sha256_update(&h, buf + SEMQI_S_OFFSET, SEMQI_HEADER_BYTES - SEMQI_S_OFFSET);
semq_sha256_update(&h, ids_p, (size_t)ids_len);
semq_sha256_update(&h, rows_p, (size_t)rows_len);
semq_sha256_final(&h, digest);
semq_sha256_init(&h);
semq_sha256_update(&h, digest, 32u);
semq_sha256_update(&h, man_p, (size_t)manifest_len);
semq_sha256_final(&h, digest + 32);
}
if (memcmp(digest, foot_p, 32u) != 0) {
semqi_free(pairs);
return semqi_fail_integrity(err, SEMQ_WHICH_CONTENT, "content digest does not match the footer");
}
if (memcmp(digest + 32, foot_p + 32, 32u) != 0) {
semqi_free(pairs);
return semqi_fail_integrity(err, SEMQ_WHICH_STATE, "state id does not match the footer");
}
for (uint64_t i = 0u; i < n; i++) {
uint64_t unit;
if (!semqi_row_canonical(&cfg, rows_p + (size_t)i * bpv, &unit)) {
semqi_free(pairs);
return semqi_fail(err, SEMQ_ERR_FORMAT, i, (uint64_t)SEMQ_SECTION_ROWS, "row is not canonical");
}
}
semq_encoding_t* enc = semqi_encoding_alloc();
if (enc == NULL) { semqi_free(pairs); return SEMQI_NOMEM(err); }
enc->config = cfg;
enc->bpv = bpv;
enc->units = semq_config_units_per_row(&cfg);
enc->id_kind = kind;
enc->n = n;
enc->ids = (uint8_t*)semqi_alloc(ids_len);
enc->rows = (uint8_t*)semqi_alloc(rows_len);
enc->manifest = (uint8_t*)semqi_alloc(manifest_len);
if (enc->ids == NULL || enc->rows == NULL || enc->manifest == NULL) {
semqi_free(pairs);
semq_encoding_free(enc);
return SEMQI_NOMEM(err);
}
memcpy(enc->ids, ids_p, (size_t)ids_len); enc->ids_len = ids_len;
memcpy(enc->rows, rows_p, (size_t)rows_len); enc->rows_len = rows_len;
memcpy(enc->manifest, man_p, (size_t)manifest_len); enc->manifest_len = manifest_len;
enc->n_pairs = n_pairs;
enc->pairs = pairs;
memcpy(enc->header, buf, SEMQI_HEADER_BYTES);
memcpy(enc->footer, foot_p, SEMQI_FOOTER_BYTES);
*out = enc;
semqi_ok(err);
return SEMQ_OK;
}