Expand description
§VSF (Versatile Storage Format)
Self-describing binary format with hierarchical structure, strong typing, and cryptographic primitives.
§Features
- Self-describing: Type markers embedded in the data stream
- Hierarchical: Offset-based seeking, unlimited nesting depth
- Strongly-typed: Primitives (u0-u7, i3-i7, f32, f64, complex), tensors, Spirix Scalars and Circles
- Cryptographic: Built-in BLAKE3 hashing and Ed25519 signing
- Eagle Time: universal timestamps
- Huffman text compression: ~2× compression over UTF-8 for strings
§Core Type System
§Primitives
- Integers:
u0-u7(unsigned),i3-i7(signed) - IEEE Floats:
f5(f32),f6(f64) - IEEE Complex:
j5(Complex), j6(Complex) - Spirix:
s33-s77(Scalar),c33-c77(Circle)
§Tensors
- Contiguous (
t): Row-major multi-dimensional arrays (1D-4D) - Strided (
q): Non-contiguous views with explicit stride
§Metadata and Labels
VSF uses labels within sections for metadata:
l: Label text - identifies a field within a section (e.g., “shutter_speed”, “author”)- Section fields can contain multiple values:
(label:value1,value2,value3) - Sections can contain hierarchical fields:
[dImaging (lshutter_speed:f6{0.01})(laperture:f5{2.8})]
Other Metadata Types:
x: Huffman compressed Unicode text stringse: Eagle Time (seconds since lunar landing)d: Data type identifiero: Byte offsetsb: Byte lengthsn: Countsg: Cryptographic signaturesh: Cryptographic hashes
§File Structure
VSF files follow a hierarchical structure:
RÅ< Magic number + header start
z3{5} Format version (FIRST - determines encoding)
y3{5} Backward compatibility version
b#{header length} Header size (now we know how to encode it!)
L#{file length} Total file size in bytes (optional, for TCP streaming without parse-as-you-go)
eu6{current time as u64} Eagle Time when emitted (u64 oscillations, 704ps precision). OPTIONAL — devices without a clock (no RTC, no network, pre-handshake) omit the `e` field entirely rather than lie with a placeholder. Readers detect absence by the next byte being `h` (provenance hash) instead of `e`.
hp3{31}{provenance hash} Provenance: BLAKE3 hash of content (required, always 32 bytes)
ge{64}{signature} Ed25519 signature over entire file AFTER provinence hash is patched in (optional, rolling or provinence, must have one or the other)
hb{31}{rolling_hash} Rolling: BLAKE3 of current state with History (optional)
k#{key} File-level encryption key (optional)
n#{field count} Number of fields
(d3{9}raw_image:h#{hash},o#{offset},b#{size},n#{count}) Field with values
(d3{9}thumbnail:h#{hash},o#{offset},b#{size},n#{count}) ...
> Header end
[(section_fields...)...] Section data at offset for RAW image, note that if section is not encrypted and closer than 1MB from the header, section name, count and length are not required. otherwise all three. [d3{9}thumbnailn#{number of fields}b#{length of section}(section_fields...)...]Hash Strategy (Always BLAKE3):
- hp (hash provenance): Content identity - BLAKE3 hash of immutable content. Required. Computed with hp field as zeros, then filled in. Creates stable identifier for original content.
- ge (signature): Optional Ed25519 signature. When signing, compute hp, sign it, then replace hp bytes with ge signature.
- hb (hash rolling): Current file state - Optional BLAKE3 hash including History section. Updates when History updates. Useful for tracking mutable file evolution. ge or hb, must have one.
Provenance Verification: To verify a file’s provenance, zero the hp and signature/rolling hash fields and compute BLAKE3 - it will match the stored hp if original. If present, verify the ge signature against hp to authenticate the creator.
Terminology:
- Header: Everything between
RÅ<and> - Provenance primitives: Version, timestamp, hash, signature (NOT wrapped in
()) - Header field: Section pointer
(d"name" o b n)with POSITIONAL values (no:or,) - Section: Actual data blocks after the header, located at specified offsets
- Section field: Individual
(field:value)or(field:v0,v1)entries within a section ?and{}:?indicates length (ASCII 0-Z),{}indicates binary data
The : and , separators in label records make the format human-readable in hex editors and aid in forensics and corruption analysis with minimal overhead.
§Section Flattening Example
A section with hierarchical fields for camera metadata:
[d{Imaging} (l{shutter_speed}:f6{0.01}) // 1/100s as f64 (l{aperture}:f5{2.8}) // f/2.8 as f32 (l{iso}:u4{400}) // ISO 400 ]Which flattens to:
'[' + 'd' + '3' + {7u8} + "Imaging" + '(' + 'l' + '3' + {13u8} + "shutter_speed" + ':' + 'f' + '6' + {0.01f64} + ')' + '(' + 'l' + '3' + {8u8} + "aperture" + ':' + 'f' + '5' + {2.8f32} + ')' + '(' + 'l' + '3' + {3u8} + "iso" + ':' + 'u' + '4'+ {400u16} + ')' + ']'Where ‘char’ indicates a single byte character, and “string” indicates ASCII text bytes.
And the final flattened byte stream is:
[d3{0x07}Imaging(l3{0x0D}shutter_speed:f6{0x7B 14 AE 47 E1 7A 84 3F})(l3{0x08}aperture:f5{0x33 33 33 40})(l3{0x03}iso:u4{0x01 90})]Each section field is enclosed by ()’s and always starts with a text identifier (l marker + ASCII string), followed by : and its value(s) separated by ,. Section fields are flattened sequentially, creating a self-describing stream.
§Optional History Section (Will change heavily as design matures)
For applications requiring detailed tracking beyond the immutable creation timestamp:
[dHistory (ef6{1234567890.5},hb{256}{hash_at_creation},ltool:x{Lumis},lversion:z{0.1.2},lhost:x{workstation-sea}) (ef6{1234567920.3},hb{256}{hash_after_modify},ltool:x{Photon},laction:x{modified},lhost:x{laptop-pdx}) (ef6{1234567950.1},hb{256}{hash_after_access},laction:x{accessed},lhost:x{phone-mobile}) ]Each history entry records the file’s hb hash at that point in time, creating a verifiable chain of file states. To verify history integrity, recompute hb for each historical state by truncating the History section to that entry.
Which flattens to:
'[' + 'd' + '1' + {7u8} + "History" + '(' + 'e' + 'f' + '6' + {1234567890.5f64} + ',' + 'h' + 'b' + '3' + {32u8} + {32 bytes BLAKE3 hash} + ',' + 'l' + '1' + {4u8} + "tool" + ':' + 'x' + '1' + {5u8} + "Lumis" + ',' + 'l' + '1' + {7u8} + "version" + ':' + 'z' + '1' + {5u8} + "0.1.2" + ',' + 'l' + '1' + {4u8} + "host" + ':' + 'x' + '2' + {15u8} + "workstation-sea" + ')' + '(' + 'e' + 'f' + '6' + {1234567920.3f64} + ',' + 'h' + 'b' + '3' + {32u8} + {32 bytes BLAKE3 hash} + ',' + 'l' + '1' + {4u8} + "tool" + ':' + 'x' + '1' + {6u8} + "Photon" + ',' + 'l' + '1' + {6u8} + "action" + ':' + 'x' + '1' + {8u8} + "modified" + ',' + 'l' + '1' + {4u8} + "host" + ':' + 'x' + '1' + {10u8} + "laptop-pdx" + ')' + '(' + 'e' + 'f' + '6' + {1234567950.1f64} + ',' + 'h' + 'b' + '3' + {32u8} + {32 bytes BLAKE3 hash} + ',' + 'l' + '1' + {6u8} + "action" + ':' + 'x' + '1' + {8u8} + "accessed" + ',' + 'l' + '1' + {4u8} + "host" + ':' + 'x' + '1' + {12u8} + "mobile" + ')' + ']'Each history entry is a complete event enclosed in ()’s with timestamp, tool, action, and context. The History section has its own hash in the header label record for integrity verification, but is NOT included in hs (static content hash). It IS included in hb (rolling file hash).
§Quick Start
use vsf::{VsfType, VsfBuilder, Tensor, parse};
// Encode a tensor
let tensor = Tensor::new(vec![3, 4], vec![1u16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
let encoded = VsfType::t_u4(tensor).flatten();
// Decode it back
let mut ptr = 0;
let decoded = parse(&encoded, &mut ptr).unwrap();
// Build a complete VSF file with header
let vsf_file = VsfBuilder::new()
.add_section("metadata", vec![
("width".to_string(), VsfType::u(1920, false)),
("height".to_string(), VsfType::u(1080, false)),
])
.add_unboxed("pixels", vec![0xFF; 1024])
.build()
.unwrap();§Eagle Time Formats
Eagle Time counts oscillations since 1969-07-20 20:17:40 UTC (Apollo 11 lunar landing). Always coordinated, no timezones, no daylight saving. One universal time standard.
- eu6: 64-bit oscillation count (
u64) - 704ps precision, deterministic integer timestamps (default) - ef5: 32-bit float (
f32) - ~2 minute precision, legacy compact format - ef6: 64-bit float (
f64) - ~200ns precision, legacy high-accuracy format
The format version doesn’t change the epoch or oscillation frequency - 1,420,407,826 Hz (21cm hydrogen line).
§Optional in the header
Eagle Time is OPTIONAL in the file header. Devices that genuinely cannot know what time it is — embedded sensors without an RTC, freshly-booted SoCs before any network sync, single-purpose ASICs — omit the e field rather than emit a placeholder. A reader that finds the next byte after l (file length) is h (provenance hash) rather than e knows the producer was clockless and skips the field. Devices that DO know the time (host CLIs via nunc-time, kernels via QTIMER, anything network-synced) include it normally. Lying about the time is worse than admitting you don’t know it.
§Parsing and Encoding
Element-level parsing:
use vsf::parse; let data = vec![b'u', b'3', 42]; let mut ptr = 0; let value = parse(&data, &mut ptr)?; // Parses one VsfType elementHeader encoding (VsfHeader):
use vsf::file_format::VsfHeader; let mut header = VsfHeader::new(version, backward_compat); header.add_field(field); let bytes = header.encode()?; // Encodes header to bytesHeader decoding (VsfHeader):
use vsf::file_format::VsfHeader; let (header, header_end) = VsfHeader::decode(&bytes)?; // Parses the full RÅ< headerVsfHeader::decode() is implemented at file_format.rs (VsfHeader::decode); it returns the parsed header plus the byte offset where the header ends.
§Parsing APIs: Two Tiers
VSF provides two parsing approaches for sections, each suited to different use cases:
§Low-Level: VsfSection::parse() (file_format.rs)
Schema-agnostic parsing that extracts raw data without validation:
use vsf::VsfSection;
let mut ptr = 0; let section = VsfSection::parse(&bytes, &mut ptr)?; // Returns VsfSection with name and Vec<VsfField> // No schema required, no validation performedUse when:
- Reading unknown/arbitrary VSF data
- Debugging or inspecting files
- Building tooling that handles any section type
- You don’t have or need a schema
§High-Level: SectionBuilder::parse() (schema/section.rs)
Schema-validated parsing for type-safe workflows:
use vsf::schema::{SectionSchema, SectionBuilder, TypeConstraint};
let schema = SectionSchema::new("camera") .field("iso", TypeConstraint::AnyUnsigned) .field("shutter", TypeConstraint::AnyFloat);
let builder = SectionBuilder::parse(schema, §ion_bytes)?; // Validates section name matches schema // Validates each field against type constraints // Returns SectionBuilder for modify → re-encode workflowUse when:
- You know the expected structure
- Type safety and validation matter
- You need to modify and re-encode sections
- Building applications with defined schemas
Both parse the same [d"name"(d"field":value)...] binary format—SectionBuilder adds schema enforcement on top of the low-level parsing.
§Module Structure
types- Core type definitions (VsfType, Tensor, EagleTime, WorldCoord)encoding- Binary serialization (exponential-width integers, flatten)decoding- Binary parsing withparse()functionfile_format- VSF file headers and sections (VsfHeader, VsfSection)vsf_builder- High-level builder for complete filesschema- Type-safe section schemas with field validation and parse→modify→encodeverification- Cryptographic hashing and signingcrypto_algorithms- Algorithm identifiers for hashes, signatures, keys, MACsdecrypt- Decryption utilities (requirescryptofeature)text_encoding- Huffman compression for Unicode strings (requirestextfeature)colour- Colourspace conversions (VSF RGB, Rec.2020, sRGB, XYZ)builders- Domain-specific builders (RAW images)inspect- Inspection and formatting utilities (requiresinspectfeature)
Re-exports§
pub use types::datetime_to_eagle_time;pub use types::BitPackedTensor;pub use types::EagleTime;pub use types::EtType;pub use types::LayoutOrder;pub use types::NaScheme;pub use types::StridedTensor;pub use types::Tensor;pub use types::VsfType;pub use types::WaAddress;pub use types::WorldCoord;pub use types::OSCILLATIONS_PER_SECOND;pub use types::eagle_time_nanos;Deprecated pub use types::eagle_time_oscillations;pub use colour::convert::ColourFormat;pub use colour::convert::RgbLinearF32;pub use colour::convert::RgbaLinearF32;pub use encoding::EncodeNumber;pub use encoding::EncodeNumberInclusive;pub use decoding::parse;pub use decoding::parse;pub use file_format::validate_name;pub use file_format::HeaderField;pub use file_format::VsfField;pub use file_format::VsfHeader;pub use file_format::VsfSection;pub use vsf_builder::SectionMeta;pub use vsf_builder::VsfBuilder;pub use builders::build_raw_image;pub use builders::lumis_raw_capture;pub use builders::parse_raw_image;pub use builders::Aperture;pub use builders::BlackLevel;pub use builders::CalibrationHash;pub use builders::CameraBuilder;pub use builders::CameraSettings;pub use builders::CfaPattern;pub use builders::ExposureCompensation;pub use builders::FlashFired;pub use builders::FocalLength;pub use builders::FocusDistance;pub use builders::IsoSpeed;pub use builders::LensBuilder;pub use builders::LensInfo;pub use builders::Magic9;pub use builders::Manufacturer;pub use builders::MeteringMode;pub use builders::ModelName;pub use builders::ParsedRawImage;pub use builders::RawImageBuilder;pub use builders::RawMetadata;pub use builders::RawMetadataBuilder;pub use builders::SerialNumber;pub use builders::ShutterTime;pub use builders::WhiteLevel;
Modules§
- builders
- High-level builders for common VSF use cases
- colour
- VSF Colourspace Library
- crypto_
algorithms - Cryptographic algorithm identifiers for VSF hash, signature, and key types
- decoding
- VSF Decoding Module
- encoding
- VSF Encoding Module
- file_
format - VSF file format with headers and hierarchical fields
- prelude
- no_std-friendly prelude: re-exports of the
alloctypes that std’s prelude provides automatically. Individual files import viause crate::prelude::*;to stay compatible acrossstdandno_std + allocbuilds. - schema
- VSF Schema System - Type-safe section and field validation
- spectral_
image - VSF-Image v0 — spectral-first raw image container. The DNG-shaped problem solved forward: an image is K channels of sensor counts, and each channel carries its own spectral sensitivity curve. A Bayer RAW is K=3 with camera curves; an LED multispectral composite is K=25 with LED×sensor product curves; RGB under CIE 1931 is a rendering resolved at read/export time, never the storage model. Store the question, not the answer.
- themes
- VSF Inspector Themes
- types
- VSF Type System
- verification
- VSF verification functions for hashing and signing
- vsf_
builder - High-level builder for VSF files
Macros§
- type_
mismatch_ err - Build an error string for an unexpected VsfType variant at decode time.
Constants§
- VSF_
BACKWARD_ COMPAT - Oldest format version this BUILD can read — the floor is build-time chosen, cargo-style. Default is the current version only: wire breaks are never silently bridged, and a too-old file is rejected by name at the header. The plan for honoring old archives: a
compat-v*feature per legacy dialect lowers this floor AND compiles in that era’s decode paths, dispatched on the file’s z at parse time; when none is enabled, old files get the loud version error (never a wrong parse). No compat feature may ship as a floor-only no-op — lowering the floor without the era’s real readers recreates silent misinterpretation. None exist yet: a truecompat-v8needs spirix 0.0.x payload decode vendored (the old Scalar bit semantics), and a truecompat-v7needs the pre-remap x codebook vendored from git (c12c7e2~1) plus the l-as-ASCII-label mapping, and v7 sub-dialects (l→a marker remap ~0.3.5, x codebook remap 0.4.0) shipped without z bumps, so “v7” in the field is ambiguous anyway — per-archive forensics if real v7 data ever needs recovering. - VSF_
VERSION - Current VSF format version v9: spirix payloads (rd/rb/rw/rq and everything nesting them) reinterpreted under spirix 0.1 semantics: implicit-sign Scalar fraction, AMBIG=0 exponent convention, Circle keeps the N1 fraction. Same bytes decode to DIFFERENT values than v8, so this is a wire break for spirix-carrying files even tho non-spirix files are bit-identical. v8: x text codebook remapped to cover the full Unicode codespace (all 1,112,064 codepoints pre-assigned) + NFC canonicalization at the encoder boundary. Changed the Huffman bitstream for every x value — even pure-ASCII strings — so v7 x bytes and v8 x bytes are mutually unintelligible. v7: Added opcodes (op type), literal VSF format, proper bracket notation (⦉⦊ vs {})