From 5701d937c8d36a1f629073130d26246ecc02caf7 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Fri, 6 Mar 2020 21:19:43 +0200 Subject: [PATCH] Signed variable integers fixes. (#96) * Fix signed varints. Add tests for signed varints. Remove some casts to allow usage at compile time. * Fix vsizeof() on 32bit platforms. * Add `hint` and `zint` types for proper signed integer encoding. * Fix varint related bugs. * Update requirements. * Fix interop tests because of fixed readLine. * Add putVarint, getVarint and tests. --- libp2p.nimble | 4 +- libp2p/daemon/daemonapi.nim | 29 ++-- libp2p/protocols/pubsub/rpc/protobuf.nim | 2 +- libp2p/varint.nim | 200 ++++++++++++++++++---- libp2p/vbuffer.nim | 4 +- tests/testinterop.nim | 14 +- tests/testvarint.nim | 204 ++++++++++++++++++++++- 7 files changed, 399 insertions(+), 58 deletions(-) diff --git a/libp2p.nimble b/libp2p.nimble index 31b9bfbce..8cfef98fb 100644 --- a/libp2p.nimble +++ b/libp2p.nimble @@ -10,8 +10,8 @@ skipDirs = @["tests", "examples", "Nim"] requires "nim > 0.19.4", "secp256k1", "nimcrypto >= 0.4.1", - "chronos >= 2.3.5", - "bearssl >= 0.1.3", + "chronos >= 2.3.8", + "bearssl >= 0.1.4", "chronicles >= 0.7.0", "stew" diff --git a/libp2p/daemon/daemonapi.nim b/libp2p/daemon/daemonapi.nim index f299c6570..f2b238159 100644 --- a/libp2p/daemon/daemonapi.nim +++ b/libp2p/daemon/daemonapi.nim @@ -8,8 +8,8 @@ ## those terms. ## This module implementes API for `go-libp2p-daemon`. -import os, osproc, strutils, tables, streams, strtabs -import chronos, stew/base58 +import os, osproc, strutils, tables, strtabs +import chronos import ../varint, ../multiaddress, ../multicodec, ../cid, ../peer import ../wire, ../multihash, ../protobuf/minprotobuf import ../crypto/crypto @@ -175,7 +175,7 @@ proc requestConnect(peerid: PeerID, for item in addresses: msg.write(initProtoField(2, item.data.buffer)) if timeout > 0: - msg.write(initProtoField(3, timeout)) + msg.write(initProtoField(3, hint64(timeout))) result.write(initProtoField(1, cast[uint](RequestType.CONNECT))) result.write(initProtoField(2, msg)) result.finish() @@ -201,7 +201,7 @@ proc requestStreamOpen(peerid: PeerID, for item in protocols: msg.write(initProtoField(2, item)) if timeout > 0: - msg.write(initProtoField(3, timeout)) + msg.write(initProtoField(3, hint64(timeout))) result.write(initProtoField(1, cast[uint](RequestType.STREAM_OPEN))) result.write(initProtoField(3, msg)) result.finish() @@ -235,7 +235,7 @@ proc requestDHTFindPeer(peer: PeerID, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(2, peer)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -251,7 +251,7 @@ proc requestDHTFindPeersConnectedToPeer(peer: PeerID, msg.write(initProtoField(1, msgid)) msg.write(initProtoField(2, peer)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -268,7 +268,7 @@ proc requestDHTFindProviders(cid: Cid, msg.write(initProtoField(3, cid.data.buffer)) msg.write(initProtoField(6, count)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -283,7 +283,7 @@ proc requestDHTGetClosestPeers(key: string, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(4, key)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -298,7 +298,7 @@ proc requestDHTGetPublicKey(peer: PeerID, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(2, peer)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -313,7 +313,7 @@ proc requestDHTGetValue(key: string, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(4, key)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -328,7 +328,7 @@ proc requestDHTSearchValue(key: string, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(4, key)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -345,7 +345,7 @@ proc requestDHTPutValue(key: string, value: openarray[byte], msg.write(initProtoField(4, key)) msg.write(initProtoField(5, value)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -360,7 +360,7 @@ proc requestDHTProvide(cid: Cid, timeout = 0): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(3, cid.data.buffer)) if timeout > 0: - msg.write(initProtoField(7, uint(timeout))) + msg.write(initProtoField(7, hint64(timeout))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.DHT))) result.write(initProtoField(5, msg)) @@ -374,7 +374,7 @@ proc requestCMTagPeer(peer: PeerID, tag: string, weight: int): ProtoBuffer = msg.write(initProtoField(1, msgid)) msg.write(initProtoField(2, peer)) msg.write(initProtoField(3, tag)) - msg.write(initProtoField(4, weight)) + msg.write(initProtoField(4, hint64(weight))) msg.finish() result.write(initProtoField(1, cast[uint](RequestType.CONNMANAGER))) result.write(initProtoField(6, msg)) @@ -949,7 +949,6 @@ proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async.} = try: var pb = await transp.transactMessage(requestListPeers()) pb.withMessage() do: - var address = newSeq[byte]() result = newSeq[PeerInfo]() var res = pb.enterSubmessage() while res != 0: diff --git a/libp2p/protocols/pubsub/rpc/protobuf.nim b/libp2p/protocols/pubsub/rpc/protobuf.nim index a114c9c17..719ba6144 100644 --- a/libp2p/protocols/pubsub/rpc/protobuf.nim +++ b/libp2p/protocols/pubsub/rpc/protobuf.nim @@ -150,7 +150,7 @@ proc encodeSubs*(subs: SubOpts, pb: var ProtoBuffer) {.gcsafe.} = proc decodeSubs*(pb: var ProtoBuffer): seq[SubOpts] {.gcsafe.} = while true: var subOpt: SubOpts - var subscr: int + var subscr: uint discard pb.getVarintValue(1, subscr) subOpt.subscribe = cast[bool](subscr) trace "read subscribe field", subscribe = subOpt.subscribe diff --git a/libp2p/varint.nim b/libp2p/varint.nim index 60f556de4..9cb044b5b 100644 --- a/libp2p/varint.nim +++ b/libp2p/varint.nim @@ -15,7 +15,7 @@ ## - LibP2P varint, which is able to encode only 63bits of uint64 number and ## maximum size of encoded value is 9 octets (bytes). ## https://github.com/multiformats/unsigned-varint -import bitops +import bitops, typetraits type VarintStatus* {.pure.} = enum @@ -31,21 +31,70 @@ type LP* = object ## Use this type to specify LibP2P varint encoding + zint32* = distinct int32 + zint64* = distinct int64 + zint* = distinct int + ## Signed integer types which will be encoded using zigzag encoding. + + hint32* = distinct int32 + hint64* = distinct int64 + hint* = distinct int + ## Signed integer types which will be encoded using simple cast. + PBSomeUVarint* = uint | uint64 | uint32 - PBSomeSVarint* = int | int64 | int32 - PBSomeVarint* = PBSomeUVarint | PBSomeSVarint + PBSomeSVarint* = hint | hint64 | hint32 + PBZigVarint* = zint | zint64 | zint32 + PBSomeVarint* = PBSomeUVarint | PBSomeSVarint | PBZigVarint LPSomeUVarint* = uint | uint64 | uint32 | uint16 | uint8 LPSomeVarint* = LPSomeUVarint SomeVarint* = PBSomeVarint | LPSomeVarint SomeUVarint* = PBSomeUVarint | LPSomeUVarint VarintError* = object of CatchableError -proc vsizeof*(x: SomeVarint): int {.inline.} = +proc vsizeof*(x: SomeUVarint): int {.inline.} = ## Returns number of bytes required to encode integer ``x`` as varint. - if x == cast[type(x)](0): - result = 1 + if x == type(x)(0): + 1 else: - result = (fastLog2(x) + 1 + 7 - 1) div 7 + (fastLog2(x) + 1 + 7 - 1) div 7 + +proc vsizeof*(x: PBSomeSVarint): int {.inline.} = + ## Returns number of bytes required to encode signed integer ``x``. + ## + ## Note: This procedure interprets signed integer as ProtoBuffer's + ## ``int32`` and ``int64`` integers. + when sizeof(x) == 8: + if int64(x) == 0'i64: + 1 + else: + (fastLog2(uint64(x)) + 1 + 7 - 1) div 7 + else: + if int32(x) == 0'i32: + 1 + else: + (fastLog2(uint32(x)) + 1 + 7 - 1) div 7 + +proc vsizeof*(x: PBZigVarint): int {.inline.} = + ## Returns number of bytes required to encode signed integer ``x``. + ## + ## Note: This procedure interprets signed integer as ProtoBuffer's + ## ``sint32`` and ``sint64`` integer. + when sizeof(x) == 8: + if int64(x) == 0'i64: + 1 + else: + if int64(x) < 0'i64: + vsizeof(not(uint64(x) shl 1)) + else: + vsizeof(uint64(x) shl 1) + else: + if int32(x) == 0'i32: + 1 + else: + if int32(x) < 0'i32: + vsizeof(not(uint32(x) shl 1)) + else: + vsizeof(uint32(x) shl 1) proc getUVarint*[T: PB|LP](vtype: typedesc[T], pbytes: openarray[byte], @@ -83,16 +132,16 @@ proc getUVarint*[T: PB|LP](vtype: typedesc[T], var shift = 0'u8 result = VarintStatus.Incomplete outlen = 0 - outval = cast[type(outval)](0) + outval = type(outval)(0) for i in 0..= MaxBits: result = VarintStatus.Overflow outlen = 0 - outval = cast[type(outval)](0) + outval = type(outval)(0) break else: - outval = outval or (cast[type(outval)](b and 0x7F'u8) shl shift) + outval = outval or (type(outval)(b and 0x7F'u8) shl shift) shift += 7 inc(outlen) if (b and 0x80'u8) == 0'u8: @@ -100,12 +149,12 @@ proc getUVarint*[T: PB|LP](vtype: typedesc[T], break if result == VarintStatus.Incomplete: outlen = 0 - outval = cast[type(outval)](0) + outval = type(outval)(0) when vtype is LP: if result == VarintStatus.Success: if outlen != vsizeof(outval): - outval = cast[type(outval)](0) + outval = type(outval)(0) outlen = 0 result = VarintStatus.Overlong @@ -135,16 +184,16 @@ proc putUVarint*[T: PB|LP](vtype: typedesc[T], when vtype is LP: if sizeof(outval) == 8: - if (cast[uint64](outval) and 0x8000_0000_0000_0000'u64) != 0'u64: + if (uint64(outval) and 0x8000_0000_0000_0000'u64) != 0'u64: result = Overflow return - if value <= cast[type(outval)](0x7F): - buffer[0] = cast[byte](outval and 0xFF) + if value <= type(outval)(0x7F): + buffer[0] = byte(outval and 0xFF) inc(k) else: - while value != cast[type(outval)](0): - buffer[k] = cast[byte]((value and 0x7F) or 0x80) + while value != type(outval)(0): + buffer[k] = byte((value and 0x7F) or 0x80) value = value shr 7 inc(k) buffer[k - 1] = buffer[k - 1] and 0x7F'u8 @@ -158,9 +207,11 @@ proc putUVarint*[T: PB|LP](vtype: typedesc[T], proc getSVarint*(pbytes: openarray[byte], outsize: var int, outval: var PBSomeSVarint): VarintStatus {.inline.} = - ## Decode Google ProtoBuf's `signed varint` from buffer ``pbytes`` and store - ## it to ``outval``. On success ``outlen`` will be set to number of bytes - ## processed while decoding `signed varint`. + ## Decode signed integer (``int32`` or ``int64``) from buffer ``pbytes`` + ## and store it to ``outval``. + ## + ## On success ``outlen`` will be set to number of bytes processed while + ## decoding signed varint. ## ## If array ``pbytes`` is empty, ``Incomplete`` error will be returned. ## @@ -181,15 +232,44 @@ proc getSVarint*(pbytes: openarray[byte], outsize: var int, result = PB.getUVarint(pbytes, outsize, value) if result == VarintStatus.Success: - if (value and cast[type(value)](1)) != cast[type(value)](0): + outval = cast[type(outval)](value) + +proc getSVarint*(pbytes: openarray[byte], outsize: var int, + outval: var PBZigVarint): VarintStatus {.inline.} = + ## Decode Google ProtoBuf's zigzag encoded signed integer (``sint32`` or + ## ``sint64`` ) from buffer ``pbytes`` and store it to ``outval``. + ## + ## On success ``outlen`` will be set to number of bytes processed while + ## decoding signed varint. + ## + ## If array ``pbytes`` is empty, ``Incomplete`` error will be returned. + ## + ## If there not enough bytes available in array ``pbytes`` to decode `signed + ## varint`, ``Incomplete`` error will be returned. + ## + ## If encoded value can produce integer overflow, ``Overflow`` error will be + ## returned. + ## + ## Note, when decoding 10th byte of 64bit integer only 1 bit from byte will be + ## decoded, all other bits will be ignored. When decoding 5th byte of 32bit + ## integer only 4 bits from byte will be decoded, all other bits will be + ## ignored. + when sizeof(outval) == 8: + var value: uint64 + else: + var value: uint32 + + result = PB.getUVarint(pbytes, outsize, value) + if result == VarintStatus.Success: + if (value and type(value)(1)) != type(value)(0): outval = cast[type(outval)](not(value shr 1)) else: outval = cast[type(outval)](value shr 1) proc putSVarint*(pbytes: var openarray[byte], outsize: var int, - outval: PBSomeSVarint): VarintStatus {.inline.} = - ## Encode Google ProtoBuf's `signed varint` ``outval`` and store it to array - ## ``pbytes``. + outval: PBZigVarint): VarintStatus {.inline.} = + ## Encode signed integer ``outval`` using ProtoBuffer's zigzag encoding + ## (``sint32`` or ``sint64``) and store it to array ``pbytes``. ## ## On success ``outlen`` will hold number of bytes (octets) used to encode ## unsigned integer ``v``. @@ -202,18 +282,76 @@ proc putSVarint*(pbytes: var openarray[byte], outsize: var int, ## Maximum encoded length of 32bit integer is 5 octets. when sizeof(outval) == 8: var value: uint64 = - if outval < 0: - not(cast[uint64](outval) shl 1) + if int64(outval) < 0'i64: + not(uint64(outval) shl 1) else: - cast[uint64](outval) shl 1 + uint64(outval) shl 1 else: var value: uint32 = - if outval < 0: - not(cast[uint32](outval) shl 1) + if int32(outval) < 0'i32: + not(uint32(outval) shl 1) else: - cast[uint32](outval) shl 1 + uint32(outval) shl 1 result = PB.putUVarint(pbytes, outsize, value) +proc putSVarint*(pbytes: var openarray[byte], outsize: var int, + outval: PBSomeSVarint): VarintStatus {.inline.} = + ## Encode signed integer ``outval`` (``int32`` or ``int64``) and store it to + ## array ``pbytes``. + ## + ## On success ``outlen`` will hold number of bytes (octets) used to encode + ## unsigned integer ``v``. + ## + ## If there not enough bytes available in buffer ``pbytes``, ``Incomplete`` + ## error will be returned and ``outlen`` will be set to number of bytes + ## required. + ## + ## Maximum encoded length of 64bit integer is 10 octets. + ## Maximum encoded length of 32bit integer is 5 octets. + when sizeof(outval) == 8: + result = PB.putUVarint(pbytes, outsize, uint64(outval)) + else: + result = PB.putUVarint(pbytes, outsize, uint32(outval)) + +template varintFatal(msg) = + const m = msg + {.fatal: m.} + +proc putVarint*[T: PB|LP](vtype: typedesc[T], pbytes: var openarray[byte], + nbytes: var int, value: SomeVarint): VarintStatus {.inline.} = + when vtype is PB: + when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): + result = putSVarint(pbytes, nbytes, value) + elif (type(value) is PBSomeUVarint): + result = PB.putUVarint(pbytes, nbytes, value) + else: + varintFatal("Protobuf's varint do not support type [" & + typetraits.name(type(value)) & "]") + elif vtype is LP: + when (type(value) is LPSomeVarint): + result = LP.putUVarint(pbytes, nbytes, value) + else: + varintFatal("LibP2P's varint do not support type [" & + typetraits.name(type(value)) & "]") + +proc getVarint*[T: PB|LP](vtype: typedesc[T], pbytes: openarray[byte], + nbytes: var int, + value: var SomeVarint): VarintStatus {.inline.} = + when vtype is PB: + when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): + result = getSVarint(pbytes, nbytes, value) + elif (type(value) is PBSomeUVarint): + result = PB.getUVarint(pbytes, nbytes, value) + else: + varintFatal("Protobuf's varint do not support type [" & + typetraits.name(type(value)) & "]") + elif vtype is LP: + when (type(value) is LPSomeVarint): + result = LP.getUVarint(pbytes, nbytes, value) + else: + varintFatal("LibP2P's varint do not support type [" & + typetraits.name(type(value)) & "]") + proc encodeVarint*(vtype: typedesc[PB], value: PBSomeVarint): seq[byte] {.inline.} = ## Encode integer to Google ProtoBuf's `signed/unsigned varint` and returns @@ -224,7 +362,7 @@ proc encodeVarint*(vtype: typedesc[PB], result.setLen(5) else: result.setLen(10) - when type(value) is PBSomeSVarint: + when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): let res = putSVarint(result, outsize, value) else: let res = PB.putUVarint(result, outsize, value) diff --git a/libp2p/vbuffer.nim b/libp2p/vbuffer.nim index 69e268b4a..585a12a13 100644 --- a/libp2p/vbuffer.nim +++ b/libp2p/vbuffer.nim @@ -74,14 +74,14 @@ proc writeLPVarint*(vb: var VBuffer, value: LPSomeUVarint) = doAssert(res == VarintStatus.Success) vb.offset += length -proc writeVarint*(vb: var VBuffer, value: LPSomeUVarint) = +proc writeVarint*(vb: var VBuffer, value: LPSomeUVarint) = writeLPVarint(vb, value) proc writeSeq*[T: byte|char](vb: var VBuffer, value: openarray[T]) = ## Write array ``value`` to buffer ``vb``, value will be prefixed with ## varint length of the array. var length = 0 - vb.buffer.setLen(len(vb.buffer) + vsizeof(len(value)) + len(value)) + vb.buffer.setLen(len(vb.buffer) + vsizeof(uint(len(value))) + len(value)) let res = LP.putUVarint(toOpenArray(vb.buffer, vb.offset, len(vb.buffer) - 1), length, uint(len(value))) doAssert(res == VarintStatus.Success) diff --git a/tests/testinterop.nim b/tests/testinterop.nim index 6d1ed765e..e447cdd6f 100644 --- a/tests/testinterop.nim +++ b/tests/testinterop.nim @@ -232,6 +232,14 @@ suite "Interop": proc runTests(): Future[bool] {.async.} = var protos = @["/test-stream"] var test = "TEST STRING" + # We are preparing expect string, which should be prefixed with varint + # length and do not have `\r\n` suffix, because we going to use + # readLine(). + var buffer = initVBuffer() + buffer.writeSeq(test & "\r\n") + buffer.finish() + var expect = newString(len(buffer) - 2) + copyMem(addr expect[0], addr buffer.buffer[0], len(expect)) let nativeNode = createNode() let awaiters = await nativeNode.start() @@ -241,8 +249,10 @@ suite "Interop": var testFuture = newFuture[string]("test.future") proc daemonHandler(api: DaemonAPI, stream: P2PStream) {.async.} = + # We should perform `readLp()` instead of `readLine()`. `readLine()` + # here reads actually length prefixed string. var line = await stream.transp.readLine() - check line == test + check line == expect testFuture.complete(line) await daemonNode.addHandler(protos, daemonHandler) @@ -250,7 +260,7 @@ suite "Interop": daemonPeer.addresses), protos[0]) await conn.writeLp(test & "\r\n") - result = test == (await wait(testFuture, 10.secs)) + result = expect == (await wait(testFuture, 10.secs)) await nativeNode.stop() await allFutures(awaiters) await daemonNode.close() diff --git a/tests/testvarint.nim b/tests/testvarint.nim index 15caa0a3a..144e26dc5 100644 --- a/tests/testvarint.nim +++ b/tests/testvarint.nim @@ -16,6 +16,84 @@ const PBedgeValues = [ (1'u64 shl 63), 0xFFFF_FFFF_FFFF_FFFF'u64 ] +const PBPositiveSignedEdgeValues = [ + 0'u64, 0x3F'u64, + 0x40'u64, 0x1FFF'u64, + 0x2000'u64, 0xFFFFF'u64, + 0x100000'u64, 0x7FFFFFF'u64, + 0x8000000'u64, 0x3FFFFFFFF'u64, + 0x400000000'u64, 0x1FFFFFFFFFF'u64, + 0x20000000000'u64, 0xFFFFFFFFFFFF'u64, + 0x1000000000000'u64, 0x7FFFFFFFFFFFFF'u64, + 0x80000000000000'u64, 0x3FFFFFFFFFFFFFFF'u64, + 0x4000000000000000'u64, 0x7FFFFFFFFFFFFFFF'u64 +] + +const PBNegativeSignedEdgeValues = [ + 0x0000000000000000'u64, 0xFFFFFFFFFFFFFFC0'u64, + 0xFFFFFFFFFFFFFFBF'u64, 0xFFFFFFFFFFFFE000'u64, + 0xFFFFFFFFFFFFDFFF'u64, 0xFFFFFFFFFFF00000'u64, + 0xFFFFFFFFFFEFFFFF'u64, 0xFFFFFFFFF8000000'u64, + 0xFFFFFFFFF7FFFFFF'u64, 0xFFFFFFFC00000000'u64, + 0xFFFFFFFBFFFFFFFF'u64, 0xFFFFFE0000000000'u64, + 0xFFFFFDFFFFFFFFFF'u64, 0xFFFF000000000000'u64, + 0xFFFEFFFFFFFFFFFF'u64, 0xFF80000000000000'u64, + 0xFF7FFFFFFFFFFFFF'u64, 0xC000000000000000'u64, + 0xBFFFFFFFFFFFFFFF'u64, 0x8000000000000000'u64 +] + +const PBPositiveSignedZigZagEdgeExpects = [ + "00", "7E", + "8001", "FE7F", + "808001", "FEFF7F", + "80808001", "FEFFFF7F", + "8080808001", "FEFFFFFF7F", + "808080808001", "FEFFFFFFFF7F", + "80808080808001", "FEFFFFFFFFFF7F", + "8080808080808001", "FEFFFFFFFFFFFF7F", + "808080808080808001", "FEFFFFFFFFFFFFFF7F", + "80808080808080808001", "FEFFFFFFFFFFFFFFFF01" +] + +const PBNegativeSignedZigZagEdgeExpects = [ + "00", "7F", + "8101", "FF7F", + "818001", "FFFF7F", + "81808001", "FFFFFF7F", + "8180808001", "FFFFFFFF7F", + "818080808001", "FFFFFFFFFF7F", + "81808080808001", "FFFFFFFFFFFF7F", + "8180808080808001", "FFFFFFFFFFFFFF7F", + "818080808080808001", "FFFFFFFFFFFFFFFF7F", + "81808080808080808001", "FFFFFFFFFFFFFFFFFF01", +] + +const PBPositiveSignedEdgeExpects = [ + "00", "3F", + "40", "FF3F", + "8040", "FFFF3F", + "808040", "FFFFFF3F", + "80808040", "FFFFFFFF3F", + "8080808040", "FFFFFFFFFF3F", + "808080808040", "FFFFFFFFFFFF3F", + "80808080808040", "FFFFFFFFFFFFFF3F", + "8080808080808040", "FFFFFFFFFFFFFFFF3F", + "808080808080808040", "FFFFFFFFFFFFFFFF7F" +] + +const PBNegativeSignedEdgeExpects = [ + "00", "C0FFFFFFFFFFFFFFFF01", + "BFFFFFFFFFFFFFFFFF01", "80C0FFFFFFFFFFFFFF01", + "FFBFFFFFFFFFFFFFFF01", "8080C0FFFFFFFFFFFF01", + "FFFFBFFFFFFFFFFFFF01", "808080C0FFFFFFFFFF01", + "FFFFFFBFFFFFFFFFFF01", "80808080C0FFFFFFFF01", + "FFFFFFFFBFFFFFFFFF01", "8080808080C0FFFFFF01", + "FFFFFFFFFFBFFFFFFF01", "808080808080C0FFFF01", + "FFFFFFFFFFFFBFFFFF01", "80808080808080C0FF01", + "FFFFFFFFFFFFFFBFFF01", "8080808080808080C001", + "FFFFFFFFFFFFFFFFBF01", "80808080808080808001" +] + const PBedgeExpects = [ "00", "7F", "8001", "FF7F", @@ -33,6 +111,22 @@ const PBedgeSizes = [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10 ] +const PBEdgeSignedPositiveZigZagSizes = [ + 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10 +] + +const PBEdgeSignedNegativeZigZagSizes = [ + 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10 +] + +const PBEdgeSignedPositiveSizes = [ + 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9 +] + +const PBEdgeSignedNegativeSizes = [ + 1, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 +] + const LPedgeValues = [ 0'u64, (1'u64 shl 7) - 1'u64, (1'u64 shl 7), (1'u64 shl 14) - 1'u64, @@ -85,21 +179,72 @@ proc toHex*(a: openarray[byte], lowercase: bool = false): string = suite "Variable integer test suite": test "vsizeof() edge cases test": - for i in 0..