Add preliminary encode/decode of varints

This commit is contained in:
Joey Yakimowich-Payne 2020-03-14 20:15:03 -06:00
commit fe9c5fa1f3
2 changed files with 113 additions and 7 deletions

View file

@ -1,7 +1,113 @@
# This is just an example to get you started. A typical library package
# exports the main API in this file. Note that you cannot rename this file
# but you can remove it if you wish.
import faststreams
proc add*(x, y: int): int =
## Adds two files together.
return x + y
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()

View file

@ -11,4 +11,4 @@ skipDirs = @["tests"]
# Dependencies
requires "nim >= 1.0.6"
requires "nim >= 1.0.6", "faststreams"