/* * wire.cpp — Antheos Protocol v9 Wire Encoding * * Control characters, word types, radix/unit flags, and wire-format * encoding/decoding per Antheos Protocol 1.0 Level 1, v9 specification. * * Copyright (c) 2025-2026 Are Bjørby * SPDX-License-Identifier: MIT */ #include "antheos.hpp" #include namespace antheos::wire { /* ── Validation ── */ bool is_reserved(uint8_t byte) { switch (byte) { case SOM: case EOM: case SOR: case SOU: case EOW: case SOW: case SOB: return true; default: return false; } } bool is_valid_word_type(uint8_t byte) { switch (byte) { case static_cast(WordType::Symbol): case static_cast(WordType::Text): case static_cast(WordType::Integer): case static_cast(WordType::Real): case static_cast(WordType::Scientific): case static_cast(WordType::Timestamp): case static_cast(WordType::Blob): case static_cast(WordType::Duration): case static_cast(WordType::Path): case static_cast(WordType::Logical): case static_cast(WordType::Id): case static_cast(WordType::Message): return true; default: return false; } } bool is_radix_flag(uint8_t byte) { switch (byte) { case static_cast(Radix::Binary): case static_cast(Radix::Octal): case static_cast(Radix::Decimal): case static_cast(Radix::Hex): case static_cast(Radix::Base32): return true; default: return false; } } bool is_unit_flag(uint8_t byte) { switch (byte) { case static_cast(Unit::Byte): case static_cast(Unit::Word): case static_cast(Unit::Dword): case static_cast(Unit::Qword): case static_cast(Unit::Megabyte): case static_cast(Unit::Gigabyte): case static_cast(Unit::Terabyte): return true; default: return false; } } /* ── Flag requirement rules (v9 §6.1) ── */ bool needs_radix(WordType type) { switch (type) { case WordType::Integer: case WordType::Real: case WordType::Scientific: case WordType::Blob: return true; default: return false; } } bool needs_unit(WordType type) { switch (type) { case WordType::Integer: case WordType::Real: case WordType::Scientific: case WordType::Blob: return true; default: return false; } } bool allows_radix(WordType type) { switch (type) { case WordType::Integer: case WordType::Real: case WordType::Scientific: case WordType::Blob: case WordType::Id: return true; default: return false; } } /* ANTHEOS-WIRE-FLAG-RULES-ASYMMETRIC — §6.1 in ONE place, for every site that * has to honour it. * * The rules were enforced only while ENCODING, and only their forbidden half: * each check sat inside `if (flag present)`, so a word type that REQUIRES radix * and unit encoded happily with neither. `needs_radix` had no caller at all — * the dead witness to the missing half. Meanwhile both readers checked that a * flag byte was a valid flag and never that the type was allowed to carry it, * so a TEXT word with a radix flag was refused by this library's encoder and * accepted by both of its readers. * * Three sites had to agree and did not, which is why this is a predicate rather * than three copies of an `if`: word_encode, word_decode, and the parser's * word-head state machine now ask the same question, and a future word type * changes its answer once. * * Both halves, per §6.1: INTEGER / REAL / SCIENTIFIC / BLOB require radix AND * unit; ID may carry a radix and must not carry a unit; every other type may * carry neither. */ bool flags_conform(WordType type, Radix radix, Unit unit) { const bool has_radix = radix != Radix::None; const bool has_unit = unit != Unit::None; if (has_radix && !allows_radix(type)) return false; /* carries a forbidden radix */ if (has_unit && !needs_unit(type)) return false; /* carries a forbidden unit */ if (!has_radix && needs_radix(type)) return false; /* lacks a required radix */ if (!has_unit && needs_unit(type)) return false; /* lacks a required unit */ return true; } /* ── Word encoding — v9 wire format ── * * [SOW][WT]([SOR][RF])([SOU][UF])[SOB]Body[EOW] */ std::optional> word_encode( WordType type, Radix radix, Unit unit, const uint8_t* body, size_t body_len) { if (!is_valid_word_type(static_cast(type))) return std::nullopt; uint8_t rf = static_cast(radix); uint8_t uf = static_cast(unit); if (rf != 0 && !is_radix_flag(rf)) return std::nullopt; if (uf != 0 && !is_unit_flag(uf)) return std::nullopt; /* ANTHEOS-WIRE-FLAG-RULES-ASYMMETRIC — the §6.1 rules, both halves, from * the one predicate the readers also use. The checks this replaces sat * inside `if (flag present)`, so the REQUIRED half was unenforced: an * INTEGER with neither flag encoded happily. */ if (!flags_conform(type, radix, unit)) return std::nullopt; for (size_t i = 0; i < body_len; i++) { if (is_reserved(body[i])) return std::nullopt; } /* SOW + WT + SOB + EOW + body + optional SOR/RF + optional SOU/UF */ size_t cap = 4 + body_len; if (rf != 0) cap += 2; if (uf != 0) cap += 2; std::vector out; out.reserve(cap); out.push_back(SOW); out.push_back(static_cast(type)); if (rf != 0) { out.push_back(SOR); out.push_back(rf); } if (uf != 0) { out.push_back(SOU); out.push_back(uf); } out.push_back(SOB); if (body_len > 0 && body != nullptr) out.insert(out.end(), body, body + body_len); out.push_back(EOW); return out; } /* ── Word decoding — v9 wire format ── */ std::optional word_decode(const uint8_t* in, size_t in_len) { if (in == nullptr || in_len < 4) return std::nullopt; size_t pos = 0; if (in[pos++] != SOW) return std::nullopt; uint8_t wt = in[pos++]; if (!is_valid_word_type(wt)) return std::nullopt; DecodedWord dw; dw.type = static_cast(wt); dw.radix = Radix::None; dw.unit = Unit::None; if (pos < in_len && in[pos] == SOR) { pos++; if (pos >= in_len) return std::nullopt; if (!is_radix_flag(in[pos])) return std::nullopt; dw.radix = static_cast(in[pos++]); } if (pos < in_len && in[pos] == SOU) { pos++; if (pos >= in_len) return std::nullopt; if (!is_unit_flag(in[pos])) return std::nullopt; dw.unit = static_cast(in[pos++]); } /* ANTHEOS-WIRE-FLAG-RULES-ASYMMETRIC — the flag BYTES were validated * above; this is the half that was missing, that the word TYPE is allowed * to carry what it carries and carries what it must. Without it a TEXT * word with a radix flag decoded cleanly here while this library's own * encoder refused to produce one. */ if (!flags_conform(dw.type, dw.radix, dw.unit)) return std::nullopt; if (pos >= in_len || in[pos] != SOB) return std::nullopt; pos++; size_t body_start = pos; while (pos < in_len && in[pos] != EOW) { if (is_reserved(in[pos])) return std::nullopt; pos++; } if (pos >= in_len) return std::nullopt; dw.body.assign(in + body_start, in + pos); return dw; } /* ── Convenience encoders ── */ std::optional> encode_symbol(char verb) { uint8_t body = static_cast(verb); return word_encode(WordType::Symbol, Radix::None, Unit::None, &body, 1); } std::optional> encode_text(std::string_view text) { return word_encode(WordType::Text, Radix::None, Unit::None, reinterpret_cast(text.data()), text.size()); } std::optional> encode_id_plain(std::string_view id) { return word_encode(WordType::Id, Radix::None, Unit::None, reinterpret_cast(id.data()), id.size()); } std::optional> encode_id_base32(std::string_view id) { return word_encode(WordType::Id, Radix::Base32, Unit::None, reinterpret_cast(id.data()), id.size()); } std::optional> encode_id_decimal(uint32_t value) { auto s = std::to_string(value); return word_encode(WordType::Id, Radix::Decimal, Unit::None, reinterpret_cast(s.data()), s.size()); } std::optional> encode_integer( Radix radix, Unit unit, std::string_view value) { return word_encode(WordType::Integer, radix, unit, reinterpret_cast(value.data()), value.size()); } std::optional> encode_logical(std::string_view expr) { return word_encode(WordType::Logical, Radix::None, Unit::None, reinterpret_cast(expr.data()), expr.size()); } std::optional> encode_path(std::string_view path) { return word_encode(WordType::Path, Radix::None, Unit::None, reinterpret_cast(path.data()), path.size()); } std::optional> encode_message(std::string_view ref) { if (ref.empty()) return std::nullopt; return word_encode(WordType::Message, Radix::None, Unit::None, reinterpret_cast(ref.data()), ref.size()); } } // namespace antheos::wire