Skip to content

Expose codec functions (encode/decode header, length, base32) in all bindings and the Go port #45

Description

@titusz

Motivation

The C2PA soft-binding integration (io.iscc.v0) defines the binding value as a seamless byte stream of concatenated raw ISCC-UNITs (variable-length self-describing header + hash body, 34 bytes per 256-bit unit, MainType ascending). Producing and parsing that stream requires the low-level codec:

  • pack (unit string → raw bytes): decode_base32
  • unpack (byte stream → unit strings): decode_headerdecode_lengthencode_component, looping on the returned tail

All of these are public in the Rust crate (crates/iscc-lib/src/codec.rs), but none of the bindings nor the Go port expose them. Verified against iscc-lib 0.6.0 (Python): only encode_component, iscc_decode, iscc_decompose, and encode_base64 are exported (from #6/#7), so neither direction is currently possible outside Rust without reimplementing the codec. The same subset (and the same gap) applies to napi, wasm, jni, rb, ffi, uniffi, and packages/go (codec.go exports only EncodeBase64, EncodeComponent, IsccDecompose, IsccDecode, JsonToDataUrl).

Requested functions

Expose the remaining codec surface, matching the Rust signatures:

pub fn encode_header(mtype, stype, version, length) -> IsccResult<Vec<u8>>
pub fn decode_header(data: &[u8]) -> IsccResult<(MainType, SubType, Version, u32, Vec<u8>)>
pub fn encode_length(mtype: MainType, length: u32) -> IsccResult<u32>
pub fn decode_length(mtype: MainType, length: u32, stype: SubType) -> u32
pub fn encode_base32(data: &[u8]) -> String
pub fn decode_base32(code: &str) -> IsccResult<Vec<u8>>

Scope:

  • crates/iscc-py (Python)
  • crates/iscc-napi (Node)
  • crates/iscc-wasm (WASM)
  • crates/iscc-jni (Java)
  • crates/iscc-rb (Ruby)
  • crates/iscc-ffi (C/C++, .NET, Swift, Kotlin consumers)
  • crates/iscc-uniffi
  • packages/go (native implementations in codec.go)

Reference use case

Round-trip validated with iscc-core 1.3.0 (the Python reference, which exposes the full codec):

def pack_units(units):  # list of canonical ISCC-UNIT strings -> bytes
    raw = [decode_base32(u.removeprefix("ISCC:")) for u in units]
    return b"".join(sorted(raw, key=lambda r: decode_header(r)[0]))

def unpack_units(data):  # bytes -> list of canonical ISCC-UNIT strings
    units, tail = [], data
    while tail:
        mtype, stype, version, length_idx, body = decode_header(tail)
        nbytes = decode_length(mtype, length_idx) // 8
        units.append("ISCC:" + encode_component(mtype, stype, version, nbytes * 8, body[:nbytes]))
        tail = body[nbytes:]
    return units

Four 256-bit units (Meta, Content-Text, Data, Instance) pack to exactly 136 bytes and restore bit-identically. Because headers are self-describing, no framing/length prefixes are needed — which is what makes this viable as the c2pa.soft-binding bstr value format.

Acceptance

  • Codec parity across all bindings + Go, with the existing per-binding smoke/parity tests extended by a shared pack/unpack round-trip vector (e.g. the 4-unit/136-byte example above).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions