Skip to main content

Rust implementation

The osf-core crate is a safe, performant Rust implementation of the Open Streaming Format. It targets systems programming, high-performance server applications and — later, via no_std — embedded targets.

osf-core is also the foundation of the Python binding osfdata: Rust does the actual work, Python is only a thin PyO3 wrapper on top (see DECISIONS §18). One codebase, two audiences.

Capabilities

The read path is complete (raw BlockReader plus typed DataManager), the write path produces OSF5 in block mode, and the crate runs a full round-trip for every bundled reference file (load → write → reload, compared bit-for-bit).

CapabilityStatus
Magic header (OSF4/OSF5) incl. legacy identifiers
OSF4 XML and OSF5 JSON metablock parsers
Block reader (all data types) + ReaderStats
Best-effort on truncated files
Typed DataManager, access by name and index
Equidistant segments (multiple bcStartData)
Block writer (OSF5) + round-trip validation
Transparent OSFZ decompression (gzip + zlib)
PyO3 bindings (osfdata)

Building

From implementations/rust/:

cargo build
cargo test
cargo clippy

Six integration suites run over examples/ and examples/generated/ and check every bundled .osf file from the magic header to the round-trip. An #[ignore]-gated performance probe can be run with cargo test --release -- --ignored; steam_loco.osf reads and writes locally in ~3 ms each.

osf-core is part of the repository workspace; it has not been published to crates.io yet. Until then the crate is consumed via a path or git dependency.

Inspecting a file

cargo run --example inspect -- ../../examples/steam_loco.osf
cargo run --example inspect -- ../../examples/weather_station.osfz

inspect is fast (header + metablock only). For a full read with counters there is stats, for a typed channel summary dump, and copy demonstrates the writer (load → write → verify).

Manager API

use osf_core::DataManager;

let mgr = DataManager::load_from_file("examples/steam_loco.osf")?;
println!("Channels: {}", mgr.channels().len());

// Access by name (mandatory, DECISIONS §10)
let temp = mgr.channel("Sensor.Temperature").expect("not found");

// Iterate over the samples — segment timestamps are reconstructed lazily
for sample in temp.samples_with_time() {
println!("{} ns: {:?}", sample.timestamp_ns, sample.value);
}

load_from_file is the convenient entry point; load_from_reader(impl Read) does the same starting from any reader. The lower-level BlockReader iterator stays available to callers that want to process raw blocks.

Writer API

Two tiers, symmetric to the read side:

use osf_core::{DataManager, writer};

// Convenience: write a DataManager back as OSF5 (round-trip)
let mgr = DataManager::load_from_file("input.osf")?;
writer::write_to_file(&mgr, "output.osf")?;
use osf_core::writer::{WriterBuilder, ChannelDef};
use osf_core::types::{ChannelType, DataType};

// Builder: programmatic construction
let mut builder = WriterBuilder::new().creator("my-app:1.0").tag("preview");
let idx = builder.add_channel(ChannelDef {
name: "Sensor.Temperature".into(),
data_type: DataType::Double,
channel_type: ChannelType::Scalar,
..Default::default()
})?;
builder.add_timestamped_samples_f64(idx, &timestamps_ns, &values)?;
builder.write_to_file("output.osf")?;

Constraints

  • OSF5 only (DECISIONS §6) and block mode only (DECISIONS §7); streaming writes are reserved for embedded language targets.
  • No OSFZ output (DECISIONS §12) — OSFZ is read transparently.
  • Equidistant blocks float/double only (spec rev 2026-05-04); other numeric types as bcAbsTimeStampData.
  • Block splitting and bumping sizeoflengthvalue (2 → 4 for large variable samples) happen automatically.

Transparent OSFZ

The reader detects compressed OSF files by their first two bytes (gzip 0x1F 0x8B, zlib 0x78 …) and inflates them via flate2 (pure Rust, no system zlib) before the magic-header parser sees them. ReaderStats exposes compressed and compression_format (None/Zlib/Gzip).

Source code and further reading