efm-rs
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.
Features
- 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).
Installation
Add the following to your Cargo.toml:
[]
= "0.1"
This crate targets Rust edition 2024.
Quick start
use EFM;
How it works
- The library uses a pre-defined lookup table (
EFM_TABLE_RAW) of 256 14-bit patterns. - For encoding:
- Input data is processed in 8-byte chunks.
- Each byte is mapped to its corresponding 14-bit code.
- Those 8 codes are packed into a big-endian 128-bit accumulator and then serialized to 14 bytes (skipping the top 2 MSB bytes).
- For decoding:
- Input data is processed in 14-byte chunks.
- The 14 bytes are converted back to a 128-bit value.
- The value is split into eight 14-bit symbols, each of which is reverse-mapped back to the original byte.
- If any 14-bit symbol is not found in the reverse map, decoding fails and returns
None.
This yields a fixed-rate transform: every 8 input bytes produce 14 output bytes. Consequently, the expansion factor is 14/8 = 1.75×.
API overview
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>- Processes input in 8-byte chunks using a private
encode_quattuordecuplemethod. - For inputs whose length is not a multiple of 8, the extra tail is currently ignored because
chunks_exact(8)is used.
- Processes input in 8-byte chunks using a private
pub fn decode(&self, value: &[u8]) -> Option<Vec<u8>>- Processes input in 14-byte chunks using a private
decode_quattuordecuplemethod. - Returns
Noneif any 14-bit group does not map back to a valid byte.
- Processes input in 14-byte chunks using a private
Usage notes and limitations
- Block sizing:
- Encode operates on 8-byte blocks. Any remainder bytes beyond complete 8-byte blocks are not encoded (ignored).
- Decode operates on 14-byte blocks. Any remainder bytes beyond complete 14-byte blocks are ignored before decoding.
- Validation:
decodewill returnNoneif 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.
Examples
- Roundtrip for multiple blocks:
use EFM;
- Handling non-multiple-of-8 input on encode:
use EFM;
- Handling invalid input on decode:
use EFM;
Performance
- 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).
Safety
- 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.
Testing
There is a basic unit test verifying a single-block encode→decode roundtrip for the string "hello wo". You can run the tests with:
Versioning
This project follows semantic versioning where feasible. Prior to 1.0.0, minor version bumps may include breaking changes as the API evolves.
License
This project is dual-licensed under either of:
- Apache License, Version 2.0
- MIT license
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:
Contributing
Contributions are welcome! Feel free to open issues or pull requests for bug reports, feature requests, or documentation improvements.