chore: orc support

Refactor public API to use `var openArray[seq[byte]]` instead of raw
pointers for ORC compatibility.

The previous API required callers to extract raw pointers from seqs
before calling encode/decode. With refc and shallowCopy, reference
counting kept buffers alive. ORC doesn't track raw pointers and can
reallocate seq buffers at yield points, leaving pointers dangling.

The new openArray API borrows data safely.

Also updates CI to Nim 2.2.4 and bumps version to 0.2.0.

Part of https://github.com/logos-storage/nim-leopard/issues/24

Signed-off-by: Chrysostomos Nanakos <chris@include.gr>
This commit is contained in:
Chrysostomos Nanakos 2025-12-23 14:59:51 +02:00
parent 0478b12df9
commit f1878d6aaa
No known key found for this signature in database
5 changed files with 186 additions and 256 deletions

View File

@ -23,7 +23,7 @@ jobs:
runner: windows-latest runner: windows-latest
} }
# Earliest supported and latest nim # Earliest supported and latest nim
nim: [1.6.18, "stable"] nim: [binary:2.2.4]
name: ${{ matrix.platform.icon }} ${{ matrix.platform.label }} - Nim v${{ matrix.nim }} name: ${{ matrix.platform.icon }} ${{ matrix.platform.label }} - Nim v${{ matrix.nim }}
runs-on: ${{ matrix.platform.runner }} runs-on: ${{ matrix.platform.runner }}
defaults: defaults:
@ -33,10 +33,9 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: jiro4989/setup-nim-action@v2 - uses: iffy/install-nim@v5
with: with:
nim-version: ${{matrix.nim}} version: ${{ matrix.nim }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install - name: Install
run: nimble install -y run: nimble install -y
- name: Build and run tests - name: Build and run tests

View File

@ -1,7 +1,7 @@
mode = ScriptMode.Verbose mode = ScriptMode.Verbose
packageName = "leopard" packageName = "leopard"
version = "0.1.1" version = "0.2.0"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "A wrapper for Leopard-RS" description = "A wrapper for Leopard-RS"
license = "Apache License 2.0 or MIT" license = "Apache License 2.0 or MIT"

View File

@ -16,27 +16,27 @@ import ./utils
export wrapper, results export wrapper, results
const const BuffMultiples* = 64
BuffMultiples* = 64
type type
LeoBufferPtr* = ptr UncheckedArray[byte] LeoBufferPtr* = ptr UncheckedArray[byte]
LeoCoderKind* {.pure.} = enum LeoCoderKind* {.pure.} = enum
Encoder, Encoder
Decoder Decoder
Leo* = object of RootObj Leo* = object of RootObj
bufSize*: int # size of the buffer in multiples of 64 bufSize*: int # size of the buffer in multiples of 64
buffers*: int # total number of data buffers (K) buffers*: int # total number of data buffers (K)
parity*: int # total number of parity buffers (M) parity*: int # total number of parity buffers (M)
dataBufferPtr: seq[LeoBufferPtr] # buffer where data is copied before encoding dataBufferPtr: seq[LeoBufferPtr] # buffer where data is copied before encoding
workBufferCount: int # number of parity work buffers workBufferCount: int # number of parity work buffers
workBufferPtr: seq[LeoBufferPtr] # buffer where parity data is written during encoding or before decoding workBufferPtr: seq[LeoBufferPtr]
# buffer where parity data is written during encoding or before decoding
case kind: LeoCoderKind case kind: LeoCoderKind
of LeoCoderKind.Decoder: of LeoCoderKind.Decoder:
decodeBufferCount: int # number of decoding work buffers decodeBufferCount: int # number of decoding work buffers
decodeBufferPtr: seq[LeoBufferPtr] # work buffer used for decoding decodeBufferPtr: seq[LeoBufferPtr] # work buffer used for decoding
of LeoCoderKind.Encoder: of LeoCoderKind.Encoder:
discard discard
@ -44,9 +44,8 @@ type
LeoDecoder* = object of Leo LeoDecoder* = object of Leo
func encode*( func encode*(
self: var LeoEncoder, self: var LeoEncoder, data, parity: var openArray[seq[byte]]
data,parity: ptr UncheckedArray[ptr UncheckedArray[byte]], ): Result[void, cstring] =
dataLen,parityLen: int ): Result[void, cstring] =
## Encode a list of buffers in `data` into a number of `bufSize` sized ## Encode a list of buffers in `data` into a number of `bufSize` sized
## `parity` buffers ## `parity` buffers
## ##
@ -54,43 +53,40 @@ func encode*(
## `parity` - list of parity `buffers` of size `bufSize` ## `parity` - list of parity `buffers` of size `bufSize`
## ##
if dataLen != self.buffers: if data.len != self.buffers:
return err("Number of data buffers should match!") return err("Number of data buffers should match!")
if parityLen != self.parity: if parity.len != self.parity:
return err("Number of parity buffers should match!") return err("Number of parity buffers should match!")
# zero encode work buffer to avoid corrupting with previous run # zero encode work buffer to avoid corrupting with previous run
for i in 0..<self.workBufferCount: for i in 0 ..< self.workBufferCount:
zeroMem(self.workBufferPtr[i], self.bufSize) zeroMem(self.workBufferPtr[i], self.bufSize)
# copy data into aligned buffer # copy data into aligned buffer
for i in 0..<dataLen: for i in 0 ..< data.len:
copyMem(self.dataBufferPtr[i], addr data[i][0], self.bufSize) copyMem(self.dataBufferPtr[i], addr data[i][0], self.bufSize)
let let res = leoEncode(
res = leoEncode( self.bufSize.culonglong,
self.bufSize.culonglong, self.buffers.cuint,
self.buffers.cuint, self.parity.cuint,
self.parity.cuint, self.workBufferCount.cuint,
self.workBufferCount.cuint, cast[LeoDataPtr](addr self.dataBufferPtr[0]),
cast[LeoDataPtr](addr self.dataBufferPtr[0]), cast[ptr pointer](addr self.workBufferPtr[0]),
cast[ptr pointer](addr self.workBufferPtr[0])) )
if ord(res) != ord(LeopardSuccess): if ord(res) != ord(LeopardSuccess):
return err(leoResultString(res.LeopardResult)) return err(leoResultString(res.LeopardResult))
for i in 0..<parityLen: for i in 0 ..< parity.len:
copyMem(parity[i], self.workBufferPtr[i], self.bufSize) copyMem(addr parity[i][0], self.workBufferPtr[i], self.bufSize)
return ok() return ok()
func decode*( func decode*(
self: var LeoDecoder, self: var LeoDecoder, data, parity, recovered: var openArray[seq[byte]]
data, ): Result[void, cstring] =
parity,
recovered: ptr UncheckedArray[ptr UncheckedArray[byte]],
dataLen,parityLen,recoveredLen: int): Result[void, cstring] =
## Decode a list of buffers in `data` and `parity` into a list ## Decode a list of buffers in `data` and `parity` into a list
## of `recovered` buffers of `bufSize`. The list of `recovered` ## of `recovered` buffers of `bufSize`. The list of `recovered`
## buffers should be match the `Leo.buffers` ## buffers should be match the `Leo.buffers`
@ -100,55 +96,55 @@ func decode*(
## `recovered` - list of recovered `buffers` of size `bufSize` ## `recovered` - list of recovered `buffers` of size `bufSize`
## ##
if dataLen != self.buffers: if data.len != self.buffers:
return err("Number of data buffers should match!") return err("Number of data buffers should match!")
if parityLen != self.parity: if parity.len != self.parity:
return err("Number of parity buffers should match!") return err("Number of parity buffers should match!")
if recoveredLen != self.buffers: if recovered.len != self.buffers:
return err("Number of recovered buffers should match buffers!") return err("Number of recovered buffers should match buffers!")
# clean out work and data buffers # clean out work and data buffers
for i in 0..<self.workBufferCount: for i in 0 ..< self.workBufferCount:
zeroMem(self.workBufferPtr[i], self.bufSize) zeroMem(self.workBufferPtr[i], self.bufSize)
for i in 0..<self.decodeBufferCount: for i in 0 ..< self.decodeBufferCount:
zeroMem(self.decodeBufferPtr[i], self.bufSize) zeroMem(self.decodeBufferPtr[i], self.bufSize)
for i in 0..<dataLen: for i in 0 ..< data.len:
zeroMem(self.dataBufferPtr[i], self.bufSize) zeroMem(self.dataBufferPtr[i], self.bufSize)
# this is needed because erasures are nil pointers # this is needed because erasures are nil pointers
var var
dataPtr = newSeq[LeoBufferPtr](dataLen) dataPtr = newSeq[LeoBufferPtr](data.len)
parityPtr = newSeq[LeoBufferPtr](self.workBufferCount) parityPtr = newSeq[LeoBufferPtr](self.workBufferCount)
# copy data into aligned buffer # copy data into aligned buffer
for i in 0..<dataLen: for i in 0 ..< data.len:
if not data[i].isNil: if data[i].len > 0:
copyMem(self.dataBufferPtr[i],addr data[i][0], self.bufSize) copyMem(self.dataBufferPtr[i], addr data[i][0], self.bufSize)
dataPtr[i] = self.dataBufferPtr[i] dataPtr[i] = self.dataBufferPtr[i]
else: else:
dataPtr[i] = nil dataPtr[i] = nil
# copy parity into aligned buffer # copy parity into aligned buffer
for i in 0..<self.workBufferCount: for i in 0 ..< self.workBufferCount:
if i < parityLen and not parity[i].isNil: if i < parity.len and parity[i].len > 0:
copyMem(self.workBufferPtr[i], addr parity[i][0], self.bufSize) copyMem(self.workBufferPtr[i], addr parity[i][0], self.bufSize)
parityPtr[i] = self.workBufferPtr[i] parityPtr[i] = self.workBufferPtr[i]
else: else:
parityPtr[i] = nil parityPtr[i] = nil
let let res = leoDecode(
res = leoDecode( self.bufSize.culonglong,
self.bufSize.culonglong, self.buffers.cuint,
self.buffers.cuint, self.parity.cuint,
self.parity.cuint, self.decodeBufferCount.cuint,
self.decodeBufferCount.cuint, cast[LeoDataPtr](addr dataPtr[0]),
cast[LeoDataPtr](addr dataPtr[0]), cast[LeoDataPtr](addr parityPtr[0]),
cast[LeoDataPtr](addr parityPtr[0]), cast[ptr pointer](addr self.decodeBufferPtr[0]),
cast[ptr pointer](addr self.decodeBufferPtr[0])) )
if ord(res) != ord(LeopardSuccess): if ord(res) != ord(LeopardSuccess):
return err(leoResultString(res.LeopardResult)) return err(leoResultString(res.LeopardResult))
@ -193,11 +189,8 @@ func free*(self: var Leo) =
# self.free() # self.free()
proc init[TT: Leo]( proc init[TT: Leo](
T: type TT, T: type TT, bufSize, buffers, parity: int, kind: LeoCoderKind
bufSize, ): Result[T, cstring] =
buffers,
parity: int,
kind: LeoCoderKind): Result[T, cstring] =
if bufSize mod BuffMultiples != 0: if bufSize mod BuffMultiples != 0:
return err("bufSize should be multiples of 64 bytes!") return err("bufSize should be multiples of 64 bytes!")
@ -219,46 +212,33 @@ proc init[TT: Leo](
if (let res = leoInit(); res.ord != LeopardSuccess.ord): if (let res = leoInit(); res.ord != LeopardSuccess.ord):
return err(leoResultString(res.LeopardResult)) return err(leoResultString(res.LeopardResult))
var var self = T(kind: kind, bufSize: bufSize, buffers: buffers, parity: parity)
self = T(
kind: kind,
bufSize: bufSize,
buffers: buffers,
parity: parity)
self.workBufferCount = leoEncodeWorkCount( self.workBufferCount = leoEncodeWorkCount(buffers.cuint, parity.cuint).int
buffers.cuint,
parity.cuint).int
# initialize encode work buffers # initialize encode work buffers
for _ in 0..<self.workBufferCount: for _ in 0 ..< self.workBufferCount:
self.workBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc())) self.workBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc()))
# initialize data buffers # initialize data buffers
for _ in 0..<self.buffers: for _ in 0 ..< self.buffers:
self.dataBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc())) self.dataBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc()))
if self.kind == LeoCoderKind.Decoder: if self.kind == LeoCoderKind.Decoder:
self.decodeBufferCount = leoDecodeWorkCount( self.decodeBufferCount = leoDecodeWorkCount(buffers.cuint, parity.cuint).int
buffers.cuint,
parity.cuint).int
# initialize decode work buffers # initialize decode work buffers
for _ in 0..<self.decodeBufferCount: for _ in 0 ..< self.decodeBufferCount:
self.decodeBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc())) self.decodeBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc()))
ok(self) ok(self)
proc init*( proc init*(
T: type LeoEncoder, T: type LeoEncoder, bufSize, buffers, parity: int
bufSize, ): Result[LeoEncoder, cstring] =
buffers,
parity: int): Result[LeoEncoder, cstring] =
LeoEncoder.init(bufSize, buffers, parity, LeoCoderKind.Encoder) LeoEncoder.init(bufSize, buffers, parity, LeoCoderKind.Encoder)
proc init*( proc init*(
T: type LeoDecoder, T: type LeoDecoder, bufSize, buffers, parity: int
bufSize, ): Result[LeoDecoder, cstring] =
buffers,
parity: int): Result[LeoDecoder, cstring] =
LeoDecoder.init(bufSize, buffers, parity, LeoCoderKind.Decoder) LeoDecoder.init(bufSize, buffers, parity, LeoCoderKind.Decoder)

View File

@ -24,22 +24,22 @@ proc randomCRCPacket*(data: var openArray[byte]) =
copyMem(addr data[4], unsafeAddr crc, sizeof(crc)) copyMem(addr data[4], unsafeAddr crc, sizeof(crc))
proc checkCRCPacket*(data: ptr UncheckedArray[byte], len: int): bool = proc checkCRCPacket*(data: openArray[byte]): bool =
if len < 16: if data.len < 16:
for i in 1..<len: for d in data[1 .. data.high]:
if data[i] != data[0]: if d != data[0]:
raise (ref Defect)(msg: "Packet don't match") raise (ref Defect)(msg: "Packet don't match")
else: else:
var var
crc = len.uint32 crc = data.len.uint32
packCrc: uint32 packCrc: uint32
packSize: uint32 packSize: uint32
copyMem(addr packSize, unsafeAddr data[0], sizeof(packSize)) copyMem(addr packSize, unsafeAddr data[0], sizeof(packSize))
if packSize != len.uint: if packSize != data.len.uint:
raise (ref Defect)(msg: "Packet size don't match!") raise (ref Defect)(msg: "Packet size don't match!")
for i in 4..<len: for i in 4..< data.len:
let v = data[i] let v = data[i]
crc = (crc shl 3) and (crc shr (32 - 3)) crc = (crc shl 3) and (crc shr (32 - 3))
crc += v crc += v
@ -49,43 +49,19 @@ proc checkCRCPacket*(data: ptr UncheckedArray[byte], len: int): bool =
if packCrc == crc: if packCrc == crc:
return true return true
proc dropRandomIdx*(bufs: ptr UncheckedArray[ptr UncheckedArray[byte]], bufsLen,dropCount: int) = proc dropRandomIdx*(bufs: var openArray[seq[byte]], dropCount: int) =
var var
count = 0 count = 0
dups: seq[int] dups: seq[int]
size = bufsLen size = bufs.len
while count < dropCount: while count < dropCount:
let i = rand(0..<size) let i = rand(0..<size)
if dups.find(i) == -1: if dups.find(i) == -1:
dups.add(i) dups.add(i)
bufs[i]=nil bufs[i].setLen(0)
count.inc count.inc
proc createDoubleArray*(
outerLen, innerLen: int
): ptr UncheckedArray[ptr UncheckedArray[byte]] =
# Allocate outer array
result = cast[ptr UncheckedArray[ptr UncheckedArray[byte]]](alloc0(
sizeof(ptr UncheckedArray[byte]) * outerLen
))
# Allocate each inner array
for i in 0 ..< outerLen:
result[i] = cast[ptr UncheckedArray[byte]](alloc0(sizeof(byte) * innerLen))
proc freeDoubleArray*(
arr: ptr UncheckedArray[ptr UncheckedArray[byte]], outerLen: int
) =
# Free each inner array
for i in 0 ..< outerLen:
if not arr[i].isNil:
dealloc(arr[i])
# Free outer array
if not arr.isNil:
dealloc(arr)
proc testPackets*( proc testPackets*(
buffers, buffers,
parity, parity,
@ -96,37 +72,34 @@ proc testPackets*(
decoder: var LeoDecoder): Result[void, cstring] = decoder: var LeoDecoder): Result[void, cstring] =
var var
dataBuf = createDoubleArray(buffers, bufSize) dataBuf = newSeqOfCap[seq[byte]](buffers)
parityBuf = createDoubleArray(parity, bufSize) parityBuf = newSeqOfCap[seq[byte]](parity)
recoveredBuf = createDoubleArray(buffers, bufSize) recoveredBuf = newSeqOfCap[seq[byte]](buffers)
defer:
freeDoubleArray(dataBuf, buffers)
freeDoubleArray(parityBuf, parity)
freeDoubleArray(recoveredBuf, buffers)
for i in 0..<buffers: for i in 0..<buffers:
var var dataSeq = newSeq[byte](bufSize)
dataSeq = newSeq[byte](bufSize)
randomCRCPacket(dataSeq) randomCRCPacket(dataSeq)
copyMem(dataBuf[i],addr dataSeq[0],bufSize) dataBuf.add(dataSeq)
encoder.encode(dataBuf, parityBuf,buffers,parity).tryGet() recoveredBuf.add(newSeq[byte](bufSize))
for _ in 0 ..< parity:
parityBuf.add(newSeq[byte](bufSize))
encoder.encode(dataBuf, parityBuf).tryGet()
if dataLosses > 0: if dataLosses > 0:
dropRandomIdx(dataBuf,buffers, dataLosses) dropRandomIdx(dataBuf, dataLosses)
if parityLosses > 0: if parityLosses > 0:
dropRandomIdx(parityBuf,parity,parityLosses) dropRandomIdx(parityBuf, parityLosses)
decoder.decode(dataBuf, parityBuf, recoveredBuf,buffers,parity,buffers).tryGet() decoder.decode(dataBuf, parityBuf, recoveredBuf).tryGet()
for i in 0..<buffers: for i, d in dataBuf:
if dataBuf[i].isNil: if d.len <= 0:
if not checkCRCPacket(recoveredBuf[i],bufSize): if not checkCRCPacket(recoveredBuf[i]):
return err(("Check failed for packet " & $i).cstring) return err(("Check failed for packet " & $i).cstring)
ok() ok()

View File

@ -18,86 +18,65 @@ suite "Leopard Parametrization":
test "Should not allow invalid data/parity buffer counts": test "Should not allow invalid data/parity buffer counts":
check: check:
LeoEncoder.init(64, 1, 2).error == LeoEncoder.init(64, 1, 2).error ==
"number of parity buffers cannot exceed number of data buffers!" "number of parity buffers cannot exceed number of data buffers!"
test "Should not allow data + parity to exceed 65536": test "Should not allow data + parity to exceed 65536":
check: check:
LeoEncoder.init(64, 65536 + 1, 0).error == LeoEncoder.init(64, 65536 + 1, 0).error ==
"number of parity and data buffers cannot exceed 65536!" "number of parity and data buffers cannot exceed 65536!"
LeoEncoder.init(64, 32768 + 1, 32768).error == LeoEncoder.init(64, 32768 + 1, 32768).error ==
"number of parity and data buffers cannot exceed 65536!" "number of parity and data buffers cannot exceed 65536!"
test "Should not allow encoding with invalid data buffer counts": test "Should not allow encoding with invalid data buffer counts":
var var
dataLen =3
parityLen = 2
leo = LeoEncoder.init(64, 4, 2).tryGet() leo = LeoEncoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64) data = newSeq[seq[byte]](3)
parity = createDoubleArray(parityLen, 64) parity = newSeq[seq[byte]](2)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
check: check:
leo.encode(data, parity,dataLen,parityLen).error == "Number of data buffers should match!" leo.encode(data, parity).error == "Number of data buffers should match!"
test "Should not allow encoding with invalid parity buffer counts": test "Should not allow encoding with invalid parity buffer counts":
var var
dataLen =4
parityLen = 3
leo = LeoEncoder.init(64, 4, 2).tryGet() leo = LeoEncoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64) data = newSeq[seq[byte]](4)
parity = createDoubleArray(parityLen, 64) parity = newSeq[seq[byte]](3)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
check: check:
leo.encode(data, parity,dataLen,parityLen).error == "Number of parity buffers should match!" leo.encode(data, parity).error == "Number of parity buffers should match!"
test "Should not allow decoding with invalid data buffer counts": test "Should not allow decoding with invalid data buffer counts":
var var
dataLen =3
parityLen = 2
leo = LeoDecoder.init(64, 4, 2).tryGet() leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64) data = newSeq[seq[byte]](3)
parity = createDoubleArray(parityLen, 64) parity = newSeq[seq[byte]](2)
recovered = createDoubleArray(dataLen, 64) recovered = newSeq[seq[byte]](3)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
freeDoubleArray(recovered, dataLen)
check: check:
leo.decode(data, parity, recovered,dataLen,parityLen,dataLen).error == "Number of data buffers should match!" leo.decode(data, parity, recovered).error == "Number of data buffers should match!"
test "Should not allow decoding with invalid data buffer counts": test "Should not allow decoding with invalid data buffer counts":
var var
dataLen =4
parityLen = 1
recoveredLen = 3
leo = LeoDecoder.init(64, 4, 2).tryGet() leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64) data = newSeq[seq[byte]](4)
parity = createDoubleArray(parityLen, 64) parity = newSeq[seq[byte]](1)
recovered = createDoubleArray(recoveredLen, 64) recovered = newSeq[seq[byte]](3)
check: check:
leo.decode(data, parity, recovered,dataLen,parityLen,recoveredLen).error == "Number of parity buffers should match!" leo.decode(data, parity, recovered).error ==
"Number of parity buffers should match!"
test "Should not allow decoding with invalid data buffer counts": test "Should not allow decoding with invalid data buffer counts":
var var
dataLen =4
parityLen = 2
recoveredLen = 3
leo = LeoDecoder.init(64, 4, 2).tryGet() leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64) data = newSeq[seq[byte]](4)
parity = createDoubleArray(parityLen, 64) parity = newSeq[seq[byte]](2)
recovered = createDoubleArray(recoveredLen, 64) recovered = newSeq[seq[byte]](3)
check: check:
leo.decode(data, parity, recovered,dataLen,parityLen,recoveredLen).error == "Number of recovered buffers should match buffers!" leo.decode(data, parity, recovered).error ==
"Number of recovered buffers should match buffers!"
suite "Leopard simple Encode/Decode": suite "Leopard simple Encode/Decode":
const const
@ -109,76 +88,67 @@ suite "Leopard simple Encode/Decode":
var var
encoder: LeoEncoder encoder: LeoEncoder
decoder: LeoDecoder decoder: LeoDecoder
data: ptr UncheckedArray[ptr UncheckedArray[byte]] data: seq[seq[byte]]
parity: ptr UncheckedArray[ptr UncheckedArray[byte]] parity: seq[seq[byte]]
recovered: ptr UncheckedArray[ptr UncheckedArray[byte]] recovered: seq[seq[byte]]
setup: setup:
encoder = LeoEncoder.init(BufferSize, DataCount, ParityCount).tryGet() encoder = LeoEncoder.init(BufferSize, DataCount, ParityCount).tryGet()
decoder = LeoDecoder.init(BufferSize, DataCount, ParityCount).tryGet() decoder = LeoDecoder.init(BufferSize, DataCount, ParityCount).tryGet()
data = createDoubleArray(DataCount, BufferSize) data = newSeq[seq[byte]](DataCount)
parity = createDoubleArray(ParityCount, BufferSize) parity = newSeq[seq[byte]](ParityCount)
recovered = createDoubleArray(DataCount, BufferSize) recovered = newSeq[seq[byte]](DataCount)
teardown: teardown:
freeDoubleArray(data, DataCount)
freeDoubleArray(parity, ParityCount)
freeDoubleArray(recovered, DataCount)
encoder.free() encoder.free()
decoder.free() decoder.free()
test "Test 2 data loses out of 4 possible": test "Test 2 data loses out of 4 possible":
for i in 0..<DataCount: for i in 0 ..< DataCount:
var data[i] = newSeq[byte](BufferSize)
str = TestString & " " & $i recovered[i] = newSeq[byte](BufferSize)
var str = TestString & " " & $i
copyMem(data[i], addr str[0], str.len)
encoder.encode(data, parity,DataCount,ParityCount).tryGet()
var
data1 =cast[ptr UncheckedArray[byte]](allocShared0(sizeof(byte) * BufferSize))
data2 = cast[ptr UncheckedArray[byte]](allocShared0(sizeof(byte) * BufferSize))
defer:
deallocShared(data1)
deallocShared(data2)
copyMem(data1,data[0], BufferSize)
copyMem(data2,data[1], BufferSize)
data[0]=nil
data[1]=nil
decoder.decode(data, parity, recovered,DataCount,ParityCount,DataCount).tryGet()
check equalMem(recovered[0], data1, BufferSize)
check equalMem(recovered[1], data2, BufferSize)
test "Test 1 data and 1 parity loss out of 4 possible":
for i in 0..<DataCount:
var
str = TestString & " " & $i
copyMem(addr data[i][0], addr str[0], str.len) copyMem(addr data[i][0], addr str[0], str.len)
encoder.encode(data, parity,DataCount,ParityCount).tryGet() for i in 0 ..< ParityCount:
parity[i] = newSeq[byte](BufferSize)
encoder.encode(data, parity).tryGet()
var data1 = cast[ptr UncheckedArray[byte]](allocShared0(sizeof(byte) * BufferSize))
defer: deallocShared(data1) var
data1 = data[0]
data2 = data[1]
copyMem(data1,data[0], BufferSize) data[0].setLen(0)
data[1].setLen(0)
data[0]=nil decoder.decode(data, parity, recovered).tryGet()
parity[0]=nil
decoder.decode(data, parity, recovered,DataCount,ParityCount,DataCount).tryGet() check recovered[0] == data1
check recovered[1] == data2
check equalMem(recovered[0], data1, BufferSize) test "Test 1 data and 1 parity loss out of 4 possible":
for i in 0 ..< DataCount:
data[i] = newSeq[byte](BufferSize)
recovered[i] = newSeq[byte](BufferSize)
var str = TestString & " " & $i
copyMem(addr data[i][0], addr str[0], str.len)
for i in 0 ..< ParityCount:
parity[i] = newSeq[byte](BufferSize)
encoder.encode(data, parity).tryGet()
var data1 = data[0]
data[0].setLen(0)
parity[0].setLen(0)
decoder.decode(data, parity, recovered).tryGet()
check recovered[0] == data1
suite "Leopard Encode/Decode": suite "Leopard Encode/Decode":
test "bufSize = 4096, K = 800, M = 200 - drop data = 200 data": test "bufSize = 4096, K = 800, M = 200 - drop data = 200 data":
@ -228,7 +198,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -246,7 +217,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -264,7 +236,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -282,7 +255,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -300,7 +274,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -318,7 +293,8 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
@ -336,23 +312,25 @@ suite "Leopard Encode/Decode":
try: try:
encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet() encoder = LeoEncoder.init(bufSize, buffers, parity).tryGet()
decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet() decoder = LeoDecoder.init(bufSize, buffers, parity).tryGet()
testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder).tryGet() testPackets(buffers, parity, bufSize, dataLoses, parityLoses, encoder, decoder)
.tryGet()
finally: finally:
encoder.free() encoder.free()
decoder.free() decoder.free()
suite "Leopard use same encoder/decoder multiple times": suite "Leopard use same encoder/decoder multiple times":
var var
encoder: LeoEncoder encoder: LeoEncoder
decoder: LeoDecoder decoder: LeoDecoder
try: try:
encoder = LeoEncoder.init(4096, 800, 800).tryGet() encoder = LeoEncoder.init(4096, 800, 800).tryGet()
decoder = LeoDecoder.init(4096, 800, 800).tryGet() decoder = LeoDecoder.init(4096, 800, 800).tryGet()
for i in 0..10: for i in 0 .. 10:
let lost = 40 * i let lost = 40 * i
test "Encode/Decode using same encoder/decoder - lost data = " & $lost & " lost parity = " & $lost: test "Encode/Decode using same encoder/decoder - lost data = " & $lost &
testPackets(800, 800, 4096, 40 * i, 40 * i, encoder, decoder).tryGet() " lost parity = " & $lost:
finally: testPackets(800, 800, 4096, 40 * i, 40 * i, encoder, decoder).tryGet()
encoder.free() finally:
decoder.free() encoder.free()
decoder.free()