ReedSolomonEx
Note: This is a fork of ewildgoose/rs_protect with the intention to experiment with new approaches and extend platform support.
ReedSolomonEx is an Elixir wrapper around the reed-solomon Rust crate using Rustler.
It provides fast, pure-Rust Reed-Solomon encoding and decoding for binary data.
Protection is implemented on a per byte basis and an ECC/Parity code will be generated
of length N, which will protect and correct against up to N/2 errors or erasures in the
source. This makes it useful for protecting short binaries against corruption in transmission
Differences from Upstream
This fork diverges from the original with the following goals:
- Extended platform support — Native builds for embedded targets including:
aarch64(ARM64 embedded devices)riscv64(RISC-V platforms)
- Experimental features — A playground for testing new approaches and optimizations
- Nerves-friendly — Designed to work seamlessly with Nerves embedded Elixir projects
Features
- Encode flat binary messages with added parity bytes
- Decode and correct up to
floor(parity_bytes / 2)corrupted bytes with correct/2, correct/3 - Detect uncorrectable errors
- Get the number of corrections made with correct_err_count/2, correct_err_count/3
- Detect whether a message is corrupted with is_corrupted/1
- Batch encode/decode many chunks per NIF call with encode_batch/3, correct_batch/3
Installation
Add to mix.exs:
def deps do
[
{:reed_solomon_ex, "~> 0.2"}
]
end
Usage
iex> data = <<1, 2, 3, 4, 5>>
iex> {:ok, encoded} = ReedSolomonEx.encode(data, 4)
iex> ReedSolomonEx.is_corrupted(encoded, 4)
{:ok, false}
iex> {:ok, decoded} = ReedSolomonEx.correct(encoded, 4)
decoded == data
iex> corrupted = :binary.replace(encoded, <<2>>, <<42>>, global: false)
iex> ReedSolomonEx.is_corrupted(corrupted, 4)
{:ok, true}
iex> {:ok, {recovered, count}} = ReedSolomonEx.correct_err_count(corrupted, 4)
{:ok, {^data, 1}}
Batch API
When encoding many small chunks (e.g. framing a large payload), use
encode_batch/3 to amortize NIF call overhead — one NIF call encodes a whole
slice of chunks and the generator polynomial is computed once:
{:ok, codewords} = ReedSolomonEx.encode_batch(chunks, 8)
# results is a list of {:ok, data} | :error, one per codeword
{:ok, results} = ReedSolomonEx.correct_batch(codewords, 8)
Batches are internally sliced so no single NIF invocation exceeds the ~1ms
regular-scheduler budget, even on slow embedded cores. Pass slice_size: n
to tune the slicing for your hardware.
Scheduler Behavior
Reed-Solomon codewords are capped at 255 bytes, so individual operations are sub-millisecond even on slow cores. As of v0.2.0:
encode/2,encode_ecc/2,encode_batch/3,is_corrupted/2and batch slices always run on regular schedulers.correct/2,3,correct_err_count/2,3run on regular schedulers forparity_bytes <= 32and on dirty-CPU schedulers above that, where worst-case decode work on a ~1 GHz in-order core can exceed 1ms.
This matters on embedded targets where dirty-CPU schedulers are saturated by other long-running NIFs (e.g. ML inference): before v0.2.0 every call was dirty-scheduled and a sub-millisecond encode could wait behind a 100ms+ inference job.
Dynamic Parity Strategy
def choose_parity(bytes) when byte_size(bytes) > 80, do: 16
def choose_parity(_), do: 4
Development
This project uses devenv for development environment setup.
# Enter the development shell
devenv shell
# Or use direnv for automatic shell activation
direnv allow
Building
# Force local build (for development)
REED_SOLOMON_EX_FORCE_BUILD=1 mix compile
# Run tests
mix test