| Crates.io | efm-rs |
| lib.rs | efm-rs |
| version | 0.1.0 |
| created_at | 2025-08-24 11:37:04.380631+00 |
| updated_at | 2025-08-24 11:37:04.380631+00 |
| description | A library to decode and encode efm modulated bytes (Eight-to-Fourteen Modulation) |
| homepage | |
| repository | https://github.com/markusstoller/efm-rs |
| max_upload_size | |
| id | 1808275 |
| size | 24,764 |
A tiny, zero-config Rust library that provides EFM (Eight-to-Fourteen Modulation) style encoding and decoding. It exposes a simple, symmetric API to encode arbitrary bytes into 14-bit symbols and decode them back.
Note: This crate implements an internal mapping table and a fixed 8-byte to 14-byte block transform inspired by the EFM scheme historically used on optical media. It is not intended for compatibility with any specific media format but rather to offer a compact demonstration and utility of 8→14 symbol mapping with round-trip safety.
Simple API:
EFM::encode(&[u8]) -> Vec<u8> encodes bytes in 8-byte blocks.EFM::decode(&[u8]) -> Option<Vec<u8>> decodes bytes in 14-byte blocks.Fast, table-based mapping using precomputed 14-bit codes.
Round-trip tested: encoding then decoding returns the original input (for full 8-byte blocks).
Add the following to your Cargo.toml:
[dependencies]
efm-rs = "0.1"
This crate targets Rust edition 2024.
use efm_rs::EFM;
fn main() {
let efm = EFM::new();
// Encoding works on 8-byte chunks. Here we use exactly 8 bytes.
let input = b"hello wo"; // 8 bytes
let encoded: Vec<u8> = efm.encode(input);
// Decoding expects 14-byte chunks (each 8 input bytes become 14 output bytes)
let decoded: Vec<u8> = efm.decode(&encoded).expect("valid encoding");
assert_eq!(decoded, input);
}
EFM_TABLE_RAW) of 256 14-bit patterns.None.This yields a fixed-rate transform: every 8 input bytes produce 14 output bytes. Consequently, the expansion factor is 14/8 = 1.75×.
pub struct EFM — stateless encoder/decoder wrapper with two internal mapping tables.
pub fn new() -> EFM — constructs the mapper tables once.pub fn encode(&self, value: &[u8]) -> Vec<u8>
encode_quattuordecuple method.chunks_exact(8) is used.pub fn decode(&self, value: &[u8]) -> Option<Vec<u8>>
Processes input in 14-byte chunks using a private decode_quattuordecuple method.
Returns None if any 14-bit group does not map back to a valid byte.
Block sizing:
Validation: decode will return None if it encounters an unknown 14-bit code. If you are transporting or storing encoded data, ensure it remains intact.
Endianness: Internally, values are packed big-endian; callers only interact with byte slices, so no special handling is needed.
No streaming API: The crate currently provides whole-slice encode/decode. You can build streaming on top by buffering into 8- or 14-byte chunks respectively.
use efm_rs::EFM;
fn main() {
let efm = EFM::new();
let input = b"abcdefghijklmnop"; // 16 bytes = two full blocks
let encoded = efm.encode(input);
assert_eq!(encoded.len(), 2 * 14);
let decoded = efm.decode(&encoded).unwrap();
assert_eq!(&decoded, input);
}
use efm_rs::EFM;
fn main() {
let efm = EFM::new();
let input = b"123456789"; // 9 bytes
let encoded = efm.encode(input);
// Only the first 8 bytes are encoded. The trailing one byte is ignored.
assert_eq!(encoded.len(), 14);
}
use efm_rs::EFM;
fn main() {
let efm = EFM::new();
// 14 bytes of arbitrary data that likely don't correspond to valid codewords
let bogus = [0xFFu8; 14];
assert!(efm.decode(&bogus).is_none());
}
The implementation is table-driven and uses fixed-size chunking with preallocation where possible.
No heap allocations beyond the returned Vec buffers and those required for vector growth.
For throughput-sensitive use, prefer larger buffers that contain many full blocks (multiples of 8 bytes for encode, 14 bytes for decode).
The crate does not use unsafe Rust.
Panics: The public methods do not panic for well-formed inputs. Internally, chunking uses chunks_exact, avoiding out-of-bounds access.
There is a basic unit test verifying a single-block encode→decode roundtrip for the string "hello wo". You can run the tests with:
cargo test
This project follows semantic versioning where feasible. Prior to 1.0.0, minor version bumps may include breaking changes as the API evolves.
This project is dual-licensed under either of:
at your option.
The license information is declared in Cargo.toml as MIT OR Apache-2.0. If you need the full texts, you can add LICENSE-MIT and LICENSE-APACHE files to the repository or refer to the standard license texts:
Contributions are welcome! Feel free to open issues or pull requests for bug reports, feature requests, or documentation improvements.