2018-11-19 02:52:11 +00:00
|
|
|
## Nim-Libp2p
|
|
|
|
## Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
## Licensed under either of
|
|
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
## at your option.
|
|
|
|
## This file may not be copied, modified, or distributed except according to
|
|
|
|
## those terms.
|
|
|
|
|
|
|
|
## This module implements minimal Google's ProtoBuf primitives.
|
2020-05-31 14:22:49 +00:00
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
import ../varint, stew/endians2
|
2018-11-19 02:52:11 +00:00
|
|
|
|
|
|
|
const
|
|
|
|
MaxMessageSize* = 1'u shl 22
|
|
|
|
|
|
|
|
type
|
|
|
|
ProtoFieldKind* = enum
|
|
|
|
## Protobuf's field types enum
|
|
|
|
Varint, Fixed64, Length, StartGroup, EndGroup, Fixed32
|
|
|
|
|
|
|
|
ProtoFlags* = enum
|
|
|
|
## Protobuf's encoding types
|
2019-09-09 16:57:17 +00:00
|
|
|
WithVarintLength, WithUint32BeLength, WithUint32LeLength
|
2018-11-19 02:52:11 +00:00
|
|
|
|
|
|
|
ProtoBuffer* = object
|
|
|
|
## Protobuf's message representation object
|
|
|
|
options: set[ProtoFlags]
|
|
|
|
buffer*: seq[byte]
|
|
|
|
offset*: int
|
|
|
|
length*: int
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
ProtoHeader* = object
|
|
|
|
wire*: ProtoFieldKind
|
|
|
|
index*: uint64
|
|
|
|
|
2018-11-19 02:52:11 +00:00
|
|
|
ProtoField* = object
|
|
|
|
## Protobuf's message field representation object
|
2020-07-13 12:43:07 +00:00
|
|
|
index*: int
|
|
|
|
case kind*: ProtoFieldKind
|
2018-11-19 02:52:11 +00:00
|
|
|
of Varint:
|
|
|
|
vint*: uint64
|
|
|
|
of Fixed64:
|
|
|
|
vfloat64*: float64
|
|
|
|
of Length:
|
|
|
|
vbuffer*: seq[byte]
|
|
|
|
of Fixed32:
|
|
|
|
vfloat32*: float32
|
|
|
|
of StartGroup, EndGroup:
|
|
|
|
discard
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
ProtoResult {.pure.} = enum
|
|
|
|
VarintDecodeError,
|
|
|
|
MessageIncompleteError,
|
|
|
|
BufferOverflowError,
|
|
|
|
MessageSizeTooBigError,
|
|
|
|
NoError
|
|
|
|
|
|
|
|
ProtoScalar* = uint | uint32 | uint64 | zint | zint32 | zint64 |
|
|
|
|
hint | hint32 | hint64 | float32 | float64
|
|
|
|
|
|
|
|
const
|
|
|
|
SupportedWireTypes* = {
|
|
|
|
int(ProtoFieldKind.Varint),
|
|
|
|
int(ProtoFieldKind.Fixed64),
|
|
|
|
int(ProtoFieldKind.Length),
|
|
|
|
int(ProtoFieldKind.Fixed32)
|
|
|
|
}
|
|
|
|
|
|
|
|
template checkFieldNumber*(i: int) =
|
|
|
|
doAssert((i > 0 and i < (1 shl 29)) and not(i >= 19000 and i <= 19999),
|
|
|
|
"Incorrect or reserved field number")
|
|
|
|
|
|
|
|
template getProtoHeader*(index: int, wire: ProtoFieldKind): uint64 =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get protobuf's field header integer for ``index`` and ``wire``.
|
2020-07-13 12:43:07 +00:00
|
|
|
((uint64(index) shl 3) or uint64(wire))
|
2018-11-19 02:52:11 +00:00
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
template getProtoHeader*(field: ProtoField): uint64 =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get protobuf's field header integer for ``field``.
|
2020-07-13 12:43:07 +00:00
|
|
|
((uint64(field.index) shl 3) or uint64(field.kind))
|
2018-11-19 02:52:11 +00:00
|
|
|
|
|
|
|
template toOpenArray*(pb: ProtoBuffer): untyped =
|
|
|
|
toOpenArray(pb.buffer, pb.offset, len(pb.buffer) - 1)
|
|
|
|
|
|
|
|
template isEmpty*(pb: ProtoBuffer): bool =
|
|
|
|
len(pb.buffer) - pb.offset <= 0
|
|
|
|
|
|
|
|
template isEnough*(pb: ProtoBuffer, length: int): bool =
|
|
|
|
len(pb.buffer) - pb.offset - length >= 0
|
|
|
|
|
|
|
|
template getPtr*(pb: ProtoBuffer): pointer =
|
|
|
|
cast[pointer](unsafeAddr pb.buffer[pb.offset])
|
|
|
|
|
|
|
|
template getLen*(pb: ProtoBuffer): int =
|
|
|
|
len(pb.buffer) - pb.offset
|
|
|
|
|
|
|
|
proc vsizeof*(field: ProtoField): int {.inline.} =
|
|
|
|
## Returns number of bytes required to store protobuf's field ``field``.
|
|
|
|
case field.kind
|
|
|
|
of ProtoFieldKind.Varint:
|
2020-07-13 12:43:07 +00:00
|
|
|
vsizeof(getProtoHeader(field)) + vsizeof(field.vint)
|
2018-11-19 02:52:11 +00:00
|
|
|
of ProtoFieldKind.Fixed64:
|
2020-07-13 12:43:07 +00:00
|
|
|
vsizeof(getProtoHeader(field)) + sizeof(field.vfloat64)
|
2018-11-19 02:52:11 +00:00
|
|
|
of ProtoFieldKind.Fixed32:
|
2020-07-13 12:43:07 +00:00
|
|
|
vsizeof(getProtoHeader(field)) + sizeof(field.vfloat32)
|
2018-11-19 02:52:11 +00:00
|
|
|
of ProtoFieldKind.Length:
|
2020-07-13 12:43:07 +00:00
|
|
|
vsizeof(getProtoHeader(field)) + vsizeof(uint64(len(field.vbuffer))) +
|
|
|
|
len(field.vbuffer)
|
2018-11-19 02:52:11 +00:00
|
|
|
else:
|
2020-07-13 12:43:07 +00:00
|
|
|
0
|
2018-11-19 02:52:11 +00:00
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoField*(index: int, value: SomeVarint): ProtoField {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Initialize ProtoField with integer value.
|
|
|
|
result = ProtoField(kind: Varint, index: index)
|
|
|
|
when type(value) is uint64:
|
|
|
|
result.vint = value
|
|
|
|
else:
|
|
|
|
result.vint = cast[uint64](value)
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoField*(index: int, value: bool): ProtoField {.deprecated.} =
|
2019-09-11 19:06:50 +00:00
|
|
|
## Initialize ProtoField with integer value.
|
|
|
|
result = ProtoField(kind: Varint, index: index)
|
|
|
|
result.vint = byte(value)
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoField*(index: int,
|
|
|
|
value: openarray[byte]): ProtoField {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Initialize ProtoField with bytes array.
|
|
|
|
result = ProtoField(kind: Length, index: index)
|
|
|
|
if len(value) > 0:
|
|
|
|
result.vbuffer = newSeq[byte](len(value))
|
|
|
|
copyMem(addr result.vbuffer[0], unsafeAddr value[0], len(value))
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoField*(index: int, value: string): ProtoField {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Initialize ProtoField with string.
|
|
|
|
result = ProtoField(kind: Length, index: index)
|
|
|
|
if len(value) > 0:
|
|
|
|
result.vbuffer = newSeq[byte](len(value))
|
|
|
|
copyMem(addr result.vbuffer[0], unsafeAddr value[0], len(value))
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoField*(index: int,
|
|
|
|
value: ProtoBuffer): ProtoField {.deprecated, inline.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Initialize ProtoField with nested message stored in ``value``.
|
|
|
|
##
|
|
|
|
## Note: This procedure performs shallow copy of ``value`` sequence.
|
|
|
|
result = ProtoField(kind: Length, index: index)
|
|
|
|
if len(value.buffer) > 0:
|
|
|
|
shallowCopy(result.vbuffer, value.buffer)
|
|
|
|
|
|
|
|
proc initProtoBuffer*(data: seq[byte], offset = 0,
|
|
|
|
options: set[ProtoFlags] = {}): ProtoBuffer =
|
|
|
|
## Initialize ProtoBuffer with shallow copy of ``data``.
|
|
|
|
shallowCopy(result.buffer, data)
|
|
|
|
result.offset = offset
|
|
|
|
result.options = options
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc initProtoBuffer*(data: openarray[byte], offset = 0,
|
|
|
|
options: set[ProtoFlags] = {}): ProtoBuffer =
|
|
|
|
## Initialize ProtoBuffer with copy of ``data``.
|
|
|
|
result.buffer = @data
|
|
|
|
result.offset = offset
|
|
|
|
result.options = options
|
|
|
|
|
2018-11-19 02:52:11 +00:00
|
|
|
proc initProtoBuffer*(options: set[ProtoFlags] = {}): ProtoBuffer =
|
|
|
|
## Initialize ProtoBuffer with new sequence of capacity ``cap``.
|
|
|
|
result.buffer = newSeqOfCap[byte](128)
|
|
|
|
result.options = options
|
|
|
|
if WithVarintLength in options:
|
|
|
|
# Our buffer will start from position 10, so we can store length of buffer
|
|
|
|
# in [0, 9].
|
|
|
|
result.buffer.setLen(10)
|
|
|
|
result.offset = 10
|
2019-09-09 16:57:17 +00:00
|
|
|
elif {WithUint32LeLength, WithUint32BeLength} * options != {}:
|
|
|
|
# Our buffer will start from position 4, so we can store length of buffer
|
2020-07-13 12:43:07 +00:00
|
|
|
# in [0, 3].
|
2019-09-09 16:57:17 +00:00
|
|
|
result.buffer.setLen(4)
|
|
|
|
result.offset = 4
|
2018-11-19 02:52:11 +00:00
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc write*[T: ProtoScalar](pb: var ProtoBuffer,
|
|
|
|
field: int, value: T) =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var length = 0
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint) or
|
|
|
|
(T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
let flength = vsizeof(getProtoHeader(field, ProtoFieldKind.Varint)) +
|
|
|
|
vsizeof(value)
|
|
|
|
let header = ProtoFieldKind.Varint
|
|
|
|
elif T is float32:
|
|
|
|
let flength = vsizeof(getProtoHeader(field, ProtoFieldKind.Fixed32)) +
|
|
|
|
sizeof(T)
|
|
|
|
let header = ProtoFieldKind.Fixed32
|
|
|
|
elif T is float64:
|
|
|
|
let flength = vsizeof(getProtoHeader(field, ProtoFieldKind.Fixed64)) +
|
|
|
|
sizeof(T)
|
|
|
|
let header = ProtoFieldKind.Fixed64
|
|
|
|
|
|
|
|
pb.buffer.setLen(len(pb.buffer) + flength)
|
|
|
|
|
|
|
|
let hres = PB.putUVarint(pb.toOpenArray(), length,
|
|
|
|
getProtoHeader(field, header))
|
|
|
|
doAssert(hres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint):
|
|
|
|
let vres = PB.putUVarint(pb.toOpenArray(), length, value)
|
|
|
|
doAssert(vres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
elif (T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
let vres = putSVarint(pb.toOpenArray(), length, value)
|
|
|
|
doAssert(vres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
elif T is float32:
|
|
|
|
doAssert(pb.isEnough(sizeof(T)))
|
|
|
|
let u32 = cast[uint32](value)
|
|
|
|
pb.buffer[pb.offset ..< pb.offset + sizeof(T)] = u32.toBytesLE()
|
|
|
|
pb.offset += sizeof(T)
|
|
|
|
elif T is float64:
|
|
|
|
doAssert(pb.isEnough(sizeof(T)))
|
|
|
|
let u64 = cast[uint64](value)
|
|
|
|
pb.buffer[pb.offset ..< pb.offset + sizeof(T)] = u64.toBytesLE()
|
|
|
|
pb.offset += sizeof(T)
|
|
|
|
|
|
|
|
proc writePacked*[T: ProtoScalar](pb: var ProtoBuffer, field: int,
|
|
|
|
value: openarray[T]) =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var length = 0
|
|
|
|
let dlength =
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint) or
|
|
|
|
(T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
var res = 0
|
|
|
|
for item in value:
|
|
|
|
res += vsizeof(item)
|
|
|
|
res
|
|
|
|
elif (T is float32) or (T is float64):
|
|
|
|
len(value) * sizeof(T)
|
|
|
|
|
|
|
|
let header = getProtoHeader(field, ProtoFieldKind.Length)
|
|
|
|
let flength = vsizeof(header) + vsizeof(uint64(dlength)) + dlength
|
|
|
|
pb.buffer.setLen(len(pb.buffer) + flength)
|
|
|
|
let hres = PB.putUVarint(pb.toOpenArray(), length, header)
|
|
|
|
doAssert(hres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
length = 0
|
|
|
|
let lres = PB.putUVarint(pb.toOpenArray(), length, uint64(dlength))
|
|
|
|
doAssert(lres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
for item in value:
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint):
|
|
|
|
length = 0
|
|
|
|
let vres = PB.putUVarint(pb.toOpenArray(), length, item)
|
|
|
|
doAssert(vres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
elif (T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
length = 0
|
|
|
|
let vres = PB.putSVarint(pb.toOpenArray(), length, item)
|
|
|
|
doAssert(vres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
elif T is float32:
|
|
|
|
doAssert(pb.isEnough(sizeof(T)))
|
|
|
|
let u32 = cast[uint32](item)
|
|
|
|
pb.buffer[pb.offset ..< pb.offset + sizeof(T)] = u32.toBytesLE()
|
|
|
|
pb.offset += sizeof(T)
|
|
|
|
elif T is float64:
|
|
|
|
doAssert(pb.isEnough(sizeof(T)))
|
|
|
|
let u64 = cast[uint64](item)
|
|
|
|
pb.buffer[pb.offset ..< pb.offset + sizeof(T)] = u64.toBytesLE()
|
|
|
|
pb.offset += sizeof(T)
|
|
|
|
|
|
|
|
proc write*[T: byte|char](pb: var ProtoBuffer, field: int,
|
|
|
|
value: openarray[T]) =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var length = 0
|
|
|
|
let flength = vsizeof(getProtoHeader(field, ProtoFieldKind.Length)) +
|
|
|
|
vsizeof(uint64(len(value))) + len(value)
|
|
|
|
pb.buffer.setLen(len(pb.buffer) + flength)
|
|
|
|
let hres = PB.putUVarint(pb.toOpenArray(), length,
|
|
|
|
getProtoHeader(field, ProtoFieldKind.Length))
|
|
|
|
doAssert(hres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
let lres = PB.putUVarint(pb.toOpenArray(), length,
|
|
|
|
uint64(len(value)))
|
|
|
|
doAssert(lres.isOk())
|
|
|
|
pb.offset += length
|
|
|
|
if len(value) > 0:
|
|
|
|
doAssert(pb.isEnough(len(value)))
|
|
|
|
copyMem(addr pb.buffer[pb.offset], unsafeAddr value[0], len(value))
|
|
|
|
pb.offset += len(value)
|
|
|
|
|
|
|
|
proc write*(pb: var ProtoBuffer, field: int, value: ProtoBuffer) {.inline.} =
|
|
|
|
## Encode Protobuf's sub-message ``value`` and store it to protobuf's buffer
|
|
|
|
## ``pb`` with field number ``field``.
|
|
|
|
write(pb, field, value.buffer)
|
|
|
|
|
|
|
|
proc write*(pb: var ProtoBuffer, field: ProtoField) {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Encode protobuf's field ``field`` and store it to protobuf's buffer ``pb``.
|
|
|
|
var length = 0
|
2020-05-31 14:22:49 +00:00
|
|
|
var res: VarintResult[void]
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.buffer.setLen(len(pb.buffer) + vsizeof(field))
|
2020-07-13 12:43:07 +00:00
|
|
|
res = PB.putUVarint(pb.toOpenArray(), length, getProtoHeader(field))
|
2020-05-31 14:22:49 +00:00
|
|
|
doAssert(res.isOk())
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += length
|
|
|
|
case field.kind
|
|
|
|
of ProtoFieldKind.Varint:
|
2018-11-20 21:39:35 +00:00
|
|
|
res = PB.putUVarint(pb.toOpenArray(), length, field.vint)
|
2020-05-31 14:22:49 +00:00
|
|
|
doAssert(res.isOk())
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += length
|
|
|
|
of ProtoFieldKind.Fixed64:
|
2019-03-14 02:55:47 +00:00
|
|
|
doAssert(pb.isEnough(8))
|
2018-11-19 02:52:11 +00:00
|
|
|
var value = cast[uint64](field.vfloat64)
|
|
|
|
pb.buffer[pb.offset] = byte(value and 0xFF'u32)
|
2019-10-29 18:51:48 +00:00
|
|
|
pb.buffer[pb.offset + 1] = byte((value shr 8) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 2] = byte((value shr 16) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 3] = byte((value shr 24) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 4] = byte((value shr 32) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 5] = byte((value shr 40) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 6] = byte((value shr 48) and 0xFF'u64)
|
|
|
|
pb.buffer[pb.offset + 7] = byte((value shr 56) and 0xFF'u64)
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += 8
|
|
|
|
of ProtoFieldKind.Fixed32:
|
2019-03-14 02:55:47 +00:00
|
|
|
doAssert(pb.isEnough(4))
|
2018-11-19 02:52:11 +00:00
|
|
|
var value = cast[uint32](field.vfloat32)
|
|
|
|
pb.buffer[pb.offset] = byte(value and 0xFF'u32)
|
|
|
|
pb.buffer[pb.offset + 1] = byte((value shr 8) and 0xFF'u32)
|
|
|
|
pb.buffer[pb.offset + 2] = byte((value shr 16) and 0xFF'u32)
|
|
|
|
pb.buffer[pb.offset + 3] = byte((value shr 24) and 0xFF'u32)
|
|
|
|
pb.offset += 4
|
|
|
|
of ProtoFieldKind.Length:
|
2018-11-20 21:39:35 +00:00
|
|
|
res = PB.putUVarint(pb.toOpenArray(), length, uint(len(field.vbuffer)))
|
2020-05-31 14:22:49 +00:00
|
|
|
doAssert(res.isOk())
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += length
|
2019-03-14 02:55:47 +00:00
|
|
|
doAssert(pb.isEnough(len(field.vbuffer)))
|
2018-11-22 14:12:46 +00:00
|
|
|
if len(field.vbuffer) > 0:
|
|
|
|
copyMem(addr pb.buffer[pb.offset], unsafeAddr field.vbuffer[0],
|
|
|
|
len(field.vbuffer))
|
|
|
|
pb.offset += len(field.vbuffer)
|
2018-11-19 02:52:11 +00:00
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
proc finish*(pb: var ProtoBuffer) =
|
|
|
|
## Prepare protobuf's buffer ``pb`` for writing to stream.
|
2019-03-14 02:55:47 +00:00
|
|
|
doAssert(len(pb.buffer) > 0)
|
2018-11-19 02:52:11 +00:00
|
|
|
if WithVarintLength in pb.options:
|
|
|
|
let size = uint(len(pb.buffer) - 10)
|
2019-03-04 18:23:17 +00:00
|
|
|
let pos = 10 - vsizeof(size)
|
|
|
|
var usedBytes = 0
|
|
|
|
let res = PB.putUVarint(pb.buffer.toOpenArray(pos, 9), usedBytes, size)
|
2020-05-31 14:22:49 +00:00
|
|
|
doAssert(res.isOk())
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset = pos
|
2019-09-09 16:57:17 +00:00
|
|
|
elif WithUint32BeLength in pb.options:
|
|
|
|
let size = uint(len(pb.buffer) - 4)
|
2020-07-13 12:43:07 +00:00
|
|
|
pb.buffer[0 ..< 4] = toBytesBE(uint32(size))
|
2019-09-09 16:57:17 +00:00
|
|
|
pb.offset = 4
|
|
|
|
elif WithUint32LeLength in pb.options:
|
|
|
|
let size = uint(len(pb.buffer) - 4)
|
2020-07-13 12:43:07 +00:00
|
|
|
pb.buffer[0 ..< 4] = toBytesLE(uint32(size))
|
2019-09-09 16:57:17 +00:00
|
|
|
pb.offset = 4
|
2018-11-19 02:52:11 +00:00
|
|
|
else:
|
|
|
|
pb.offset = 0
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc getHeader(data: var ProtoBuffer, header: var ProtoHeader): bool =
|
|
|
|
var length = 0
|
|
|
|
var hdr = 0'u64
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, hdr).isOk():
|
|
|
|
let index = uint64(hdr shr 3)
|
|
|
|
let wire = hdr and 0x07
|
|
|
|
if wire in SupportedWireTypes:
|
|
|
|
data.offset += length
|
|
|
|
header = ProtoHeader(index: index, wire: cast[ProtoFieldKind](wire))
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc skipValue(data: var ProtoBuffer, header: ProtoHeader): bool =
|
|
|
|
case header.wire
|
|
|
|
of ProtoFieldKind.Varint:
|
|
|
|
var length = 0
|
|
|
|
var value = 0'u64
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, value).isOk():
|
|
|
|
data.offset += length
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
of ProtoFieldKind.Fixed32:
|
|
|
|
if data.isEnough(sizeof(uint32)):
|
|
|
|
data.offset += sizeof(uint32)
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
of ProtoFieldKind.Fixed64:
|
|
|
|
if data.isEnough(sizeof(uint64)):
|
|
|
|
data.offset += sizeof(uint64)
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
of ProtoFieldKind.Length:
|
|
|
|
var length = 0
|
|
|
|
var bsize = 0'u64
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
|
|
|
data.offset += length
|
|
|
|
if bsize <= uint64(MaxMessageSize):
|
|
|
|
if data.isEnough(int(bsize)):
|
|
|
|
data.offset += int(bsize)
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
of ProtoFieldKind.StartGroup, ProtoFieldKind.EndGroup:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc getValue[T: ProtoScalar](data: var ProtoBuffer,
|
|
|
|
header: ProtoHeader,
|
|
|
|
outval: var T): ProtoResult =
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint):
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Varint)
|
|
|
|
var length = 0
|
|
|
|
var value = T(0)
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, value).isOk():
|
|
|
|
data.offset += length
|
|
|
|
outval = value
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
ProtoResult.VarintDecodeError
|
|
|
|
elif (T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Varint)
|
|
|
|
var length = 0
|
|
|
|
var value = T(0)
|
|
|
|
if getSVarint(data.toOpenArray(), length, value).isOk():
|
|
|
|
data.offset += length
|
|
|
|
outval = value
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
ProtoResult.VarintDecodeError
|
|
|
|
elif T is float32:
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Fixed32)
|
|
|
|
if data.isEnough(sizeof(float32)):
|
|
|
|
outval = cast[float32](fromBytesLE(uint32, data.toOpenArray()))
|
|
|
|
data.offset += sizeof(float32)
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageIncompleteError
|
|
|
|
elif T is float64:
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Fixed64)
|
|
|
|
if data.isEnough(sizeof(float64)):
|
|
|
|
outval = cast[float64](fromBytesLE(uint64, data.toOpenArray()))
|
|
|
|
data.offset += sizeof(float64)
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageIncompleteError
|
|
|
|
|
|
|
|
proc getValue[T:byte|char](data: var ProtoBuffer, header: ProtoHeader,
|
|
|
|
outBytes: var openarray[T],
|
|
|
|
outLength: var int): ProtoResult =
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Length)
|
|
|
|
var length = 0
|
|
|
|
var bsize = 0'u64
|
|
|
|
|
|
|
|
outLength = 0
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
|
|
|
data.offset += length
|
|
|
|
if bsize <= uint64(MaxMessageSize):
|
|
|
|
if data.isEnough(int(bsize)):
|
|
|
|
outLength = int(bsize)
|
|
|
|
if len(outBytes) >= int(bsize):
|
|
|
|
if bsize > 0'u64:
|
|
|
|
copyMem(addr outBytes[0], addr data.buffer[data.offset], int(bsize))
|
|
|
|
data.offset += int(bsize)
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
# Buffer overflow should not be critical failure
|
|
|
|
data.offset += int(bsize)
|
|
|
|
ProtoResult.BufferOverflowError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageIncompleteError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageSizeTooBigError
|
|
|
|
else:
|
|
|
|
ProtoResult.VarintDecodeError
|
|
|
|
|
|
|
|
proc getValue[T:seq[byte]|string](data: var ProtoBuffer, header: ProtoHeader,
|
|
|
|
outBytes: var T): ProtoResult =
|
|
|
|
doAssert(header.wire == ProtoFieldKind.Length)
|
|
|
|
var length = 0
|
|
|
|
var bsize = 0'u64
|
|
|
|
outBytes.setLen(0)
|
|
|
|
|
|
|
|
if PB.getUVarint(data.toOpenArray(), length, bsize).isOk():
|
|
|
|
data.offset += length
|
|
|
|
if bsize <= uint64(MaxMessageSize):
|
|
|
|
if data.isEnough(int(bsize)):
|
|
|
|
outBytes.setLen(bsize)
|
|
|
|
if bsize > 0'u64:
|
|
|
|
copyMem(addr outBytes[0], addr data.buffer[data.offset], int(bsize))
|
|
|
|
data.offset += int(bsize)
|
|
|
|
ProtoResult.NoError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageIncompleteError
|
|
|
|
else:
|
|
|
|
ProtoResult.MessageSizeTooBigError
|
|
|
|
else:
|
|
|
|
ProtoResult.VarintDecodeError
|
|
|
|
|
|
|
|
proc getField*[T: ProtoScalar](data: ProtoBuffer, field: int,
|
|
|
|
output: var T): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var value: T
|
|
|
|
var res = false
|
|
|
|
var pb = data
|
|
|
|
output = T(0)
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
output = T(0)
|
|
|
|
return false
|
|
|
|
let wireCheck =
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint) or
|
|
|
|
(T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
header.wire == ProtoFieldKind.Varint
|
|
|
|
elif T is float32:
|
|
|
|
header.wire == ProtoFieldKind.Fixed32
|
|
|
|
elif T is float64:
|
|
|
|
header.wire == ProtoFieldKind.Fixed64
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if wireCheck:
|
|
|
|
let r = getValue(pb, header, value)
|
|
|
|
case r
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
res = true
|
|
|
|
output = value
|
|
|
|
else:
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
# We are ignoring wire types different from what we expect, because it
|
|
|
|
# is how `protoc` is working.
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output = T(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output = T(0)
|
|
|
|
return false
|
|
|
|
res
|
|
|
|
|
|
|
|
proc getField*[T: byte|char](data: ProtoBuffer, field: int,
|
|
|
|
output: var openarray[T],
|
|
|
|
outlen: var int): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var pb = data
|
|
|
|
var res = false
|
|
|
|
|
|
|
|
outlen = 0
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
if len(output) > 0:
|
|
|
|
zeroMem(addr output[0], len(output))
|
|
|
|
outlen = 0
|
|
|
|
return false
|
|
|
|
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if header.wire == ProtoFieldKind.Length:
|
|
|
|
let r = getValue(pb, header, output, outlen)
|
|
|
|
case r
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
res = true
|
|
|
|
of ProtoResult.BufferOverflowError:
|
|
|
|
# Buffer overflow error is not critical error, we still can get
|
|
|
|
# field values with proper size.
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
if len(output) > 0:
|
|
|
|
zeroMem(addr output[0], len(output))
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
# We are ignoring wire types different from ProtoFieldKind.Length,
|
|
|
|
# because it is how `protoc` is working.
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
if len(output) > 0:
|
|
|
|
zeroMem(addr output[0], len(output))
|
|
|
|
outlen = 0
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
if len(output) > 0:
|
|
|
|
zeroMem(addr output[0], len(output))
|
|
|
|
outlen = 0
|
|
|
|
return false
|
|
|
|
|
|
|
|
res
|
|
|
|
|
|
|
|
proc getField*[T: seq[byte]|string](data: ProtoBuffer, field: int,
|
|
|
|
output: var T): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var res = false
|
|
|
|
var pb = data
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if header.wire == ProtoFieldKind.Length:
|
|
|
|
let r = getValue(pb, header, output)
|
|
|
|
case r
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
res = true
|
|
|
|
of ProtoResult.BufferOverflowError:
|
|
|
|
# Buffer overflow error is not critical error, we still can get
|
|
|
|
# field values with proper size.
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
# We are ignoring wire types different from ProtoFieldKind.Length,
|
|
|
|
# because it is how `protoc` is working.
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
res
|
|
|
|
|
|
|
|
proc getField*(pb: ProtoBuffer, field: int, output: var ProtoBuffer): bool {.
|
|
|
|
inline.} =
|
|
|
|
var buffer: seq[byte]
|
|
|
|
if pb.getField(field, buffer):
|
|
|
|
output = initProtoBuffer(buffer)
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc getRepeatedField*[T: seq[byte]|string](data: ProtoBuffer, field: int,
|
|
|
|
output: var seq[T]): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var pb = data
|
|
|
|
output.setLen(0)
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if header.wire == ProtoFieldKind.Length:
|
|
|
|
var item: T
|
|
|
|
let r = getValue(pb, header, item)
|
|
|
|
case r
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
output.add(item)
|
|
|
|
else:
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if len(output) > 0:
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc getRepeatedField*[T: uint64|float32|float64](data: ProtoBuffer,
|
|
|
|
field: int,
|
|
|
|
output: var seq[T]): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var pb = data
|
|
|
|
output.setLen(0)
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if header.wire in {ProtoFieldKind.Varint, ProtoFieldKind.Fixed32,
|
|
|
|
ProtoFieldKind.Fixed64}:
|
|
|
|
var item: T
|
|
|
|
let r = getValue(pb, header, item)
|
|
|
|
case r
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
output.add(item)
|
|
|
|
else:
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if len(output) > 0:
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc getPackedRepeatedField*[T: ProtoScalar](data: ProtoBuffer, field: int,
|
|
|
|
output: var seq[T]): bool =
|
|
|
|
checkFieldNumber(field)
|
|
|
|
var pb = data
|
|
|
|
output.setLen(0)
|
|
|
|
|
|
|
|
while not(pb.isEmpty()):
|
|
|
|
var header: ProtoHeader
|
|
|
|
if not(pb.getHeader(header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if header.index == uint64(field):
|
|
|
|
if header.wire == ProtoFieldKind.Length:
|
|
|
|
var arritem: seq[byte]
|
|
|
|
let rarr = getValue(pb, header, arritem)
|
|
|
|
case rarr
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
var pbarr = initProtoBuffer(arritem)
|
|
|
|
let itemHeader =
|
|
|
|
when (T is uint64) or (T is uint32) or (T is uint) or
|
|
|
|
(T is zint64) or (T is zint32) or (T is zint) or
|
|
|
|
(T is hint64) or (T is hint32) or (T is hint):
|
|
|
|
ProtoHeader(wire: ProtoFieldKind.Varint)
|
|
|
|
elif T is float32:
|
|
|
|
ProtoHeader(wire: ProtoFieldKind.Fixed32)
|
|
|
|
elif T is float64:
|
|
|
|
ProtoHeader(wire: ProtoFieldKind.Fixed64)
|
|
|
|
while not(pbarr.isEmpty()):
|
|
|
|
var item: T
|
|
|
|
let res = getValue(pbarr, itemHeader, item)
|
|
|
|
case res
|
|
|
|
of ProtoResult.NoError:
|
|
|
|
output.add(item)
|
|
|
|
else:
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
else:
|
|
|
|
if not(skipValue(pb, header)):
|
|
|
|
output.setLen(0)
|
|
|
|
return false
|
|
|
|
|
|
|
|
if len(output) > 0:
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
2018-11-19 02:52:11 +00:00
|
|
|
proc getVarintValue*(data: var ProtoBuffer, field: int,
|
2020-07-13 12:43:07 +00:00
|
|
|
value: var SomeVarint): int {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get value of `Varint` type.
|
|
|
|
var length = 0
|
|
|
|
var header = 0'u64
|
|
|
|
var soffset = data.offset
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
if not data.isEmpty() and PB.getUVarint(data.toOpenArray(),
|
|
|
|
length, header).isOk():
|
2018-11-19 02:52:11 +00:00
|
|
|
data.offset += length
|
2020-07-13 12:43:07 +00:00
|
|
|
if header == getProtoHeader(field, Varint):
|
2018-11-19 02:52:11 +00:00
|
|
|
if not data.isEmpty():
|
|
|
|
when type(value) is int32 or type(value) is int64 or type(value) is int:
|
|
|
|
let res = getSVarint(data.toOpenArray(), length, value)
|
|
|
|
else:
|
2018-11-20 21:39:35 +00:00
|
|
|
let res = PB.getUVarint(data.toOpenArray(), length, value)
|
2020-05-31 14:22:49 +00:00
|
|
|
if res.isOk():
|
2019-02-28 20:29:03 +00:00
|
|
|
data.offset += length
|
|
|
|
result = length
|
|
|
|
return
|
2018-11-19 02:52:11 +00:00
|
|
|
# Restore offset on error
|
|
|
|
data.offset = soffset
|
|
|
|
|
|
|
|
proc getLengthValue*[T: string|seq[byte]](data: var ProtoBuffer, field: int,
|
2020-07-13 12:43:07 +00:00
|
|
|
buffer: var T): int {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get value of `Length` type.
|
|
|
|
var length = 0
|
|
|
|
var header = 0'u64
|
|
|
|
var ssize = 0'u64
|
|
|
|
var soffset = data.offset
|
|
|
|
result = -1
|
|
|
|
buffer.setLen(0)
|
2020-07-13 12:43:07 +00:00
|
|
|
if not data.isEmpty() and PB.getUVarint(data.toOpenArray(),
|
|
|
|
length, header).isOk():
|
2018-11-19 02:52:11 +00:00
|
|
|
data.offset += length
|
2020-07-13 12:43:07 +00:00
|
|
|
if header == getProtoHeader(field, Length):
|
|
|
|
if not data.isEmpty() and PB.getUVarint(data.toOpenArray(),
|
|
|
|
length, ssize).isOk():
|
2018-11-19 02:52:11 +00:00
|
|
|
data.offset += length
|
|
|
|
if ssize <= MaxMessageSize and data.isEnough(int(ssize)):
|
|
|
|
buffer.setLen(ssize)
|
|
|
|
# Protobuf allow zero-length values.
|
|
|
|
if ssize > 0'u64:
|
|
|
|
copyMem(addr buffer[0], addr data.buffer[data.offset], ssize)
|
|
|
|
result = int(ssize)
|
|
|
|
data.offset += int(ssize)
|
|
|
|
return
|
|
|
|
# Restore offset on error
|
|
|
|
data.offset = soffset
|
|
|
|
|
|
|
|
proc getBytes*(data: var ProtoBuffer, field: int,
|
2020-07-13 12:43:07 +00:00
|
|
|
buffer: var seq[byte]): int {.deprecated, inline.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get value of `Length` type as bytes.
|
|
|
|
result = getLengthValue(data, field, buffer)
|
|
|
|
|
|
|
|
proc getString*(data: var ProtoBuffer, field: int,
|
2020-07-13 12:43:07 +00:00
|
|
|
buffer: var string): int {.deprecated, inline.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Get value of `Length` type as string.
|
|
|
|
result = getLengthValue(data, field, buffer)
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc enterSubmessage*(pb: var ProtoBuffer): int {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Processes protobuf's sub-message and adjust internal offset to enter
|
|
|
|
## inside of sub-message. Returns field index of sub-message field or
|
|
|
|
## ``0`` on error.
|
|
|
|
var length = 0
|
|
|
|
var header = 0'u64
|
|
|
|
var msize = 0'u64
|
|
|
|
var soffset = pb.offset
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
if not pb.isEmpty() and PB.getUVarint(pb.toOpenArray(),
|
|
|
|
length, header).isOk():
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += length
|
|
|
|
if (header and 0x07'u64) == cast[uint64](ProtoFieldKind.Length):
|
2020-07-13 12:43:07 +00:00
|
|
|
if not pb.isEmpty() and PB.getUVarint(pb.toOpenArray(),
|
|
|
|
length, msize).isOk():
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += length
|
|
|
|
if msize <= MaxMessageSize and pb.isEnough(int(msize)):
|
|
|
|
pb.length = int(msize)
|
|
|
|
result = int(header shr 3)
|
|
|
|
return
|
|
|
|
# Restore offset on error
|
|
|
|
pb.offset = soffset
|
|
|
|
|
2020-07-13 12:43:07 +00:00
|
|
|
proc skipSubmessage*(pb: var ProtoBuffer) {.deprecated.} =
|
2018-11-19 02:52:11 +00:00
|
|
|
## Skip current protobuf's sub-message and adjust internal offset to the
|
|
|
|
## end of sub-message.
|
2019-03-14 02:55:47 +00:00
|
|
|
doAssert(pb.length != 0)
|
2018-11-19 02:52:11 +00:00
|
|
|
pb.offset += pb.length
|
|
|
|
pb.length = 0
|