113 lines
No EOL
3 KiB
Nim
113 lines
No EOL
3 KiB
Nim
import faststreams
|
|
|
|
const
|
|
MaxMessageSize* = 1'u shl 22
|
|
|
|
type
|
|
ProtoBuffer* = ref object
|
|
fieldNum: int
|
|
outstream: OutputStreamVar
|
|
|
|
ProtoWireType* = enum
|
|
## Protobuf's field types enum
|
|
Varint, Fixed64, Length, StartGroup, EndGroup, Fixed32
|
|
|
|
ProtoField* = object
|
|
## Protobuf's message field representation object
|
|
index: int
|
|
case kind: ProtoWireType
|
|
of Varint:
|
|
vint*: uint64
|
|
of Fixed64:
|
|
vfloat64*: float64
|
|
of Length:
|
|
vbuffer*: OutputStreamVar
|
|
of Fixed32:
|
|
vfloat32*: float32
|
|
of StartGroup, EndGroup:
|
|
discard
|
|
|
|
SomeSVarint* = int | int64 | int32 | int16 | int8 | enum
|
|
SomeUVarint* = uint | uint64 | uint32 | uint16 | uint8 | byte | bool
|
|
SomeVarint* = SomeSVarint | SomeUVarint
|
|
|
|
proc newProtoBuffer*(): ProtoBuffer =
|
|
ProtoBuffer(outstream: OutputStream.init(), fieldNum: 1)
|
|
|
|
# Main interface
|
|
proc encode*(): ProtoBuffer =
|
|
discard
|
|
|
|
proc decode*[T](source: ProtoBuffer): T =
|
|
discard
|
|
|
|
template wireType(firstByte: byte): ProtoWireType =
|
|
(firstByte and 0b111).ProtoWireType
|
|
|
|
template fieldNumber(firstByte: byte): uint =
|
|
(firstByte shr 3) and 0b1111
|
|
|
|
template protoHeader*(fieldNum: int, wire: ProtoWireType): byte =
|
|
## Get protobuf's field header integer for ``index`` and ``wire``.
|
|
((cast[uint](fieldNum) shl 3) or cast[uint](wire)).byte
|
|
|
|
proc encodeVarint(stream: OutputStreamVar, fieldNum: int, value: SomeVarint) {.inline.} =
|
|
let header = protoHeader(fieldNum, Varint)
|
|
stream.append header
|
|
|
|
when value is enum:
|
|
var value = cast[type(ord(value))](value)
|
|
elif value is bool:
|
|
var value = cast[byte](value)
|
|
else:
|
|
var value = value
|
|
|
|
when type(value) is SomeSVarint:
|
|
if value < type(value)(0):
|
|
value = not(value shl type(value)(1))
|
|
else:
|
|
value = value shl type(value)(1)
|
|
|
|
while value > type(value)(0b0111_1111):
|
|
stream.append byte((value and 0b0111_1111) or 0b1000_0000)
|
|
value = value shr 7
|
|
stream.append byte(value and 0b1111_1111)
|
|
|
|
proc encode(protobuf: ProtoBuffer, value: SomeVarint) {.inline.} =
|
|
protobuf.outstream.encodeVarint(protobuf.fieldNum, value)
|
|
inc protobuf.fieldNum
|
|
|
|
proc decode[T: SomeVarint](bytes: var seq[byte], ty: typedesc[T], offset = 0): tuple[fieldNum: uint, value: T] {.inline.} =
|
|
# Only up to 128 bits supported by the spec
|
|
assert (bytes.len - 1) <= 16
|
|
|
|
let wireTy = wireType(bytes[offset])
|
|
if wireTy != Varint:
|
|
raise newException(Exception, "Not a varint!")
|
|
|
|
result.fieldNum = fieldNumber(bytes[offset])
|
|
result.value = cast[ty](0)
|
|
var shiftAmount = 0
|
|
var i = offset + 1
|
|
while true:
|
|
result.value += T(bytes[i] and 0b0111_1111) shl shiftAmount
|
|
shiftAmount += 7
|
|
if (bytes[i] shr 7) == 0:
|
|
break
|
|
i += 1
|
|
|
|
when ty is SomeSVarint:
|
|
if (result.value and T(1)) != T(0):
|
|
result.value = cast[T](not(result.value shr T(1)))
|
|
else:
|
|
result.value = cast[T](result.value shr T(1))
|
|
|
|
proc main() =
|
|
let proto = newProtoBuffer()
|
|
proto.encode(-1500000)
|
|
var input: seq[byte] = proto.outstream.getOutput
|
|
echo input
|
|
|
|
echo decode(input, int64)
|
|
|
|
main() |