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
}
# 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 }}
runs-on: ${{ matrix.platform.runner }}
defaults:
@ -33,10 +33,9 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: jiro4989/setup-nim-action@v2
- uses: iffy/install-nim@v5
with:
nim-version: ${{matrix.nim}}
repo-token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ matrix.nim }}
- name: Install
run: nimble install -y
- name: Build and run tests

View File

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

View File

@ -16,14 +16,13 @@ import ./utils
export wrapper, results
const
BuffMultiples* = 64
const BuffMultiples* = 64
type
LeoBufferPtr* = ptr UncheckedArray[byte]
LeoCoderKind* {.pure.} = enum
Encoder,
Encoder
Decoder
Leo* = object of RootObj
@ -32,7 +31,8 @@ type
parity*: int # total number of parity buffers (M)
dataBufferPtr: seq[LeoBufferPtr] # buffer where data is copied before encoding
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
of LeoCoderKind.Decoder:
decodeBufferCount: int # number of decoding work buffers
@ -44,9 +44,8 @@ type
LeoDecoder* = object of Leo
func encode*(
self: var LeoEncoder,
data,parity: ptr UncheckedArray[ptr UncheckedArray[byte]],
dataLen,parityLen: int ): Result[void, cstring] =
self: var LeoEncoder, data, parity: var openArray[seq[byte]]
): Result[void, cstring] =
## Encode a list of buffers in `data` into a number of `bufSize` sized
## `parity` buffers
##
@ -54,10 +53,10 @@ func encode*(
## `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!")
if parityLen != self.parity:
if parity.len != self.parity:
return err("Number of parity buffers should match!")
# zero encode work buffer to avoid corrupting with previous run
@ -65,32 +64,29 @@ func encode*(
zeroMem(self.workBufferPtr[i], self.bufSize)
# 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)
let
res = leoEncode(
let res = leoEncode(
self.bufSize.culonglong,
self.buffers.cuint,
self.parity.cuint,
self.workBufferCount.cuint,
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):
return err(leoResultString(res.LeopardResult))
for i in 0..<parityLen:
copyMem(parity[i], self.workBufferPtr[i], self.bufSize)
for i in 0 ..< parity.len:
copyMem(addr parity[i][0], self.workBufferPtr[i], self.bufSize)
return ok()
func decode*(
self: var LeoDecoder,
data,
parity,
recovered: ptr UncheckedArray[ptr UncheckedArray[byte]],
dataLen,parityLen,recoveredLen: int): Result[void, cstring] =
self: var LeoDecoder, data, parity, recovered: var openArray[seq[byte]]
): Result[void, cstring] =
## Decode a list of buffers in `data` and `parity` into a list
## of `recovered` buffers of `bufSize`. The list of `recovered`
## buffers should be match the `Leo.buffers`
@ -100,13 +96,13 @@ func decode*(
## `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!")
if parityLen != self.parity:
if parity.len != self.parity:
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!")
# clean out work and data buffers
@ -116,17 +112,17 @@ func decode*(
for i in 0 ..< self.decodeBufferCount:
zeroMem(self.decodeBufferPtr[i], self.bufSize)
for i in 0..<dataLen:
for i in 0 ..< data.len:
zeroMem(self.dataBufferPtr[i], self.bufSize)
# this is needed because erasures are nil pointers
var
dataPtr = newSeq[LeoBufferPtr](dataLen)
dataPtr = newSeq[LeoBufferPtr](data.len)
parityPtr = newSeq[LeoBufferPtr](self.workBufferCount)
# copy data into aligned buffer
for i in 0..<dataLen:
if not data[i].isNil:
for i in 0 ..< data.len:
if data[i].len > 0:
copyMem(self.dataBufferPtr[i], addr data[i][0], self.bufSize)
dataPtr[i] = self.dataBufferPtr[i]
else:
@ -134,21 +130,21 @@ func decode*(
# copy parity into aligned buffer
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)
parityPtr[i] = self.workBufferPtr[i]
else:
parityPtr[i] = nil
let
res = leoDecode(
let res = leoDecode(
self.bufSize.culonglong,
self.buffers.cuint,
self.parity.cuint,
self.decodeBufferCount.cuint,
cast[LeoDataPtr](addr dataPtr[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):
return err(leoResultString(res.LeopardResult))
@ -193,11 +189,8 @@ func free*(self: var Leo) =
# self.free()
proc init[TT: Leo](
T: type TT,
bufSize,
buffers,
parity: int,
kind: LeoCoderKind): Result[T, cstring] =
T: type TT, bufSize, buffers, parity: int, kind: LeoCoderKind
): Result[T, cstring] =
if bufSize mod BuffMultiples != 0:
return err("bufSize should be multiples of 64 bytes!")
@ -219,16 +212,9 @@ proc init[TT: Leo](
if (let res = leoInit(); res.ord != LeopardSuccess.ord):
return err(leoResultString(res.LeopardResult))
var
self = T(
kind: kind,
bufSize: bufSize,
buffers: buffers,
parity: parity)
var self = T(kind: kind, bufSize: bufSize, buffers: buffers, parity: parity)
self.workBufferCount = leoEncodeWorkCount(
buffers.cuint,
parity.cuint).int
self.workBufferCount = leoEncodeWorkCount(buffers.cuint, parity.cuint).int
# initialize encode work buffers
for _ in 0 ..< self.workBufferCount:
@ -239,9 +225,7 @@ proc init[TT: Leo](
self.dataBufferPtr.add(cast[LeoBufferPtr](self.bufSize.leoAlloc()))
if self.kind == LeoCoderKind.Decoder:
self.decodeBufferCount = leoDecodeWorkCount(
buffers.cuint,
parity.cuint).int
self.decodeBufferCount = leoDecodeWorkCount(buffers.cuint, parity.cuint).int
# initialize decode work buffers
for _ in 0 ..< self.decodeBufferCount:
@ -250,15 +234,11 @@ proc init[TT: Leo](
ok(self)
proc init*(
T: type LeoEncoder,
bufSize,
buffers,
parity: int): Result[LeoEncoder, cstring] =
T: type LeoEncoder, bufSize, buffers, parity: int
): Result[LeoEncoder, cstring] =
LeoEncoder.init(bufSize, buffers, parity, LeoCoderKind.Encoder)
proc init*(
T: type LeoDecoder,
bufSize,
buffers,
parity: int): Result[LeoDecoder, cstring] =
T: type LeoDecoder, bufSize, buffers, parity: int
): Result[LeoDecoder, cstring] =
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))
proc checkCRCPacket*(data: ptr UncheckedArray[byte], len: int): bool =
if len < 16:
for i in 1..<len:
if data[i] != data[0]:
proc checkCRCPacket*(data: openArray[byte]): bool =
if data.len < 16:
for d in data[1 .. data.high]:
if d != data[0]:
raise (ref Defect)(msg: "Packet don't match")
else:
var
crc = len.uint32
crc = data.len.uint32
packCrc: uint32
packSize: uint32
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!")
for i in 4..<len:
for i in 4..< data.len:
let v = data[i]
crc = (crc shl 3) and (crc shr (32 - 3))
crc += v
@ -49,43 +49,19 @@ proc checkCRCPacket*(data: ptr UncheckedArray[byte], len: int): bool =
if packCrc == crc:
return true
proc dropRandomIdx*(bufs: ptr UncheckedArray[ptr UncheckedArray[byte]], bufsLen,dropCount: int) =
proc dropRandomIdx*(bufs: var openArray[seq[byte]], dropCount: int) =
var
count = 0
dups: seq[int]
size = bufsLen
size = bufs.len
while count < dropCount:
let i = rand(0..<size)
if dups.find(i) == -1:
dups.add(i)
bufs[i]=nil
bufs[i].setLen(0)
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*(
buffers,
parity,
@ -96,37 +72,34 @@ proc testPackets*(
decoder: var LeoDecoder): Result[void, cstring] =
var
dataBuf = createDoubleArray(buffers, bufSize)
parityBuf = createDoubleArray(parity, bufSize)
recoveredBuf = createDoubleArray(buffers, bufSize)
defer:
freeDoubleArray(dataBuf, buffers)
freeDoubleArray(parityBuf, parity)
freeDoubleArray(recoveredBuf, buffers)
dataBuf = newSeqOfCap[seq[byte]](buffers)
parityBuf = newSeqOfCap[seq[byte]](parity)
recoveredBuf = newSeqOfCap[seq[byte]](buffers)
for i in 0..<buffers:
var
dataSeq = newSeq[byte](bufSize)
var dataSeq = newSeq[byte](bufSize)
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:
dropRandomIdx(dataBuf,buffers, dataLosses)
dropRandomIdx(dataBuf, dataLosses)
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:
if dataBuf[i].isNil:
if not checkCRCPacket(recoveredBuf[i],bufSize):
for i, d in dataBuf:
if d.len <= 0:
if not checkCRCPacket(recoveredBuf[i]):
return err(("Check failed for packet " & $i).cstring)
ok()

View File

@ -30,74 +30,53 @@ suite "Leopard Parametrization":
test "Should not allow encoding with invalid data buffer counts":
var
dataLen =3
parityLen = 2
leo = LeoEncoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64)
parity = createDoubleArray(parityLen, 64)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
data = newSeq[seq[byte]](3)
parity = newSeq[seq[byte]](2)
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":
var
dataLen =4
parityLen = 3
leo = LeoEncoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64)
parity = createDoubleArray(parityLen, 64)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
data = newSeq[seq[byte]](4)
parity = newSeq[seq[byte]](3)
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":
var
dataLen =3
parityLen = 2
leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64)
parity = createDoubleArray(parityLen, 64)
recovered = createDoubleArray(dataLen, 64)
defer:
freeDoubleArray(data, dataLen)
freeDoubleArray(parity, parityLen)
freeDoubleArray(recovered, dataLen)
data = newSeq[seq[byte]](3)
parity = newSeq[seq[byte]](2)
recovered = newSeq[seq[byte]](3)
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":
var
dataLen =4
parityLen = 1
recoveredLen = 3
leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64)
parity = createDoubleArray(parityLen, 64)
recovered = createDoubleArray(recoveredLen, 64)
data = newSeq[seq[byte]](4)
parity = newSeq[seq[byte]](1)
recovered = newSeq[seq[byte]](3)
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":
var
dataLen =4
parityLen = 2
recoveredLen = 3
leo = LeoDecoder.init(64, 4, 2).tryGet()
data = createDoubleArray(dataLen, 64)
parity = createDoubleArray(parityLen, 64)
recovered = createDoubleArray(recoveredLen, 64)
data = newSeq[seq[byte]](4)
parity = newSeq[seq[byte]](2)
recovered = newSeq[seq[byte]](3)
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":
const
@ -109,76 +88,67 @@ suite "Leopard simple Encode/Decode":
var
encoder: LeoEncoder
decoder: LeoDecoder
data: ptr UncheckedArray[ptr UncheckedArray[byte]]
parity: ptr UncheckedArray[ptr UncheckedArray[byte]]
recovered: ptr UncheckedArray[ptr UncheckedArray[byte]]
data: seq[seq[byte]]
parity: seq[seq[byte]]
recovered: seq[seq[byte]]
setup:
encoder = LeoEncoder.init(BufferSize, DataCount, ParityCount).tryGet()
decoder = LeoDecoder.init(BufferSize, DataCount, ParityCount).tryGet()
data = createDoubleArray(DataCount, BufferSize)
parity = createDoubleArray(ParityCount, BufferSize)
recovered = createDoubleArray(DataCount, BufferSize)
data = newSeq[seq[byte]](DataCount)
parity = newSeq[seq[byte]](ParityCount)
recovered = newSeq[seq[byte]](DataCount)
teardown:
freeDoubleArray(data, DataCount)
freeDoubleArray(parity, ParityCount)
freeDoubleArray(recovered, DataCount)
encoder.free()
decoder.free()
test "Test 2 data loses out of 4 possible":
for i in 0 ..< DataCount:
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
data[i] = newSeq[byte](BufferSize)
recovered[i] = newSeq[byte](BufferSize)
var str = TestString & " " & $i
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))
var
data1 = data[0]
data2 = data[1]
defer: deallocShared(data1)
data[0].setLen(0)
data[1].setLen(0)
copyMem(data1,data[0], BufferSize)
decoder.decode(data, parity, recovered).tryGet()
data[0]=nil
parity[0]=nil
check recovered[0] == data1
check recovered[1] == data2
decoder.decode(data, parity, recovered,DataCount,ParityCount,DataCount).tryGet()
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)
check equalMem(recovered[0], data1, 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":
test "bufSize = 4096, K = 800, M = 200 - drop data = 200 data":
@ -228,7 +198,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -246,7 +217,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -264,7 +236,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -282,7 +255,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -300,7 +274,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -318,7 +293,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -336,7 +312,8 @@ suite "Leopard Encode/Decode":
try:
encoder = LeoEncoder.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:
encoder.free()
decoder.free()
@ -351,7 +328,8 @@ suite "Leopard use same encoder/decoder multiple times":
decoder = LeoDecoder.init(4096, 800, 800).tryGet()
for i in 0 .. 10:
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 &
" lost parity = " & $lost:
testPackets(800, 800, 4096, 40 * i, 40 * i, encoder, decoder).tryGet()
finally:
encoder.free()