Fast, safe reading and writing of ASAM MDF measurement files, in Rust.
falcon_mdf reads and writes MDF 4 (.mf4) and, with the mdf3 feature,
MDF 2/3 (.mdf) files: the formats automotive and industrial loggers record
CAN, LIN, Ethernet and sensor data to. The same decoder ships as this crate,
Python bindings, a WebAssembly build and a
desktop viewer.
- Correct, or it says so. A channel decodes to the right values or fails with a reason, never with partial data or raw values passed off as converted. Output is checked against asammdf on a corpus of CAN, LIN and GPS/IMU logs.
- Safe on files you did not write. Malformed input returns an error, not a panic, abort or hang; a sweep of 1,200 mutated files produces none of them.
- Fast. 1.8× to 4.5× faster than asammdf, depending on file size; see Performance.
The browser demo opens and
plots an .mf4 file without installing anything. The file stays on your machine.
[dependencies]
falcon_mdf = "0.7"MSRV is 1.89 for every feature combination. Default features are mmap
and parallel; everything else is opt-in:
| Flag | Enables |
|---|---|
mdf3 |
MDF 2.x/3.x reader |
dbc / arxml |
CAN databases from DBC files / AUTOSAR ECU extracts |
zstd / lz4 |
Zstandard / LZ4 data blocks |
arrow / parquet |
Arrow IPC / Parquet export |
hdf5 |
HDF5 export (pure Rust) |
mat4 / mat / mat73 |
MATLAB v4 / level 5 / v7.3 export |
asc |
Vector ASC trace export |
A block compressed with a codec that is not compiled in fails by name rather than returning wrong bytes.
use falcon_mdf::{Mf4File, SignalValues};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = Mf4File::open("measurement.mf4")?;
println!("MDF {} with {} channels", file.version(), file.channel_count());
if let Some(channel) = file.find_channel("VehicleSpeed") {
let signal = file.signal(channel)?;
match signal.values()? {
SignalValues::F64(v) => println!("first: {:?} {}", v.first(), signal.unit()),
other => println!("{} samples of {}", other.len(), other.kind().name()),
}
}
Ok(())
}Decode a CAN bus log against a DBC (dbc feature)
use falcon_mdf::{CanDatabase, IdMatching, Mf4File};
let file = Mf4File::open("truck.mf4")?;
let database = CanDatabase::from_dbc_path("j1939.dbc")?
.with_matching(IdMatching::J1939Pgn);
for signal in file.decode_bus(&database)?.iter() {
println!("{}.{}: {} readings [{}]", signal.message, signal.name, signal.len(), signal.unit);
}Write an MF4 file
use falcon_mdf::Mf4Writer;
let mut writer = Mf4Writer::new();
let group = writer.add_group(&[0.0, 0.1, 0.2])?;
group.add_channel("Speed", "km/h", &[0.0, 5.0, 10.0])?;
writer.write_to_file("out.mf4")?;Export channels to Parquet (parquet feature)
use falcon_mdf::{Mf4File, export::write_parquet};
let file = Mf4File::open("measurement.mf4")?;
let series = file.filter(&["VehicleSpeed".into(), "EngineRPM".into()])?;
write_parquet(&series, &mut std::fs::File::create("measurement.parquet")?)?;Runnable programs are in examples/: list_channels,
export_to_csv, write_mf4, decode_bus and block_map.
- MDF 4.0–4.2, sorted and unsorted, finished and unfinished; MDF 2.x/3.x with
mdf3 - Every data layout: DT/DZ/DL/HL/LD/DV/DI blocks, all six deflate/zstd/LZ4 compression forms, VLSD signals, CA arrays and invalidation bits
- Typed samples: integers keep their width, byte payloads stay bytes and text stays text; conversions from linear and rational to formulas, value/range/text tables and bitfields
- Bus logs: CAN, LIN, Ethernet and FlexRay frames; CAN signals decoded against DBC or ARXML (J1939 and multiplexing included), LIN against LDF
- Streaming: bounded-window channel reads, and reads over HTTP range requests or any byte source
- Tools: cut, resample, filter, concatenate, stack, signal arithmetic, channel search, anonymisation and a full block map
- Export to CSV, Parquet, Arrow IPC, HDF5, MATLAB MAT (v4, v5, v7.3) and Vector ASC
- Writing MF4 from scratch or from an existing file, with compression
The field reference has the complete list and how it compares with asammdf and other Rust crates.
Whole-file reads against asammdf 8.7.2 select(), release build, median of
three runs on an Apple-silicon Mac (2026-09-27):
| Files | Speed-up |
|---|---|
| Over 1 MB (12 files), geometric mean | 4.5× (worst 1.8×) |
| 122 MB, transposed deflate | 1.8× |
| 480 MB, uncompressed | 1.8× |
Files under 100 KB show much larger ratios, which mostly measure asammdf's
start-up cost. Per-file timings, memory use and method are in
benchmarks/COMPARISON.md.
Files are memory-mapped by default. Use Mf4File::open_buffered for a file
another process may still be writing or replacing.
- Arrays with more than one dynamically sized dimension
- Variable-length arrays exported to CSV, MAT or HDF5 (Arrow and Parquet work)
- Lossless editing of arbitrary files:
Mf4Writer::from_filemay drop channels and metadata it cannot represent - MDF 4.20
##LDchains whose separate invalidation needs incompatible layouts
An unsupported channel fails with Mf4Error::Unsupported, naming the feature,
and the rest of the file still reads. The ##LD case is the exception: that
file fails to open. See Limits and errors.
Click the map for the interactive version, with guided tours and links into the source.
The guides live in docs/site/. Preview them with
uvx --from zensical==0.0.65 zensical serve; CI builds them with
zensical build --strict, which fails on broken links.
Licensed under either of Apache License 2.0 or MIT license, at your option.
