# Reed-Solomon
[![Go Reference](https://pkg.go.dev/badge/github.com/klauspost/reedsolomon.svg)](https://pkg.go.dev/github.com/klauspost/reedsolomon) [![Go](https://github.com/klauspost/reedsolomon/actions/workflows/go.yml/badge.svg)](https://github.com/klauspost/reedsolomon/actions/workflows/go.yml)

Reed-Solomon Erasure Coding in Go, with speeds exceeding 1GB/s/cpu core implemented in pure Go.

This is a Go port of the [JavaReedSolomon](https://github.com/Backblaze/JavaReedSolomon) library released by 
[Backblaze](http://backblaze.com), with some additional optimizations.

For an introduction on erasure coding, see the post on the [Backblaze blog](https://www.backblaze.com/blog/reed-solomon/).

For encoding high shard counts (>256) a Leopard implementation is used.
For most platforms this performs close to the original Leopard implementation in terms of speed. 

Package home: https://github.com/klauspost/reedsolomon

Godoc: https://pkg.go.dev/github.com/klauspost/reedsolomon

# Installation
To get the package use the standard:
```bash
go get -u github.com/klauspost/reedsolomon
```

Using Go modules is recommended.

# Usage

This section assumes you know the basics of Reed-Solomon encoding. 
A good start is this [Backblaze blog post](https://www.backblaze.com/blog/reed-solomon/).

This package performs the calculation of the parity sets. The usage is therefore relatively simple.

First of all, you need to choose your distribution of data and parity shards. 
A 'good' distribution is very subjective, and will depend a lot on your usage scenario. 

To create an encoder with 10 data shards (where your data goes) and 3 parity shards (calculated):
```Go
    enc, err := reedsolomon.New(10, 3)
```
This encoder will work for all parity sets with this distribution of data and parity shards. 

If you will primarily be using it with one shard size it is recommended to use 
[`WithAutoGoroutines(shardSize)`](https://pkg.go.dev/github.com/klauspost/reedsolomon?tab=doc#WithAutoGoroutines)
as an additional parameter. This will attempt to calculate the optimal number of goroutines to use for the best speed.
It is not required that all shards are this size. 

Then you send and receive data that is a simple slice of byte slices; `[][]byte`. 
In the example above, the top slice must have a length of 13.

```Go
    data := make([][]byte, 13)
```
You should then fill the 10 first slices with *equally sized* data, 
and create parity shards that will be populated with parity data. In this case we create the data in memory, 
but you could for instance also use [mmap](https://github.com/edsrzf/mmap-go) to map files.

```Go
    // Create all shards, size them at 50000 each
    for i := range input {
      data[i] := make([]byte, 50000)
    }
    
    // The above allocations can also be done by the encoder:
    // data := enc.(reedsolomon.Extended).AllocAligned(50000)
    
    // Fill some data into the data shards
    for i, in := range data[:10] {
      for j:= range in {
         in[j] = byte((i+j)&0xff)
      }
    }
```

To populate the parity shards, you simply call `Encode()` with your data.
```Go
    err = enc.Encode(data)
```
The only cases where you should get an error is, if the data shards aren't of equal size. 
The last 3 shards now contain parity data. You can verify this by calling `Verify()`:

```Go
    ok, err = enc.Verify(data)
```

The final (and important) part is to be able to reconstruct missing shards. 
For this to work, you need to know which parts of your data is missing. 
The encoder *does not know which parts are invalid*, so if data corruption is a likely scenario, 
you need to implement a hash check for each shard. 

If a byte has changed in your set, and you don't know which it is, there is no way to reconstruct the data set.

To indicate missing data, you set the shard to nil before calling `Reconstruct()`:

```Go
    // Delete two data shards
    data[3] = nil
    data[7] = nil
    
    // Reconstruct the missing shards
    err := enc.Reconstruct(data)
```
The missing data and parity shards will be recreated. If more than 3 shards are missing, the reconstruction will fail.

If you are only interested in the data shards (for reading purposes) you can call `ReconstructData()`:

```Go
    // Delete two data shards
    data[3] = nil
    data[7] = nil
    
    // Reconstruct just the missing data shards
    err := enc.ReconstructData(data)
```

If you don't need all data shards you can use `ReconstructSome()`:

```Go
    // Delete two data shards
    data[3] = nil
    data[7] = nil
    
    // Reconstruct just the shard 3
    err := enc.ReconstructSome(data, []bool{false, false, false, true, false, false, false, false})
```

So to sum up reconstruction:
* The number of data/parity shards must match the numbers used for encoding.
* The order of shards must be the same as used when encoding.
* You may only supply data you know is valid.
* Invalid shards should be set to nil.

For complete examples of an encoder and decoder see the 
[examples folder](https://github.com/klauspost/reedsolomon/tree/master/examples).

# Splitting/Joining Data

You might have a large slice of data. 
To help you split this, there are some helper functions that can split and join a single byte slice.

```Go
   bigfile, _ := os.Readfile("myfile.data")
   
   // Split the file
   split, err := enc.Split(bigfile)
```
This will split the file into the number of data shards set when creating the encoder and create empty parity shards. 

An important thing to note is that you have to *keep track of the exact input size*. 
If the size of the input isn't divisible by the number of data shards, extra zeros will be inserted in the last shard.

To join a data set, use the `Join()` function, which will join the shards and write it to the `io.Writer` you supply: 
```Go
   // Join a data set and write it to io.Discard.
   err = enc.Join(io.Discard, data, len(bigfile))
```

## Aligned Allocations

For AMD64 aligned inputs can make a big speed difference.

This is an example of the speed difference when inputs are unaligned/aligned:

```
BenchmarkEncodeUnaligned100x20x10000-32    	  100198	     36085 ns/op	33254.75 MB/s
BenchmarkEncodeAligned100x20x10000-32      	  102711	     35207 ns/op	34084.15 MB/s
```

This is mostly the case when dealing with odd-sized shards. 

To facilitate this the package provides an `AllocAligned(shards, each int) [][]byte`. 
This will allocate a number of shards, each with the size `each`.
Each shard will then be aligned to a 64 byte boundary.

Each encoder also has a `AllocAligned(each int) [][]byte` as an extended interface which will return the same, 
but with the shard count configured in the encoder.   

It is not possible to re-aligned already allocated slices, for example when using `Split`.
When it is not possible to write to aligned shards, you should not copy to them.

# Progressive encoding

It is possible to encode individual shards using EncodeIdx:

```Go
	// EncodeIdx will add parity for a single data shard.
	// Parity shards should start out as 0. The caller must zero them.
	// Data shards must be delivered exactly once. There is no check for this.
	// The parity shards will always be updated and the data shards will remain the same.
	EncodeIdx(dataShard []byte, idx int, parity [][]byte) error
```

This allows progressively encoding the parity by sending individual data shards.
There is no requirement on shards being delivered in order, 
but when sent in order it allows encoding shards one at the time,
effectively allowing the operation to be streaming. 

The result will be the same as encoding all shards at once.
There is a minor speed penalty using this method, so send 
shards at once if they are available.

## Example

```Go
func test() {
    // Create an encoder with 7 data and 3 parity slices.
    enc, _ := reedsolomon.New(7, 3)

    // This will be our output parity.
    parity := make([][]byte, 3)
    for i := range parity {
        parity[i] = make([]byte, 10000)
    }

    for i := 0; i < 7; i++ {
        // Send data shards one at the time.
        _ = enc.EncodeIdx(make([]byte, 10000), i, parity)
    }

    // parity now contains parity, as if all data was sent in one call.
}
```

# Progressive decoding

For advanced use cases, you can progressively decode missing shards (data or parity) using `DecodeIdx`. 
This allows you to:

* Reconstruct shards from multiple sources arriving at different times
* Merge partial reconstructions from different nodes in a distributed system
* Incrementally add input shards as they become available

To access progressive decoding, cast your encoder to the `Extensions` interface:

```Go
// Cast to Extensions interface
ext := enc.(reedsolomon.Extensions)
```

## Basic Usage

`DecodeIdx` works with three parameters:
* `dst [][]byte` - Destination slices for reconstructed data (pre-allocated, initially zeroed)
* `expectInput []bool` - Which shards you expect to receive (true = expected)
* `input [][]byte` - The actual input shards for this call

Click to see example

```Go
func doReconstruct() {
	// Create encoder:
	enc, _ := reedsolomon.New(5, 3)
	ext := enc.(reedsolomon.Extensions)

	// Set up reconstruction - we want to reconstruct shards 1 and 4
	dst := make([][]byte, 5+3)
	dst[1] = make([]byte, shardSize) // Will reconstruct shard 1
	dst[4] = make([]byte, shardSize) // Will reconstruct shard 4

	// Mark which shards we expect to receive
	expectInput := []bool{
		0: true,
		1: false, // Reconstructing this shard.
		2: true,
		3: true,
		4: false, // Reconstructing this shard. 
		5: true,
		6: true,
		7: false, // We only need to supply 6 shards, so we skip this.
	}

	// First call - provide some shards
	input1 := make([][]byte, 5+3)
	input1[0] = shard0data
	input1[2] = shard2data

	err := ext.DecodeIdx(dst, expectInput, input1)

	// Second call - provide remaining shards
	input2 := make([][]byte, 5+3)
	input2[3] = shard3data
	input2[5] = shard5data
	input2[6] = shard6data

	err = ext.DecodeIdx(dst, expectInput, input2)
	// dst[1] and dst[4] now contain the reconstructed data
}
```


## Merging Partial DecodeIdx Results

You can also merge partial reconstructions from different nodes using merge mode (`expectInput == nil`):

Click to see example

```Go
func progressiveReconstruct() {
    // Create encoder: 5 data + 3 parity = 8 total shards
    enc, _ := reedsolomon.New(5, 3)
    ext := enc.(reedsolomon.Extensions)

    // Assume we lost shards 1 and 3, need to reconstruct them
    dst1 := make([][]byte, 8)
    dst1[1] = make([]byte, shardSize)  // Reconstruct shard 1
    dst1[3] = make([]byte, shardSize)  // Reconstruct shard 3

    // We need 5 valid shards total - mark which ones we expect
    expectInput := make([]bool, 8)
    expectInput[0] = true  // Have shard 0
    expectInput[2] = true  // Have shard 2
    expectInput[4] = true  // Have shard 4
    expectInput[5] = true  // Have shard 5 (parity)
    expectInput[6] = true  // Have shard 6 (parity)

    // First source provides shards 0, 2
    input1 := make([][]byte, 8)
    input1[0] = availableShards[0]
    input1[2] = availableShards[2]
    ext.DecodeIdx(dst1, expectInput, input1)

    // Second source provides shard 4, 5 and 6
    dst2 := make([][]byte, 8)
    dst2[1] = make([]byte, shardSize)  // Reconstruct shard 1
    dst2[3] = make([]byte, shardSize)  // Reconstruct shard 3

    input2 := make([][]byte, 8)
    input2[4] = availableShards[4]
    input2[5] = availableShards[5]
    input2[6] = availableShards[6]
    ext.DecodeIdx(dst2, expectInput, input2)

    // Merge the dst2 partial result into dst1
	// These can come from different machines.
    ext.DecodeIdx(dst1, nil, dst2)

    // dst1[1] and dst1[3] now contain the reconstructed data
}
```


## Important Notes

* **Consistency**: `expectInput` must be the same across all calls for a reconstruction
* **Completeness**: You must provide exactly the shards marked in `expectInput`, one time each
* **Automatic shard selection**: When more than `dataShards` positions are marked as `true` in `expectInput`, only the first `dataShards` will be used for reconstruction.
* **Size matching**: All shards must be the same size
* **Zero initialization**: Destination shards should start as zeros on the first call

Progressive decoding is particularly useful for distributed storage systems, network reconstruction scenarios, and cases where input data arrives incrementally.

While this can be used for regular reconstruction, typically that will be slightly faster and easier to use.

# Streaming/Merging

It might seem like a limitation that all data should be in memory, 
but an important property is that *as long as the number of data/parity shards are the same, 
you can merge/split data sets*, and they will remain valid as a separate set.

```Go
    // Split the data set of 50000 elements into two of 25000
    splitA := make([][]byte, 13)
    splitB := make([][]byte, 13)
    
    // Merge into a 100000 element set
    merged := make([][]byte, 13)
    
    for i := range data {
      splitA[i] = data[i][:25000]
      splitB[i] = data[i][25000:]
      
      // Concatenate it to itself
	  merged[i] = append(make([]byte, 0, len(data[i])*2), data[i]...)
	  merged[i] = append(merged[i], data[i]...)
    }
    
    // Each part should still verify as ok.
    ok, err := enc.Verify(splitA)
    if ok && err == nil {
        log.Println("splitA ok")
    }
    
    ok, err = enc.Verify(splitB)
    if ok && err == nil {
        log.Println("splitB ok")
    }
    
    ok, err = enc.Verify(merge)
    if ok && err == nil {
        log.Println("merge ok")
    }
```

This means that if you have a data set that may not fit into memory, you can split processing into smaller blocks. 
For the best throughput, don't use too small blocks.

This also means that you can divide big input up into smaller blocks, and do reconstruction on parts of your data. 
This doesn't give the same flexibility of a higher number of data shards, but it will be much more performant.

# Streaming API

There has been added support for a streaming API, to help perform fully streaming operations, 
which enables you to do the same operations, but on streams. 
To use the stream API, use [`NewStream`](https://godoc.org/github.com/klauspost/reedsolomon#NewStream) function 
to create the encoding/decoding interfaces. 

You can use [`WithConcurrentStreams`](https://godoc.org/github.com/klauspost/reedsolomon#WithConcurrentStreams) 
to ready an interface that reads/writes concurrently from the streams.

You can specify the size of each operation using 
[`WithStreamBlockSize`](https://godoc.org/github.com/klauspost/reedsolomon#WithStreamBlockSize).
This will set the size of each read/write operation.

Input is delivered as `[]io.Reader`, output as `[]io.Writer`, and functionality corresponds to the in-memory API. 
Each stream must supply the same amount of data, similar to how each slice must be similar size with the in-memory API. 
If an error occurs in relation to a stream, 
a [`StreamReadError`](https://godoc.org/github.com/klauspost/reedsolomon#StreamReadError) 
or [`StreamWriteError`](https://godoc.org/github.com/klauspost/reedsolomon#StreamWriteError) 
will help you determine which stream was the offender.

There is no buffering or timeouts/retry specified. If you want to add that, you need to add it to the Reader/Writer.

For complete examples of a streaming encoder and decoder see the 
[examples folder](https://github.com/klauspost/reedsolomon/tree/master/examples).

GF16 (more than 256 shards) is not supported by the streaming interface. 

# Advanced Options

You can modify internal options which affects how jobs are split between and processed by goroutines.

To create options, use the WithXXX functions. You can supply options to `New`, `NewStream`. 
If no Options are supplied, default options are used.

Example of how to supply options:

 ```Go
     enc, err := reedsolomon.New(10, 3, WithMaxGoroutines(25))
 ```

# Leopard Compatible GF16

When you encode more than 256 shards the library will switch to a [Leopard-RS](https://github.com/catid/leopard) implementation.

This allows encoding up to 65536 shards (data+parity) with the following limitations, similar to leopard:

* The original and recovery data must not exceed 65536 pieces.
* The shard size *must*  each be a multiple of 64 bytes.
* Each buffer should have the same number of bytes.
* Even the last shard must be rounded up to the block size.

|                 | Regular | Leopard |
|-----------------|---------|---------|
| Encode          | ✓       | ✓       |
| EncodeIdx       | ✓       | -       |
| Verify          | ✓       | ✓       |
| Reconstruct     | ✓       | ✓       |
| ReconstructData | ✓       | ✓       |
| ReconstructSome | ✓       | ✓ (+)   |
| Update          | ✓       | -       |
| Split           | ✓       | ✓       |
| Join            | ✓       | ✓       |

* (+) Same as calling `ReconstructData`.

The Split/Join functions will help to split an input to the proper sizes.

Speed can be expected to be `O(N*log(N))`, compared to the `O(N*N)`. 
Reconstruction matrix calculation is more time-consuming, 
so be sure to include that as part of any benchmark you run.  

For now SSSE3, AVX2 and AVX512 assembly are available on AMD64 platforms.

Leopard mode currently always runs as a single goroutine, since multiple 
goroutines doesn't provide any worthwhile speedup.

## Leopard GF8

It is possible to replace the default reed-solomon encoder with a leopard compatible one.
This will typically be faster when dealing with more than 20-30 shards.
Note that the limitations listed above also applies to this mode. 
See table below for speed with different number of shards.

To enable Leopard GF8 mode use `WithLeopardGF(true)`.

Benchmark Encoding and Reconstructing *1KB* shards with variable number of shards.
All implementation use inversion cache when available.
Speed is total shard size for each operation. Data shard throughput is speed/2.

| Encoder      | Shards      | Encode          | Recover All   | Recover One     |
|--------------|-------------|-----------------|---------------|-----------------|
| Cauchy       | 4+4         | 59636.23 MB/s   | 9494.17 MB/s  | 21352.14 MB/s   |
| Cauchy       | 8+8         | 89924.23 MB/s   | 15361.86 MB/s | 51814.79 MB/s   |
| Cauchy       | 16+16       | 11599.80 MB/s   | 7232.95 MB/s  | 51413.06 MB/s   |
| Cauchy       | 32+32       | 6060.57 MB/s    | 4731.84 MB/s  | 62880.86 MB/s   |
| Cauchy       | 64+64       | 3099.28 MB/s    | 2746.79 MB/s  | 72923.38 MB/s   |
| Cauchy       | 128+128     | 1576.93 MB/s    | 1472.20 MB/s  | 82504.67 MB/s   |
| Leopard GF8  | 4+4         | 55138.94 MB/s   | 7695.13 MB/s  | 12775.18 MB/s   |
| Leopard GF8  | 8+8         | 52264.75 MB/s   | 9425.42 MB/s  | 13794.19 MB/s   |
| Leopard GF8  | 16+16       | 64844.90 MB/s   | 7709.28 MB/s  | 10713.69 MB/s   |
| Leopard GF8  | 32+32       | 39757.60 MB/s   | 7671.30 MB/s  | 9575.71 MB/s    |
| Leopard GF8  | 64+64       | 40143.15 MB/s   | 6733.38 MB/s  | 9210.23 MB/s    |
| Leopard GF8  | 128+128     | 30116.72 MB/s   | 5937.93 MB/s  | 8806.07 MB/s    |
| Leopard GF16 | 256+256     | 27153.59 MB/s   | 1187.09 MB/s  | 1269.02 MB/s    |
| Leopard GF16 | 512+512     | 22315.40 MB/s   | 1888.36 MB/s  | 2136.33 MB/s    |
| Leopard GF16 | 1024+1024   | 19596.76 MB/s   | 2466.09 MB/s  | 3068.25 MB/s    |
| Leopard GF16 | 2048+2048   | 15700.86 MB/s   | 3003.09 MB/s  | 4048.59 MB/s    |
| Leopard GF16 | 4096+4096   | 15134.19 MB/s   | 3190.63 MB/s  | 4524.92 MB/s    |
| Leopard GF16 | 8192+8192   | 13246.07 MB/s   | 3067.94 MB/s  | 4062.52 MB/s    |
| Leopard GF16 | 16384+16384 | 8403.47 MB/s    | 1837.53 MB/s  | 2744.81 MB/s    |
| Leopard GF16 | 32768+32768 | 5558.16 MB/s    | 1317.53 MB/s  | 1958.46 MB/s    |

"Traditional" encoding is faster until somewhere between 8 and 16 shards.
Leopard provides fast encoding in all cases, but shows a significant overhead for reconstruction.

Calculating the reconstruction matrix takes a significant amount of computation. 
With bigger shards that will be smaller. Arguably, fewer shards typically also means bigger shards.
Due to the high shard count caching reconstruction matrices generally isn't feasible for Leopard. 

# Performance

Performance depends mainly on the number of parity shards. 
In rough terms, doubling the number of parity shards will double the encoding time.

Here are the throughput numbers with some different selections of data and parity shards. 
For reference each shard is 1MB random data, and 32 threads are used for encoding.

| Data | Parity | Go MB/s | SSSE3 MB/s | AVX2 MB/s | GFNI MB/s |
|------|--------|---------|------------|-----------|-----------|
| 5    | 2      | 40,690  | 97,827     | 202,283   | 241,747   |
| 8    | 8      | 14,244  | 62,412     | 128,343   | 271,680   |
| 10   | 4      | 17,999  | 80,093     | 152,673   | 273,628   |
| 50   | 20     | 3,520   | 24,184     | 44,734    | 47,706    |

The throughput numbers here is the size of the encoded data and parity shards.

If `runtime.GOMAXPROCS()` is set to a value higher than 1, 
the encoder will use multiple goroutines to perform the calculations in `Verify`, `Encode` and `Reconstruct`.


Benchmarking `Reconstruct()` (=`all`) versus just calling `ReconstructData()` (=`data`) gives the following result:
```
benchmark                              all MB/s       data MB/s      speedup
BenchmarkReconstruct10x2x10000-32      122764.16      110366.81      0.90x
BenchmarkReconstruct50x5x50000-32      116532.21      115657.88      0.99x
BenchmarkReconstruct10x2x1M-32         269362.91      271178.89      1.01x
BenchmarkReconstruct5x2x1M-32          236882.30      244961.33      1.03x
BenchmarkReconstruct10x4x1M-32         298882.25      308258.61      1.03x
BenchmarkReconstruct50x20x1M-32         75888.83       76367.32      1.01x
BenchmarkReconstruct10x4x16M-32         62017.48       63305.76      1.02x
```

The package will use [GFNI](https://en.wikipedia.org/wiki/AVX-512#GFNI) instructions combined with AVX512 when these are available.
This further improves speed by up to 2x over AVX2 code paths.

## ARM64 NEON

By exploiting NEON instructions the performance for ARM has been accelerated. 
Below are the performance numbers for a single core on an EC2 m6g.16xlarge (Graviton2) instance (Amazon Linux 2):

```
BenchmarkGalois128K-64        119562     10028 ns/op        13070.78 MB/s
BenchmarkGalois1M-64           14380     83424 ns/op        12569.22 MB/s
BenchmarkGaloisXor128K-64      96508     12432 ns/op        10543.29 MB/s
BenchmarkGaloisXor1M-64        10000    100322 ns/op        10452.13 MB/s
```

# Performance on ppc64le

The performance for ppc64le has been accelerated. 
This gives roughly a 10x performance improvement on this architecture as can be seen below:

```
benchmark                      old MB/s     new MB/s     speedup
BenchmarkGalois128K-160        948.87       8878.85      9.36x
BenchmarkGalois1M-160          968.85       9041.92      9.33x
BenchmarkGaloisXor128K-160     862.02       7905.00      9.17x
BenchmarkGaloisXor1M-160       784.60       6296.65      8.03x
```

# Legal

> None of section below is legal advice. Seek your own legal counsel.
> As stated by the [LICENSE](LICENSE) the authors will not be held reliable for any use of this library.
> Users are encouraged to independently verify they comply with all legal requirements. 

As can be seen in [recent news](https://www.datanami.com/2023/10/16/cloudera-hit-with-240-million-judgement-over-erasure-coding/)
there has been lawsuits related to possible patents of aspects of erasure coding functionality.

As a possible mitigation it is possible to use the tag `nopshufb` when compiling any code which includes this package.
This will remove all inclusion and use of `PSHUFB` and equivalent on other platforms.

This is done by adding `-tags=nopshufb` to `go build` and similar commands that produce binary output.

The removed code may not be infringing and even after `-tags=nopshufb` there may still be infringing code left. 

# Links
* [Backblaze Open Sources Reed-Solomon Erasure Coding Source Code](https://www.backblaze.com/blog/reed-solomon/).
* [JavaReedSolomon](https://github.com/Backblaze/JavaReedSolomon). Compatible java library by Backblaze.
* [ocaml-reed-solomon-erasure](https://gitlab.com/darrenldl/ocaml-reed-solomon-erasure). Compatible OCaml implementation.
* [reedsolomon-c](https://github.com/jannson/reedsolomon-c). C version, compatible with output from this package.
* [Reed-Solomon Erasure Coding in Haskell](https://github.com/NicolasT/reedsolomon). Haskell port of the package with similar performance.
* [reed-solomon-erasure](https://github.com/darrenldl/reed-solomon-erasure). Compatible Rust implementation.
* [go-erasure](https://github.com/somethingnew2-0/go-erasure). A similar library using cgo, slower in my tests.
* [Screaming Fast Galois Field Arithmetic](https://www.snia.org/sites/default/files/files2/files2/SDC2013/presentations/NewThinking/EthanMiller_Screaming_Fast_Galois_Field%20Arithmetic_SIMD%20Instructions.pdf). Basis for SSE3 optimizations.
* [Leopard-RS](https://github.com/catid/leopard) C library used as basis for GF16 implementation.
* [reed-solomon-simd](https://github.com/AndersTrier/reed-solomon-simd) Leopard-RS Rust implementation.

# License

This code, as the original [JavaReedSolomon](https://github.com/Backblaze/JavaReedSolomon) is published under an MIT license. See LICENSE file for more information.
