/** * thirtytwodl.h — 32DL Wire Format v2.0 * Header-only, zero malloc, C99, embedded-safe * * Wire frame: version(1) | length(2 BE) | expr(N) | crc32(4) * - version: always 0x01 * - length: total frame length including version, length, and crc fields * - crc32: CRC-32 (poly 0xEDB88320) over version + length + expr * - minimum valid frame: 7 bytes (version + length + zero-length expr + crc) * * Expression builder API: * Build compound expressions without manual byte-wrangling. * Temporal operators (duration, timeout) take an explicit inner expression * buffer — cleaner and safer than in-place rewriting. * * Example: * uint8_t inner[4], msg[128]; * twodl_builder_t b; * * // Build inner expression: affirm * twodl_builder_init(&b, inner, sizeof(inner)); * twodl_builder_add_affirm(&b); * * // Build outer expression: timeout(affirm, 500ms) * uint8_t outer[32]; * twodl_builder_t ob; * twodl_builder_init(&ob, outer, sizeof(outer)); * twodl_builder_add_timeout(&ob, inner, b.pos, 500); * * // Wrap in wire frame * size_t msg_len = sizeof(msg); * twodl_encode_message(outer, ob.pos, msg, &msg_len); * * Status: Prototype. Not safety-certified. Not for production use. */ #ifndef THIRTYTWODL_H #define THIRTYTWODL_H #include #include #include #ifdef __cplusplus extern "C" { #endif /* ============================================================ * Constants * ============================================================ */ #define TWODL_VERSION 0x01 #define TWODL_MIN_FRAME_LEN 7 /* version(1)+length(2)+crc(4) */ #define TWODL_HEADER_LEN 3 /* version(1)+length(2) */ #define TWODL_CRC_LEN 4 #define TWODL_MAX_FRAME_LEN 65535u /* uint16 length field limit */ #define TWODL_MAX_EXPR_LEN (TWODL_MAX_FRAME_LEN - TWODL_HEADER_LEN - TWODL_CRC_LEN) #define TWODL_UUID_LEN 16 #define TWODL_GPS_LEN 16 /* lat(4)+lon(4)+alt(4)+reserved(4) */ /* ============================================================ * Tags (from spec v2.0) * ============================================================ */ #define TWODL_TAG_SILENCE 0x00 /* 0 — no-op */ #define TWODL_TAG_AFFIRM 0x01 /* 1 — true */ #define TWODL_TAG_NEGATE 0x02 /* -1 — false */ #define TWODL_TAG_SHARED_GROUND 0x03 /* 1/π ≈ 0.318 — shared probabilistic threshold */ #define TWODL_TAG_HIERARCHY_0 0x04 /* h0 — top-level coordinator */ #define TWODL_TAG_UNKNOWN 0x05 /* i — unknown/pending */ #define TWODL_TAG_TEMPORAL 0x06 /* temporal — marks expression as time-sensitive */ #define TWODL_TAG_MAP 0x07 /* map — function application */ #define TWODL_TAG_COMPOSE 0x08 /* compose — sequential composition */ #define TWODL_TAG_SELECT 0x09 /* select — conditional choice */ #define TWODL_TAG_DURATION 0x0A /* duration(expr, ms) — binds time interval */ #define TWODL_TAG_TIMEOUT 0x0B /* timeout(expr, ms) — aborts if not done in time */ #define TWODL_TAG_REVOKED 0x0C /* revoked(uuid, ts) — identity revocation */ #define TWODL_TAG_UTF8_STRING 0xF0 /* utf8_string — reserved, future use */ #define TWODL_TAG_GPS_COORD 0xF1 /* L: — GPS coordinate identity */ #define TWODL_TAG_UUID 0xF2 /* V: — UUID identity */ #define TWODL_TAG_HMAC 0xFE /* hmac — HMAC-SHA256 authentication */ #define TWODL_TAG_NACK 0xFF /* NACK — error response */ /* ============================================================ * Error codes * ============================================================ */ typedef enum { TWODL_OK = 0, TWODL_ERR_NULL, /* NULL pointer argument */ TWODL_ERR_BUFFER_TOO_SMALL,/* Output buffer too small */ TWODL_ERR_INVALID_VERSION, /* Frame version != 0x01 */ TWODL_ERR_INVALID_CHECKSUM,/* CRC32 mismatch */ TWODL_ERR_UNKNOWN_TAG, /* Unrecognised tag byte in expression */ TWODL_ERR_UNTERMINATED, /* Expression shorter than tag requires */ TWODL_ERR_TRUNCATED, /* Frame shorter than claimed_len or < 7 */ TWODL_ERR_OVERFLOW /* expr_len would exceed uint16 max */ } twodl_error_t; /* ============================================================ * Decode result * ============================================================ */ typedef struct { twodl_error_t error; uint8_t version; uint16_t total_len; /* claimed frame length */ uint16_t expr_len; /* bytes of expression body */ const uint8_t *expr; /* pointer into input buf at expr start */ uint32_t crc_received; uint32_t crc_calculated; } twodl_decode_result_t; /* ============================================================ * Expression builder * ============================================================ */ typedef struct { uint8_t *buf; /* caller-provided buffer */ size_t capacity; /* buffer size in bytes */ size_t pos; /* bytes written so far */ int error; /* non-zero if any operation failed */ } twodl_builder_t; /* ============================================================ * CRC-32 (reflected, poly 0xEDB88320) * ============================================================ */ static uint32_t twodl_crc32(const uint8_t *data, size_t len) { uint32_t crc = 0xFFFFFFFFu; for (size_t i = 0; i < len; i++) { crc ^= data[i]; for (int b = 0; b < 8; b++) { if (crc & 1u) crc = (crc >> 1) ^ 0xEDB88320u; else crc >>= 1; } } return crc ^ 0xFFFFFFFFu; } /* ============================================================ * Frame encode * Writes: version(1) | length(2 BE) | expr(expr_len) | crc32(4) * Returns TWODL_OK and sets *out_len, or an error code. * ============================================================ */ static twodl_error_t twodl_encode_message( const uint8_t *expr, size_t expr_len, uint8_t *out, size_t *out_len) { if (!out || !out_len) return TWODL_ERR_NULL; if (expr_len > 0 && !expr) return TWODL_ERR_NULL; if (expr_len > TWODL_MAX_EXPR_LEN) return TWODL_ERR_OVERFLOW; size_t total = TWODL_HEADER_LEN + expr_len + TWODL_CRC_LEN; if (*out_len < total) return TWODL_ERR_BUFFER_TOO_SMALL; size_t pos = 0; out[pos++] = TWODL_VERSION; uint16_t frame_len = (uint16_t)total; out[pos++] = (uint8_t)(frame_len >> 8); out[pos++] = (uint8_t)(frame_len & 0xFF); if (expr_len > 0) { memcpy(out + pos, expr, expr_len); pos += expr_len; } uint32_t crc = twodl_crc32(out, pos); /* covers version+length+expr */ out[pos++] = (uint8_t)(crc >> 24); out[pos++] = (uint8_t)(crc >> 16); out[pos++] = (uint8_t)(crc >> 8); out[pos++] = (uint8_t)(crc & 0xFF); *out_len = pos; return TWODL_OK; } /* ============================================================ * Frame decode + validation * Populates result struct. On error, result->error is set and * a NACK should be sent. * ============================================================ */ static void twodl_decode( const uint8_t *buf, size_t buf_len, twodl_decode_result_t *result) { if (!result) return; result->error = TWODL_OK; result->version = 0; result->total_len = 0; result->expr_len = 0; result->expr = NULL; result->crc_received = 0; result->crc_calculated = 0; if (!buf) { result->error = TWODL_ERR_NULL; return; } /* Minimum valid frame is 7 bytes */ if (buf_len < TWODL_MIN_FRAME_LEN) { result->error = TWODL_ERR_TRUNCATED; return; } result->version = buf[0]; if (result->version != TWODL_VERSION) { result->error = TWODL_ERR_INVALID_VERSION; return; } uint16_t claimed = (uint16_t)((uint16_t)buf[1] << 8 | buf[2]); /* claimed must be at least 7 (minimum frame) */ if (claimed < TWODL_MIN_FRAME_LEN) { result->error = TWODL_ERR_TRUNCATED; return; } if (buf_len < claimed) { result->error = TWODL_ERR_TRUNCATED; return; } result->total_len = claimed; /* Extract CRC from last 4 bytes of claimed frame */ uint32_t recv_crc = ((uint32_t)buf[claimed - 4] << 24) | ((uint32_t)buf[claimed - 3] << 16) | ((uint32_t)buf[claimed - 2] << 8) | (uint32_t)buf[claimed - 1]; result->crc_received = recv_crc; /* Calculate CRC over version + length + expr */ uint32_t calc_crc = twodl_crc32(buf, claimed - TWODL_CRC_LEN); result->crc_calculated = calc_crc; if (recv_crc != calc_crc) { result->error = TWODL_ERR_INVALID_CHECKSUM; return; } /* Expression occupies bytes [3 .. claimed-5] */ result->expr_len = (uint16_t)(claimed - TWODL_HEADER_LEN - TWODL_CRC_LEN); result->expr = (result->expr_len > 0) ? (buf + TWODL_HEADER_LEN) : NULL; } /* ============================================================ * NACK encoder * A NACK is a framed message containing a single 0xFF byte. * ============================================================ */ static twodl_error_t twodl_encode_nack(uint8_t *buf, size_t *buf_len) { uint8_t payload[1] = { TWODL_TAG_NACK }; return twodl_encode_message(payload, 1, buf, buf_len); } /* ============================================================ * Identity encoders * ============================================================ */ /** * Encode a UUID identity into an expression fragment. * Writes 17 bytes: tag(0xF2) + uuid(16). * buf must be at least 17 bytes. */ static twodl_error_t twodl_encode_uuid_identity( const uint8_t uuid[TWODL_UUID_LEN], uint8_t *buf, size_t buf_len) { if (!uuid || !buf) return TWODL_ERR_NULL; if (buf_len < 1 + TWODL_UUID_LEN) return TWODL_ERR_BUFFER_TOO_SMALL; buf[0] = TWODL_TAG_UUID; memcpy(buf + 1, uuid, TWODL_UUID_LEN); return TWODL_OK; } /** * Encode a GPS coordinate identity into an expression fragment. * Writes 17 bytes: tag(0xF1) + lat(4BE) + lon(4BE) + alt(4BE) + reserved(4, zeroed). * Coordinates are WGS84, decimal degrees × 1e7, stored as big-endian int32. * buf must be at least 17 bytes. */ static twodl_error_t twodl_encode_gps_identity( int32_t lat, int32_t lon, int32_t alt, uint8_t *buf, size_t buf_len) { if (!buf) return TWODL_ERR_NULL; if (buf_len < 1 + TWODL_GPS_LEN) return TWODL_ERR_BUFFER_TOO_SMALL; buf[0] = TWODL_TAG_GPS_COORD; /* lat big-endian */ buf[1] = (uint8_t)((uint32_t)lat >> 24); buf[2] = (uint8_t)((uint32_t)lat >> 16); buf[3] = (uint8_t)((uint32_t)lat >> 8); buf[4] = (uint8_t)((uint32_t)lat & 0xFF); /* lon big-endian */ buf[5] = (uint8_t)((uint32_t)lon >> 24); buf[6] = (uint8_t)((uint32_t)lon >> 16); buf[7] = (uint8_t)((uint32_t)lon >> 8); buf[8] = (uint8_t)((uint32_t)lon & 0xFF); /* alt big-endian */ buf[9] = (uint8_t)((uint32_t)alt >> 24); buf[10] = (uint8_t)((uint32_t)alt >> 16); buf[11] = (uint8_t)((uint32_t)alt >> 8); buf[12] = (uint8_t)((uint32_t)alt & 0xFF); /* reserved */ buf[13] = buf[14] = buf[15] = buf[16] = 0x00; return TWODL_OK; } /* ============================================================ * Expression Builder API * * Build compound expressions safely without manual byte-wrangling. * * Pattern: * uint8_t buf[64]; * twodl_builder_t b; * twodl_builder_init(&b, buf, sizeof(buf)); * twodl_builder_add_shared_ground(&b); * twodl_builder_add_affirm(&b); * twodl_builder_add_compose(&b); * // b.pos bytes in b.buf is your expression — pass to twodl_encode_message * * For temporal operators, build the inner expression first, then pass it: * uint8_t inner[4]; * twodl_builder_t ib; * twodl_builder_init(&ib, inner, sizeof(inner)); * twodl_builder_add_affirm(&ib); * * uint8_t outer[32]; * twodl_builder_t ob; * twodl_builder_init(&ob, outer, sizeof(outer)); * twodl_builder_add_timeout(&ob, ib.buf, ib.pos, 500); // timeout(affirm, 500ms) * * Check for errors before using the result: * if (twodl_builder_ok(&b)) { ... } * ============================================================ */ /** Initialise a builder with a caller-supplied buffer. */ static void twodl_builder_init(twodl_builder_t *b, uint8_t *buf, size_t capacity) { if (!b) return; b->buf = buf; b->capacity = capacity; b->pos = 0; b->error = TWODL_OK; } /** Returns non-zero if no error has occurred. */ static int twodl_builder_ok(const twodl_builder_t *b) { return b && b->error == TWODL_OK; } /* Internal: append one byte */ static void twodl__push(twodl_builder_t *b, uint8_t byte) { if (!b || b->error) return; if (b->pos >= b->capacity) { b->error = TWODL_ERR_BUFFER_TOO_SMALL; return; } b->buf[b->pos++] = byte; } /* Internal: append N bytes */ static void twodl__push_bytes(twodl_builder_t *b, const uint8_t *data, size_t len) { if (!b || b->error || !data) return; if (b->pos + len > b->capacity) { b->error = TWODL_ERR_BUFFER_TOO_SMALL; return; } memcpy(b->buf + b->pos, data, len); b->pos += len; } /* Primitive atoms */ static void twodl_builder_add_silence(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_SILENCE); } static void twodl_builder_add_affirm(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_AFFIRM); } static void twodl_builder_add_negate(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_NEGATE); } static void twodl_builder_add_shared_ground(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_SHARED_GROUND); } static void twodl_builder_add_hierarchy_0(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_HIERARCHY_0); } static void twodl_builder_add_unknown(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_UNKNOWN); } static void twodl_builder_add_temporal(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_TEMPORAL); } static void twodl_builder_add_map(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_MAP); } static void twodl_builder_add_compose(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_COMPOSE); } static void twodl_builder_add_select(twodl_builder_t *b) { twodl__push(b, TWODL_TAG_SELECT); } /** * Add a UUID identity atom. * uuid must point to exactly 16 bytes. */ static void twodl_builder_add_uuid(twodl_builder_t *b, const uint8_t uuid[TWODL_UUID_LEN]) { if (!b || b->error || !uuid) { if (b) b->error = TWODL_ERR_NULL; return; } twodl__push(b, TWODL_TAG_UUID); twodl__push_bytes(b, uuid, TWODL_UUID_LEN); } /** * Add a GPS coordinate identity atom. * lat/lon/alt: WGS84, decimal degrees × 1e7, big-endian int32. */ static void twodl_builder_add_gps(twodl_builder_t *b, int32_t lat, int32_t lon, int32_t alt) { if (!b || b->error) return; uint8_t tmp[1 + TWODL_GPS_LEN]; size_t tmp_len = sizeof(tmp); if (twodl_encode_gps_identity(lat, lon, alt, tmp, tmp_len) != TWODL_OK) { b->error = TWODL_ERR_BUFFER_TOO_SMALL; return; } twodl__push_bytes(b, tmp, sizeof(tmp)); } /** * Add a duration operator: 0x0A * inner_buf: the pre-built inner expression bytes * inner_len: number of bytes in inner_buf * ms: duration in milliseconds (big-endian 16-bit) */ static void twodl_builder_add_duration(twodl_builder_t *b, const uint8_t *inner_buf, size_t inner_len, uint16_t ms) { if (!b || b->error || !inner_buf) { if (b) b->error = TWODL_ERR_NULL; return; } twodl__push(b, TWODL_TAG_DURATION); twodl__push_bytes(b, inner_buf, inner_len); twodl__push(b, (uint8_t)(ms >> 8)); twodl__push(b, (uint8_t)(ms & 0xFF)); } /** * Add a timeout operator: 0x0B * inner_buf: the pre-built inner expression bytes * inner_len: number of bytes in inner_buf * ms: timeout in milliseconds (big-endian 16-bit) */ static void twodl_builder_add_timeout(twodl_builder_t *b, const uint8_t *inner_buf, size_t inner_len, uint16_t ms) { if (!b || b->error || !inner_buf) { if (b) b->error = TWODL_ERR_NULL; return; } twodl__push(b, TWODL_TAG_TIMEOUT); twodl__push_bytes(b, inner_buf, inner_len); twodl__push(b, (uint8_t)(ms >> 8)); twodl__push(b, (uint8_t)(ms & 0xFF)); } #ifdef __cplusplus } #endif #endif /* THIRTYTWODL_H */