Protocol API

Header: zbc_protocol.h

Low-level RIFF chunk parsing, constants, and helper functions. Most users don’t need these directly – use the client or host APIs instead.

Constants

ARM Semihosting Opcodes

#define SH_SYS_OPEN           0x01
#define SH_SYS_CLOSE          0x02
#define SH_SYS_WRITEC         0x03
#define SH_SYS_WRITE0         0x04
#define SH_SYS_WRITE          0x05
#define SH_SYS_READ           0x06
#define SH_SYS_READC          0x07
#define SH_SYS_ISERROR        0x08
#define SH_SYS_ISTTY          0x09
#define SH_SYS_SEEK           0x0A
#define SH_SYS_FLEN           0x0C
#define SH_SYS_TMPNAM         0x0D
#define SH_SYS_REMOVE         0x0E
#define SH_SYS_RENAME         0x0F
#define SH_SYS_CLOCK          0x10
#define SH_SYS_TIME           0x11
#define SH_SYS_SYSTEM         0x12
#define SH_SYS_ERRNO          0x13
#define SH_SYS_GET_CMDLINE    0x15
#define SH_SYS_HEAPINFO       0x16
#define SH_SYS_EXIT           0x18
#define SH_SYS_EXIT_EXTENDED  0x20
#define SH_SYS_ELAPSED        0x30
#define SH_SYS_TICKFREQ       0x31
#define SH_SYS_TIMER_CONFIG   0x32

Open Mode Flags

#define SH_OPEN_R         0   /* "r" */
#define SH_OPEN_RB        1   /* "rb" */
#define SH_OPEN_R_PLUS    2   /* "r+" */
#define SH_OPEN_R_PLUS_B  3   /* "r+b" */
#define SH_OPEN_W         4   /* "w" */
#define SH_OPEN_WB        5   /* "wb" */
#define SH_OPEN_W_PLUS    6   /* "w+" */
#define SH_OPEN_W_PLUS_B  7   /* "w+b" */
#define SH_OPEN_A         8   /* "a" */
#define SH_OPEN_AB        9   /* "ab" */
#define SH_OPEN_A_PLUS    10  /* "a+" */
#define SH_OPEN_A_PLUS_B  11  /* "a+b" */

RIFF FourCC Codes

#define ZBC_ID_RIFF  ZBC_MAKEFOURCC('R','I','F','F')
#define ZBC_ID_SEMI  ZBC_MAKEFOURCC('S','E','M','I')
#define ZBC_ID_CNFG  ZBC_MAKEFOURCC('C','N','F','G')
#define ZBC_ID_CALL  ZBC_MAKEFOURCC('C','A','L','L')
#define ZBC_ID_PARM  ZBC_MAKEFOURCC('P','A','R','M')
#define ZBC_ID_DATA  ZBC_MAKEFOURCC('D','A','T','A')
#define ZBC_ID_RETN  ZBC_MAKEFOURCC('R','E','T','N')
#define ZBC_ID_ERRO  ZBC_MAKEFOURCC('E','R','R','O')

Device Register Offsets

#define ZBC_REG_SIGNATURE   0x00  /* 8 bytes, R - ASCII "SEMIHOST" */
#define ZBC_REG_RIFF_PTR    0x08  /* 16 bytes, RW - pointer to RIFF buffer */
#define ZBC_REG_DOORBELL    0x18  /* 1 byte, W - write to trigger request */
#define ZBC_REG_STATUS      0x19  /* 1 byte, RW - interrupt pending (write 0 to clear) */
#define ZBC_REG_SIZE        0x20  /* Total register space: 32 bytes */

Structures

type zbc_chunk_t

Generic RIFF chunk: id(4) + size(4) + data[size].

All chunk access goes through this struct - no magic offsets.

type zbc_riff_t

RIFF container: “RIFF”(4) + size(4) + form_type(4) + chunks…

type zbc_parsed_t

Parsed RIFF SEMI structure.

Parse once with zbc_riff_parse(), then access fields directly. Pointers reference the original buffer (no copies).

Chunk Functions

int zbc_chunk_validate(const zbc_chunk_t *chunk, const uint8_t *container_end)

Validate that a chunk fits within container bounds.

Parameters:
  • chunk – Pointer to chunk to validate

  • container_end – First byte PAST the valid container region

Returns:

ZBC_OK, ZBC_ERR_NULL_ARG, ZBC_ERR_HEADER_OVERFLOW, or ZBC_ERR_DATA_OVERFLOW

int zbc_chunk_next(zbc_chunk_t **out, const zbc_chunk_t *chunk)

Get pointer to next sibling chunk.

Caller MUST validate the returned chunk before accessing it.

Parameters:
  • out – Receives pointer to next chunk

  • chunk – Current chunk

Returns:

ZBC_OK or ZBC_ERR_NULL_ARG

int zbc_chunk_first_sub(zbc_chunk_t **out, const zbc_chunk_t *container, size_t header_size)

Get pointer to first sub-chunk within a container chunk.

Parameters:
  • out – Receives pointer to first sub-chunk

  • container – Parent chunk (e.g., CALL) that contains sub-chunks

  • header_size – Bytes to skip before sub-chunks (e.g., sizeof(zbc_call_header_t))

Returns:

ZBC_OK or ZBC_ERR_NULL_ARG

int zbc_chunk_end(const uint8_t **out, const zbc_chunk_t *chunk)

Get container end pointer (for validating sub-chunks).

Parameters:
  • out – Receives pointer to first byte past chunk data

  • chunk – The container chunk

Returns:

ZBC_OK or ZBC_ERR_NULL_ARG

int zbc_chunk_find(zbc_chunk_t **out, const uint8_t *start, const uint8_t *end, uint32_t id)

Find chunk by ID within container bounds.

Validates each chunk while searching.

Parameters:
  • out – Receives pointer to found chunk

  • start – Start of search region (first chunk)

  • end – First byte PAST the search region

  • id – FourCC to find

Returns:

ZBC_OK, ZBC_ERR_NULL_ARG, ZBC_ERR_NOT_FOUND, ZBC_ERR_HEADER_OVERFLOW, or ZBC_ERR_DATA_OVERFLOW

RIFF Functions

int zbc_riff_validate(const zbc_riff_t *riff, size_t buf_size, uint32_t expected_form)

Validate RIFF container.

Parameters:
  • riff – Pointer to RIFF container

  • buf_size – Total buffer size

  • expected_form – Expected form type (e.g., ZBC_ID_SEMI)

Returns:

ZBC_OK, ZBC_ERR_NULL_ARG, ZBC_ERR_BAD_RIFF_MAGIC, ZBC_ERR_BAD_FORM_TYPE, or ZBC_ERR_RIFF_OVERFLOW

int zbc_riff_end(const uint8_t **out, const zbc_riff_t *riff)

Get end pointer for RIFF container.

Parameters:
  • out – Receives pointer to first byte past RIFF data

  • riff – The RIFF container

Returns:

ZBC_OK or ZBC_ERR_NULL_ARG

int zbc_riff_parse_request(zbc_parsed_t *out, const uint8_t *buf, size_t buf_size, int int_size, int endian)

Parse a RIFF SEMI request buffer into a zbc_parsed_t structure.

This is the host-side entry point for parsing client requests. It walks all chunks (CNFG/CALL/PARM/DATA), extracts relevant fields, and populates the parsed structure. After this call, the caller can simply check fields like:

if (parsed.has_call) { dispatch parsed.opcode; }

Note: Clients should not call this function. Client response parsing is done internally by zbc_parse_response() which only extracts RETN/ERRO.

Parameters:
  • out – Receives parsed structure

  • buf – RIFF buffer to parse

  • buf_size – Size of buffer

  • int_size – Guest int size (for decoding PARM/RETN values)

  • endian – Guest endianness (ZBC_ENDIAN_*)

Returns:

ZBC_OK on success, error code on parse failure

Example:

zbc_parsed_t parsed;
int rc = zbc_riff_parse_request(&parsed, buf, size, 4, ZBC_ENDIAN_LITTLE);
if (rc == ZBC_OK) {
    if (parsed.has_retn) {
        /* Use parsed.result, parsed.host_errno */
    }
    if (parsed.has_erro) {
        /* Handle protocol error */
    }
}

Helper Functions

size_t zbc_strlen(const char *s)

Calculate string length without libc dependency.

Parameters:
  • s – Null-terminated string

Returns:

Length of string (not including null terminator)

void zbc_write_native_uint(uint8_t *buf, uintptr_t value, int size, int endianness)

Write an unsigned integer in specified endianness.

Parameters:
  • buf – Destination buffer

  • value – Value to write

  • size – Size in bytes (1-4)

  • endianness – ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG

intptr_t zbc_read_native_int(const uint8_t *buf, int size, int endianness)

Read a signed integer in specified endianness.

Parameters:
  • buf – Source buffer

  • size – Size in bytes (1-8)

  • endianness – ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG

Returns:

Signed integer value

uintptr_t zbc_read_native_uint(const uint8_t *buf, int size, int endianness)

Read an unsigned integer in specified endianness.

Parameters:
  • buf – Source buffer

  • size – Size in bytes (1-8)

  • endianness – ZBC_ENDIAN_LITTLE or ZBC_ENDIAN_BIG

Returns:

Unsigned integer value

RIFF Writing Functions

uint8_t *zbc_riff_begin_chunk(uint8_t *buf, size_t capacity, size_t *offset, uint32_t fourcc)

Begin writing a new RIFF chunk.

Writes the FourCC ID and reserves space for the size field. Returns pointer to the size field so caller can patch it later.

Parameters:
  • buf – Buffer to write to

  • capacity – Total buffer capacity

  • offset – Current write offset (updated on return)

  • fourcc – FourCC chunk ID

Returns:

Pointer to size field for later patching, or NULL if no space

void zbc_riff_patch_size(uint8_t *size_ptr, size_t data_size)

Patch the size field of a chunk after writing its data.

Parameters:
  • size_ptr – Pointer returned by zbc_riff_begin_chunk()

  • data_size – Actual size of chunk data

int zbc_riff_write_bytes(uint8_t *buf, size_t capacity, size_t *offset, const void *data, size_t size)

Write raw bytes to a RIFF buffer.

Parameters:
  • buf – Buffer to write to

  • capacity – Total buffer capacity

  • offset – Current write offset (updated on return)

  • data – Data to write

  • size – Number of bytes to write

Returns:

ZBC_OK on success, ZBC_ERR_BUFFER_FULL if no space

void zbc_riff_pad(uint8_t *buf, size_t capacity, size_t *offset)

Add padding byte if needed for RIFF word alignment.

RIFF requires chunks to be word-aligned (2-byte boundary).

Parameters:
  • buf – Buffer to write to

  • capacity – Total buffer capacity

  • offset – Current write offset (updated on return)

uint8_t *zbc_riff_begin_container(uint8_t *buf, size_t capacity, size_t *offset, uint32_t form_type)

Begin writing a RIFF container.

Writes “RIFF”, reserves space for size, and writes form type.

Parameters:
  • buf – Buffer to write to

  • capacity – Total buffer capacity

  • offset – Current write offset (updated on return)

  • form_type – Form type FourCC (e.g., ZBC_ID_SEMI)

Returns:

Pointer to size field for later patching, or NULL if no space

int zbc_riff_validate_container(const uint8_t *buf, size_t capacity, uint32_t expected_form_type)

Validate a RIFF container header.

Parameters:
  • buf – Buffer containing RIFF data

  • capacity – Total buffer capacity

  • expected_form_type – Expected form type (e.g., ZBC_ID_SEMI)

Returns:

ZBC_OK on success, ZBC_ERR_HEADER_OVERFLOW if buffer too small, ZBC_ERR_BAD_RIFF_MAGIC if not “RIFF”, ZBC_ERR_BAD_FORM_TYPE if wrong form

RIFF Reading Functions

int zbc_riff_read_header(const uint8_t *buf, size_t capacity, size_t offset, uint32_t *fourcc, uint32_t *size)

Read a RIFF chunk header.

Parameters:
  • buf – Buffer to read from

  • capacity – Total buffer capacity

  • offset – Offset to chunk header

  • fourcc – Receives FourCC chunk ID

  • size – Receives chunk data size

Returns:

ZBC_OK on success, ZBC_ERR_HEADER_OVERFLOW if not enough data

size_t zbc_riff_skip_chunk(const uint8_t *buf, size_t capacity, size_t offset)

Skip past a chunk to the next sibling.

Parameters:
  • buf – Buffer containing chunk

  • capacity – Total buffer capacity

  • offset – Offset to current chunk header

Returns:

Offset to next chunk, or capacity if at end

Opcode Table

const zbc_opcode_entry_t *zbc_opcode_lookup(int opcode)

Look up an opcode table entry by opcode number.

Parameters:
  • opcode – The SH_SYS_* opcode to look up

Returns:

Pointer to opcode entry, or NULL if not found

int zbc_opcode_count(void)

Get the number of entries in the opcode table.

Returns:

Number of defined opcodes

Byte Manipulation Macros

/* Read/write little-endian 32-bit */
ZBC_WRITE_U32_LE(buf, val)
ZBC_READ_U32_LE(buf)

/* Read/write little-endian 16-bit */
ZBC_WRITE_U16_LE(buf, val)
ZBC_READ_U16_LE(buf)

/* Write FourCC */
ZBC_WRITE_FOURCC(buf, c0, c1, c2, c3)

/* Memory operations (no libc) */
ZBC_MEMCPY(dst, src, n)
ZBC_MEMSET(dst, val, n)

/* Padding to even boundary */
ZBC_PAD_SIZE(size)