Compare commits

..

17 commits

Author SHA1 Message Date
Joey Yakimowich-Payne
fbdce1712d Add support for encoding/decoding custom types 2020-04-05 22:27:40 -06:00
Joey Yakimowich-Payne
86c8143567 Add checkType for object 2020-04-05 21:39:21 -06:00
Joey Yakimowich-Payne
5b49b86b23 Refactor decodeField and get* 2020-04-05 21:36:01 -06:00
Joey Yakimowich-Payne
f26b941bd7 Add support for float32 and float64 2020-04-05 14:46:50 -06:00
Joey Yakimowich-Payne
d750cac498 Fix support for char, bool, byte 2020-04-04 08:03:00 -06:00
Joey Yakimowich-Payne
d81f0f0cbe Empty objects should not be encoded 2020-04-04 07:38:14 -06:00
Joey Yakimowich-Payne
e1905e13ce Actually fix out of order decoding 2020-04-03 22:25:16 -06:00
Joey Yakimowich-Payne
ccb729d219 Fix bug with out of order deserialization 2020-04-03 21:17:47 -06:00
Joey Yakimowich-Payne
307cb7f9f3 Fix bug with size detection. Add string to test objects 2020-04-03 19:23:08 -06:00
Joey Yakimowich-Payne
aae968ac3b Add object encoding/decoding 2020-04-03 19:13:11 -06:00
Joey Yakimowich-Payne
2db4d48802 Add object field decoding. Refactor proc names to be inline with behavior 2020-04-03 18:50:00 -06:00
Joey Yakimowich-Payne
5f2219bae7 WIP Object serialization/deserialization 2020-04-03 17:44:50 -06:00
Joey Yakimowich-Payne
c924821d0a Add string encoding/decoding 2020-03-15 18:39:14 -06:00
Joey Yakimowich-Payne
ec0e5a190a Add tests, fix unsigned int 2020-03-14 21:00:19 -06:00
Joey Yakimowich-Payne
fe9c5fa1f3 Add preliminary encode/decode of varints 2020-03-14 20:15:03 -06:00
Joey Yakimowich-Payne
9d60f49e6f Add CI files 2020-03-10 16:24:23 -06:00
Joey Yakimowich-Payne
2d636d636d Nimble package init 2020-03-10 16:21:40 -06:00
6 changed files with 735 additions and 0 deletions

37
.appveyor.yml Normal file
View file

@ -0,0 +1,37 @@
version: '{build}'
image: Visual Studio 2015
cache:
- NimBinaries
matrix:
# We always want 32 and 64-bit compilation
fast_finish: false
platform:
- x86
- x64
# when multiple CI builds are queued, the tested commit needs to be in the last X commits cloned with "--depth X"
clone_depth: 10
install:
# use the newest versions documented here: https://www.appveyor.com/docs/windows-images-software/#mingw-msys-cygwin
- IF "%PLATFORM%" == "x86" SET PATH=C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1\mingw32\bin;%PATH%
- IF "%PLATFORM%" == "x64" SET PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH%
# build nim from our own branch - this to avoid the day-to-day churn and
# regressions of the fast-paced Nim development while maintaining the
# flexibility to apply patches
- curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
- env MAKE="mingw32-make -j2" ARCH_OVERRIDE=%PLATFORM% bash build_nim.sh Nim csources dist/nimble NimBinaries
- SET PATH=%CD%\Nim\bin;%PATH%
build_script:
- nimble install -y
test_script:
- nimble test
deploy: off

27
.travis.yml Normal file
View file

@ -0,0 +1,27 @@
language: c
# https://docs.travis-ci.com/user/caching/
cache:
directories:
- NimBinaries
git:
# when multiple CI builds are queued, the tested commit needs to be in the last X commits cloned with "--depth X"
depth: 10
os:
- linux
- osx
install:
# build nim from our own branch - this to avoid the day-to-day churn and
# regressions of the fast-paced Nim development while maintaining the
# flexibility to apply patches
- curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh
- env MAKE="make -j2" bash build_nim.sh Nim csources dist/nimble NimBinaries
- export PATH=$PWD/Nim/bin:$PATH
script:
- nimble install -y
- nimble test

376
protobuf_serialization.nim Normal file
View file

@ -0,0 +1,376 @@
import macros, strformat, typetraits, options
import faststreams
template sint32*() {.pragma.}
template sint64*() {.pragma.}
template sfixed32*() {.pragma.}
template sfixed64*() {.pragma.}
template fixed32*() {.pragma.}
template fixed64*() {.pragma.}
template float*() {.pragma.}
template double*() {.pragma.}
const
MaxMessageSize* = 1'u shl 22
type
ProtoBuffer* = object
fieldNum: int
outstream: OutputStreamVar
ProtoWireType* = enum
## Protobuf's field types enum
Varint, Fixed64, LengthDelimited, StartGroup, EndGroup, Fixed32
EncodingKind* = enum
ekNormal, ekZigzag
ProtoField*[T] = object
## Protobuf's message field representation object
index*: int
value*: T
SomeSVarint* = int | int64 | int32 | int16 | int8 | enum
SomeByte* = byte | bool | char | uint8
SomeUVarint* = uint | uint64 | uint32 | uint16 | SomeByte
SomeVarint* = SomeSVarint | SomeUVarint
SomeLengthDelimited* = string | seq[SomeByte] | cstring
SomeFixed64* = float64
SomeFixed32* = float32
SomeFixed* = SomeFixed32 | SomeFixed64
AnyProtoType* = SomeVarint | SomeLengthDelimited | SomeFixed | object
UnexpectedTypeError* = object of ValueError
proc newProtoBuffer*(): ProtoBuffer =
ProtoBuffer(outstream: OutputStream.init(), fieldNum: 1)
proc output*(proto: ProtoBuffer): seq[byte] {.inline.} =
proto.outstream.getOutput
template wireType(firstByte: byte): ProtoWireType =
(firstByte and 0b111).ProtoWireType
template fieldNumber(firstByte: byte): int =
((firstByte shr 3) and 0b1111).int
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
template increaseBytesRead(amount = 1) =
## Convenience template for increasing
## all of the counts
mixin isSome
bytesRead += amount
outOffset += amount
outBytesProcessed += amount
if numBytesToRead.isSome():
if (bytesRead > numBytesToRead.get()).unlikely:
raise newException(Exception, &"Number of bytes read ({bytesRead}) exceeded bytes requested ({numBytesToRead})")
proc encodeField*[T: not AnyProtoType](protobuf: var ProtoBuffer, value: T) {.inline.}
proc encodeField*[T: not AnyProtoType](protobuf: var ProtoBuffer, fieldNum: int, value: T) {.inline.}
proc encodeField[T: not AnyProtoType](stream: OutputStreamVar, fieldNum: int, value: T) {.inline.}
proc put(stream: OutputStreamVar, value: SomeVarint) {.inline.} =
when value is enum:
var value = cast[type(ord(value))](value)
elif value is bool or value is char:
var value = cast[byte](value)
else:
var value = value
when type(value) is SomeSVarint:
# Encode using zigzag
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 encodeField(stream: OutputStreamVar, fieldNum: int, value: SomeVarint) {.inline.} =
stream.append protoHeader(fieldNum, Varint)
stream.put(value)
proc put(stream: OutputStreamVar, value: SomeFixed) {.inline.} =
when typeof(value) is SomeFixed64:
var value = cast[int64](value)
else:
var value = cast[int32](value)
for _ in 0 ..< sizeof(value):
stream.append byte(value and 0b1111_1111)
value = value shr 8
proc encodeField(stream: OutputStreamVar, fieldNum: int, value: SomeFixed64) {.inline.} =
stream.append protoHeader(fieldNum, Fixed64)
stream.put(value)
proc encodeField(stream: OutputStreamVar, fieldNum: int, value: SomeFixed32) {.inline.} =
stream.append protoHeader(fieldNum, Fixed32)
stream.put(value)
proc put(stream: OutputStreamVar, value: SomeLengthDelimited) {.inline.} =
stream.put(len(value).uint)
for b in value:
stream.append byte(b)
proc encodeField(stream: OutputStreamVar, fieldNum: int, value: SomeLengthDelimited) {.inline.} =
stream.append protoHeader(fieldNum, LengthDelimited)
stream.put(value)
proc put(stream: OutputStreamVar, value: object) {.inline.}
proc encodeField(stream: OutputStreamVar, fieldNum: int, value: object) {.inline.} =
# This is currently needed in order to get the size
# of the output before adding it to the stream.
# Maybe there is a better way to do this
let objStream = OutputStream.init()
objStream.put(value)
let objOutput = objStream.getOutput()
if objOutput.len > 0:
stream.append protoHeader(fieldNum, LengthDelimited)
stream.put(objOutput)
proc put(stream: OutputStreamVar, value: object) {.inline.} =
var fieldNum = 1
for _, val in value.fieldPairs:
# Only store the value
if default(type(val)) != val:
stream.encodeField(fieldNum, val)
inc fieldNum
proc encode*(protobuf: var ProtoBuffer, value: object) {.inline.} =
protobuf.outstream.put(value)
proc encodeField*(protobuf: var ProtoBuffer, fieldNum: int, value: AnyProtoType) {.inline.} =
protobuf.outstream.encodeField(fieldNum, value)
proc encodeField*(protobuf: var ProtoBuffer, value: AnyProtoType) {.inline.} =
protobuf.encodeField(protobuf.fieldNum, value)
inc protobuf.fieldNum
proc encodeField[T: not AnyProtoType](stream: OutputStreamVar, fieldNum: int, value: T) {.inline.} =
stream.encodeField(fieldNum, value.toBytes)
proc encodeField*[T: not AnyProtoType](protobuf: var ProtoBuffer, fieldNum: int, value: T) {.inline.} =
protobuf.outstream.encodeField(fieldNum, value.toBytes)
proc encodeField*[T: not AnyProtoType](protobuf: var ProtoBuffer, value: T) {.inline.} =
protobuf.encodeField(protobuf.fieldNum, value.toBytes)
inc protobuf.fieldNum
proc get*[T: SomeFixed](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): T {.inline.} =
var bytesRead = 0
when T is SomeFixed64:
var value: int64
else:
var value: int32
var shiftAmount = 0
for _ in 0 ..< sizeof(T):
value += type(value)(bytes[outOffset]) shl shiftAmount
shiftAmount += 8
increaseBytesRead()
result = cast[T](value)
proc get[T: SomeVarint](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): T {.inline.} =
var bytesRead = 0
# Only up to 128 bits supported by the spec
when T is enum or T is char:
var value: type(ord(result))
elif T is bool:
var value: byte
else:
var value: T
var shiftAmount = 0
while true:
value += type(value)(bytes[outOffset] and 0b0111_1111) shl shiftAmount
shiftAmount += 7
if (bytes[outOffset] shr 7) == 0:
break
increaseBytesRead()
increaseBytesRead()
when ty is SomeSVarint:
if (value and type(value)(1)) != type(value)(0):
result = cast[T](not(value shr type(value)(1)))
else:
result = cast[T](value shr type(value)(1))
else:
result = T(value)
proc checkType[T: SomeVarint](tyByte: byte, ty: typedesc[T], offset: int) {.inline.} =
let wireTy = wireType(tyByte)
if wireTy != Varint:
raise newException(UnexpectedTypeError, fmt"Not a varint at offset {offset}! Received a {wireTy}")
proc checkType[T: SomeFixed](tyByte: byte, ty: typedesc[T], offset: int) {.inline.} =
let wireTy = wireType(tyByte)
if wireTy notin {Fixed32, Fixed64}:
raise newException(UnexpectedTypeError, fmt"Not a fixed32 or fixed64 at offset {offset}! Received a {wireTy}")
proc checkType[T: SomeLengthDelimited](tyByte: byte, ty: typedesc[T], offset: int) {.inline.} =
let wireTy = wireType(tyByte)
if wireTy != LengthDelimited:
raise newException(UnexpectedTypeError, fmt"Not a length delimited value at offset {offset}! Received a {wireTy}")
proc checkType[T: object](tyByte: byte, ty: typedesc[T], offset: int) {.inline.} =
let wireTy = wireType(tyByte)
if wireTy != LengthDelimited:
raise newException(UnexpectedTypeError, fmt"Not an object value at offset {offset}! Received a {wireTy}")
proc get*[T: SomeLengthDelimited](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): T {.inline.} =
var bytesRead = 0
let decodedSize = bytes.get(uint, outOffset, outBytesProcessed, numBytesToRead)
let length = decodedSize.int
when T is string:
result = newString(length)
for i in outOffset ..< (outOffset + length):
result[i - outOffset] = bytes[i].chr
elif T is cstring:
result = cast[cstring](bytes[outOffset ..< (outOffset + length)])
else:
result.setLen(length)
for i in outOffset ..< (outOffset + length):
result[i - outOffset] = type(result[0])(bytes[i])
increaseBytesRead(length)
proc decodeField*[T: SomeFixed | SomeVarint | SomeLengthDelimited](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): ProtoField[T] {.inline.} =
var bytesRead = 0
checkType(bytes[outOffset], ty, outOffset)
result.index = fieldNumber(bytes[outOffset])
increaseBytesRead()
result.value = bytes.get(ty, outOffset, outBytesProcessed, numBytesToRead)
proc decodeField*[T: object](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): ProtoField[T] {.inline.}
proc decodeField*[T: not AnyProtoType](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): ProtoField[T] {.inline.} =
var bytesRead = 0
checkType(bytes[outOffset], seq[byte], outOffset)
result.index = fieldNumber(bytes[outOffset])
increaseBytesRead()
var value = bytes.get(seq[byte], outOffset, outBytesProcessed, numBytesToRead)
result.value = value.to(T)
macro setField(obj: typed, fieldNum: int, offset: int, bytesProcessed: int, bytesToRead: Option[int], value: untyped): untyped =
let typeFields = obj.getTypeInst.getType
let objFields = typeFields[2]
expectKind objFields, nnkRecList
result = newStmtList()
let caseStmt = newNimNode(nnkCaseStmt)
caseStmt.add(fieldNum)
for i in 0 ..< len(objFields) - 1:
let field = objFields[i]
let ofBranch = newNimNode(nnkOfBranch)
ofBranch.add(newLit(i+1))
ofBranch.add(
quote do:
`obj`.`field` = decodeField(`value`, type(`obj`.`field`), `offset`, `bytesProcessed`, `bytesToRead`).value
)
caseStmt.add(ofBranch)
let field = objFields[len(objFields) - 1]
let elseBranch = newNimNode(nnkElse)
elseBranch.add(
nnkStmtList.newTree(
quote do:
`obj`.`field` = decodeField(`value`, type(`obj`.`field`), `offset`, `bytesProcessed`, `bytesToRead`).value
)
)
caseStmt.add(elseBranch)
result.add(caseStmt)
proc decodeField*[T: object](
bytes: var seq[byte],
ty: typedesc[T],
outOffset: var int,
outBytesProcessed: var int,
numBytesToRead = none(int)
): ProtoField[T] {.inline.} =
var bytesRead = 0
checkType(bytes[outOffset], ty, outOffset)
result.index = fieldNumber(bytes[outOffset])
# read LD header
# then read only amount of bytes needed
increaseBytesRead()
let decodedSize = bytes.get(uint, outOffset, outBytesProcessed, numBytesToRead)
let bytesToRead = some(decodedSize.int)
let oldOffset = outOffset
while outOffset < oldOffset + bytesToRead.get():
let fieldNum = fieldNumber(bytes[outOffset])
setField(result.value, fieldNum, outOffset, outBytesProcessed, bytesToRead, bytes)
proc decode*[T: object](
bytes: var seq[byte],
ty: typedesc[T],
): T {.inline.} =
var bytesRead = 0
var offset = 0
while offset < bytes.len - 1:
let fieldNum = fieldNumber(bytes[offset])
setField(result, fieldNum, offset, bytesRead, none(int), bytes)

View file

@ -0,0 +1,14 @@
# Package
version = "0.1.0"
author = "Joey Yakimowich-Payne"
description = "Protobuf implementation compatible with the nim-serialization framework."
license = "MIT"
srcDir = "src"
skipDirs = @["tests"]
# Dependencies
requires "nim >= 1.0.6", "faststreams"

1
tests/config.nims Normal file
View file

@ -0,0 +1 @@
switch("path", "$projectDir/../")

View file

@ -0,0 +1,280 @@
import unittest
import sequtils
import protobuf_serialization
type
MyEnum = enum
ME1, ME2, ME3
Test1 = object
a: uint
b: string
c: char
Test3 = object
g {.sfixed32.}: int
h: int
i: Test1
j: string
k: bool
l: MyInt
MyInt = distinct int
proc to*(bytes: var seq[byte], ty: typedesc[MyInt]): MyInt =
var value: int
var shiftAmount = 0
for i in 0 ..< len(bytes):
value += int(bytes[i]) shl shiftAmount
shiftAmount += 8
result = MyInt(value)
proc toBytes*(value: MyInt): seq[byte] =
var value = value.int
while value > 0:
result.add byte(value and 0b1111_1111)
value = value shr 8
proc `==`(a, b: MyInt): bool {.borrow.}
suite "Test Varint Encoding":
test "Can encode/decode enum field":
var proto = newProtoBuffer()
var bytesProcessed: int
proto.encodeField(ME3)
proto.encodeField(ME2)
var output = proto.output
assert output == @[8.byte, 4, 16, 2]
var offset = 0
let decodedME3 = decodeField(output, MyEnum, offset, bytesProcessed)
assert decodedME3.value == ME3
assert decodedME3.index == 1
let decodedME2 = decodeField(output, MyEnum, offset, bytesProcessed)
assert decodedME2.value == ME2
assert decodedME2.index == 2
test "Can encode/decode negative number field":
var proto = newProtoBuffer()
let num = -153452
var bytesProcessed: int
proto.encodeField(num)
var output = proto.output
assert output == @[8.byte, 215, 221, 18]
var offset = 0
let decoded = decodeField(output, int, offset, bytesProcessed)
assert decoded.value == num
assert decoded.index == 1
test "Can encode/decode distinct number field":
var proto = newProtoBuffer()
let num = 114151.MyInt
var bytesProcessed: int
proto.encodeField(num)
var output = proto.output
assert output == @[10.byte, 3, 231, 189, 1]
var offset = 0
let decoded = decodeField(output, MyInt, offset, bytesProcessed)
assert decoded.value.int == num.int
assert decoded.index == 1
test "Can encode/decode float32 number field":
var proto = newProtoBuffer()
let num = float32(1234.164423)
var bytesProcessed: int
proto.encodeField(num)
var output = proto.output
assert output == @[13.byte, 67, 69, 154, 68]
var offset = 0
let decoded = decodeField(output, float32, offset, bytesProcessed)
assert decoded.value == num
assert decoded.index == 1
test "Can encode/decode float64 number field":
var proto = newProtoBuffer()
let num = 12343121537452.1644232341'f64
var bytesProcessed: int
proto.encodeField(num)
var output = proto.output
assert output == @[9.byte, 84, 88, 211, 191, 182, 115, 166, 66]
var offset = 0
let decoded = decodeField(output, float64, offset, bytesProcessed)
assert decoded.value == num
assert decoded.index == 1
test "Can encode/decode bool field":
var proto = newProtoBuffer()
let boolean = true
var bytesProcessed: int
proto.encodeField(boolean)
var output = proto.output
assert output == @[8.byte, 1]
var offset = 0
let decoded = decodeField(output, bool, offset, bytesProcessed)
assert bytesProcessed == 2
assert decoded.value == boolean
assert decoded.index == 1
test "Can encode/decode char field":
var proto = newProtoBuffer()
let charVal = 'G'
var bytesProcessed: int
proto.encodeField(charVal)
var output = proto.output
assert output == @[8.byte, ord(charVal).byte]
var offset = 0
let decoded = decodeField(output, char, offset, bytesProcessed)
assert bytesProcessed == 2
assert decoded.value == charVal
assert decoded.index == 1
test "Can encode/decode unsigned number field":
var proto = newProtoBuffer()
let num = 123151.uint
var bytesProcessed: int
proto.encodeField(num)
var output = proto.output
assert output == @[8.byte, 143, 194, 7]
var offset = 0
let decoded = decodeField(output, uint, offset, bytesProcessed)
assert decoded.value == num
assert decoded.index == 1
test "Can encode/decode string field":
var proto = newProtoBuffer()
let str = "hey this is a string"
var bytesProcessed: int
proto.encodeField(str)
var output = proto.output
assert output == @[10.byte, 20, 104, 101, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103]
var offset = 0
let decoded = decodeField(output, string, offset, bytesProcessed)
assert decoded.value == str
assert decoded.index == 1
test "Can encode/decode char seq field":
var proto = newProtoBuffer()
let charSeq = "hey this is a string".toSeq
var bytesProcessed: int
proto.encodeField(charSeq)
var output = proto.output
assert output == @[10.byte, 20, 104, 101, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103]
var offset = 0
let decoded = decodeField(output, seq[char], offset, bytesProcessed)
assert decoded.value == charSeq
assert decoded.index == 1
test "Can encode/decode uint8 seq field":
var proto = newProtoBuffer()
let uint8Seq = cast[seq[uint8]]("hey this is a string".toSeq)
var bytesProcessed: int
proto.encodeField(uint8Seq)
var output = proto.output
assert output == @[10.byte, 20, 104, 101, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103]
var offset = 0
let decoded = decodeField(output, seq[uint8], offset, bytesProcessed)
assert decoded.value == uint8Seq
assert decoded.index == 1
test "Can encode/decode object field":
var proto = newProtoBuffer()
let obj = Test3(g: 300, h: 200, i: Test1(a: 100, b: "this is a test", c: 'H'), j: "testing", k: true, l: 124521.MyInt)
proto.encodeField(obj)
var offset, bytesProcessed: int
var output = proto.output
let decoded = decodeField(output, Test3, offset, bytesProcessed)
assert decoded.value == obj
assert decoded.index == 1
test "Can encode/decode object":
var proto = newProtoBuffer()
let obj = Test3(g: 300, h: 200, i: Test1(a: 100, b: "this is a test", c: 'H'), j: "testing", k: true, l: 124521.MyInt)
proto.encode(obj)
var output = proto.output
let decoded = output.decode(Test3)
assert decoded == obj
test "Can encode/decode out of order object":
var proto = newProtoBuffer()
let obj = Test3(g: 400, h: 100, i: Test1(a: 100, b: "this is a test", c: 'H'), j: "testing", k: true, l: 14514.MyInt)
proto.encodeField(6, 14514.MyInt)
proto.encodeField(2, 100)
proto.encodeField(4, "testing")
proto.encodeField(1, 400)
proto.encodeField(3, Test1(a: 100, b: "this is a test", c: 'H'))
proto.encodeField(5, true)
var output = proto.output
let decoded = output.decode(Test3)
assert decoded == obj
test "Empty object field does not get encoded":
var proto = newProtoBuffer()
let obj = Test1()
proto.encodeField(1, obj)
var output = proto.output
assert output.len == 0
let decoded = output.decode(Test1)
assert decoded == obj
test "Empty object does not get encoded":
var proto = newProtoBuffer()
let obj = Test1()
proto.encode(obj)
var output = proto.output
assert output.len == 0
let decoded = output.decode(Test1)
assert decoded == obj