aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2018-03-24 15:44:30 +0200
committerOskari Timperi <oskari.timperi@iki.fi>2018-03-24 15:44:30 +0200
commit0bc7b67059868af65d2158a8aeade5b6f777431b (patch)
treeb8ce5211ac3cf673a62911e852ed014d5f11f43d /src
downloadnimpb-0bc7b67059868af65d2158a8aeade5b6f777431b.tar.gz
nimpb-0bc7b67059868af65d2158a8aeade5b6f777431b.zip
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/protobuf/gen.nim385
-rw-r--r--src/protobuf/stream.nim311
-rw-r--r--src/protobuf/types.nim80
3 files changed, 776 insertions, 0 deletions
diff --git a/src/protobuf/gen.nim b/src/protobuf/gen.nim
new file mode 100644
index 0000000..881b275
--- /dev/null
+++ b/src/protobuf/gen.nim
@@ -0,0 +1,385 @@
+import macros
+import strutils
+
+import types
+
+type
+ MessageDesc* = object
+ name*: string
+ fields*: seq[FieldDesc]
+
+ FieldLabel* {.pure.} = enum
+ Optional = 1
+ Required
+ Repeated
+
+ FieldDesc* = object
+ name*: string
+ number*: int
+ ftype*: FieldType
+ label*: FieldLabel
+ typeName*: string
+ packed*: bool
+
+proc toNimNode(ftype: FieldType): NimNode {.compileTime.} =
+ case ftype
+ of FieldType.Double: result = ident("float64")
+ of FieldType.Float: result = ident("float32")
+ of FieldType.Int64: result = ident("int64")
+ of FieldType.UInt64: result = ident("uint64")
+ of FieldType.Int32: result = ident("int32")
+ of FieldType.Fixed64: result = ident("fixed64")
+ of FieldType.Fixed32: result = ident("fixed32")
+ of FieldType.Bool: result = ident("bool")
+ of FieldType.String: result = ident("string")
+ of FieldType.Group: result = ident("NOTIMPLEMENTED")
+ of FieldType.Message: result = ident("TODO")
+ of FieldType.Bytes: result = ident("bytes")
+ of FieldType.UInt32: result = ident("uint32")
+ of FieldType.Enum: result = ident("TODO")
+ of FieldType.SFixed32: result = ident("sfixed32")
+ of FieldType.SFixed64: result = ident("sfixed64")
+ of FieldType.SInt32: result = ident("sint32")
+ of FieldType.SInt64: result = ident("sint64")
+
+proc findColonExpr(parent: NimNode, s: string): NimNode =
+ for child in parent:
+ if child.kind != nnkExprColonExpr:
+ continue
+
+ if $child[0] == s:
+ return child
+
+proc getMessageName(desc: NimNode): string =
+ let node = findColonExpr(desc, "name")
+ result = $node[1]
+
+iterator fields(desc: NimNode): NimNode =
+ let node = findColonExpr(desc, "fields")
+ for field in node[1]:
+ yield field
+
+proc isRepeated(field: NimNode): bool =
+ let node = findColonExpr(field, "label")
+ let value = FieldLabel(node[1].intVal)
+ result = value == FieldLabel.Repeated
+
+proc isPacked(field: NimNode): bool =
+ let node = findColonExpr(field, "packed")
+ result = bool(node[1].intVal)
+
+proc getFieldType(field: NimNode): FieldType =
+ let node = findColonExpr(field, "ftype")
+ result = FieldType(node[1].intVal)
+
+proc getFullFieldType(field: NimNode): NimNode =
+ let ftype = getFieldType(field)
+ result = toNimNode(ftype)
+ if isRepeated(field):
+ result = nnkBracketExpr.newTree(ident("seq"), result)
+
+proc getFieldName(field: NimNode): string =
+ let node = findColonExpr(field, "name")
+ result = $node[1]
+
+proc getFieldNumber(field: NimNode): int =
+ result = int(findColonExpr(field, "number")[1].intVal)
+
+proc defaultValue(field: NimNode): NimNode =
+ # TODO: check if there is a default value specified for the field
+
+ if isRepeated(field):
+ return nnkPrefix.newTree(newIdentNode("@"), nnkBracket.newTree())
+
+ case getFieldType(field)
+ of FieldType.Double: result = newLit(0.0'f64)
+ of FieldType.Float: result = newLit(0.0'f32)
+ of FieldType.Int64: result = newLit(0'i64)
+ of FieldType.UInt64: result = newLit(0'u64)
+ of FieldType.Int32: result = newLit(0'i32)
+ of FieldType.Fixed64: result = newLit(0'u64)
+ of FieldType.Fixed32: result = newLit(0'u32)
+ of FieldType.Bool: result = newLit(false)
+ of FieldType.String: result = newLit("")
+ of FieldType.Group: result = newLit("NOTIMPLEMENTED")
+ of FieldType.Message: result = newLit("TODO")
+ of FieldType.Bytes: result = newCall(ident("bytes"), newLit(""))
+ of FieldType.UInt32: result = newLit(0'u32)
+ of FieldType.Enum: result = newLit("TODO")
+ of FieldType.SFixed32: result = newCall(ident("sfixed32"), newLit(0))
+ of FieldType.SFixed64: result = newCall(ident("sfixed64"), newLit(0))
+ of FieldType.SInt32: result = newCall(ident("sint32"), newLit(0))
+ of FieldType.SInt64: result = newCall(ident("sint64"), newLit(0))
+
+proc wiretype(field: NimNode): WireType =
+ result = wiretype(getFieldType(field))
+
+proc fieldInitializer(objname: string, field: NimNode): NimNode =
+ result = nnkAsgn.newTree(
+ nnkDotExpr.newTree(
+ newIdentNode(objname),
+ newIdentNode(getFieldName(field))
+ ),
+ defaultValue(field)
+ )
+
+macro generateMessageType*(desc: typed): typed =
+ let
+ impl = getImpl(symbol(desc))
+ typeSection = nnkTypeSection.newTree()
+ typedef = nnkTypeDef.newTree()
+ reclist = nnkRecList.newTree()
+
+ let name = getMessageName(impl)
+
+ let typedefRef = nnkTypeDef.newTree(postfix(newIdentNode(name), "*"), newEmptyNode(),
+ nnkRefTy.newTree(newIdentNode(name & "Obj")))
+ add(typeSection, typedefRef)
+
+ add(typeSection, typedef)
+
+ add(typedef, postfix(ident(name & "Obj"), "*"))
+ add(typedef, newEmptyNode())
+ add(typedef, nnkObjectTy.newTree(newEmptyNode(), newEmptyNode(), reclist))
+
+ for field in fields(impl):
+ let ftype = getFullFieldType(field)
+ let name = ident(getFieldName(field))
+ add(reclist, newIdentDefs(name, ftype))
+
+ add(reclist, nnkIdentDefs.newTree(
+ ident("hasField"), ident("IntSet"), newEmptyNode()))
+
+ result = newStmtList()
+ add(result, typeSection)
+
+ when defined(debug):
+ hint(repr(result))
+
+proc generateNewMessageProc(desc: NimNode): NimNode =
+ let body = newStmtList(
+ newCall(ident("new"), ident("result"))
+ )
+
+ for field in fields(desc):
+ add(body, fieldInitializer("result", field))
+
+ add(body, newAssignment(newDotExpr(ident("result"), ident("hasField")),
+ newCall(ident("initIntSet"))))
+
+ result = newProc(postfix(ident("new" & getMessageName(desc)), "*"),
+ @[ident(getMessageName(desc))],
+ body)
+
+proc generateClearFieldProc(desc, field: NimNode): NimNode =
+ let body = nnkStmtList.newTree()
+
+ let messageName = getMessageName(desc)
+ let fieldName = getFieldName(field)
+
+ add(body, fieldInitializer("message", field))
+
+ add(body, nnkCall.newTree(
+ ident("excl"),
+ nnkDotExpr.newTree(
+ ident("message"),
+ ident("hasField")
+ ),
+ newLit(getFieldNumber(field))
+ ))
+
+ result = newProc(postfix(ident("clear" & capitalizeAscii(fieldName)), "*"),
+ @[newEmptyNode(), newIdentDefs(ident("message"), ident(messageName))],
+ body)
+
+proc generateHasFieldProc(desc, field: NimNode): NimNode =
+ let body = nnkCall.newTree(
+ ident("contains"),
+ newDotExpr(ident("message"), ident("hasField")),
+ newLit(getFieldNumber(field))
+ )
+
+ let messageName = getMessageName(desc)
+ let fieldName = getFieldName(field)
+
+ result = newProc(postfix(ident("has" & capitalizeAscii(fieldName)), "*"),
+ @[ident("bool"), newIdentDefs(ident("message"), ident(messageName))],
+ body)
+
+proc generateSetFieldProc(desc, field: NimNode): NimNode =
+ # let body = nnkStmtList.newTree(nnkDiscardStmt.newTree(newEmptyNode()))
+
+ let body = newStmtList()
+
+ let messageName = getMessageName(desc)
+ let fieldName = getFieldName(field)
+
+ add(body, newAssignment(newDotExpr(ident("message"), ident(fieldName)), ident("value")))
+
+ add(body, newCall("incl", newDotExpr(ident("message"), ident("hasField")),
+ newLit(getFieldNumber(field))))
+
+ let ftype = getFullFieldType(field)
+
+ result = newProc(postfix(ident("set" & capitalizeAscii(fieldName)), "*"),
+ @[newEmptyNode(), newIdentDefs(ident("message"),
+ ident(messageName)),
+ newIdentDefs(ident("value"), ftype)],
+ body)
+
+proc generateAddToFieldProc(desc, field: NimNode): NimNode =
+ let body = newStmtList()
+
+ let messageName = getMessageName(desc)
+ let fieldName = getFieldName(field)
+
+ add(body, newCall(
+ ident("add"),
+ newDotExpr(ident("message"), ident(fieldName)),
+ ident("value")
+ ))
+
+ add(body, newCall("incl", newDotExpr(ident("message"), ident("hasField")),
+ newLit(getFieldNumber(field))))
+
+ let ftype = toNimNode(getFieldType(field))
+
+ result = newProc(postfix(ident("add" & capitalizeAscii(fieldName)), "*"),
+ @[newEmptyNode(), newIdentDefs(ident("message"),
+ ident(messageName)),
+ newIdentDefs(ident("value"), ftype)],
+ body)
+
+proc ident(wt: WireType): NimNode =
+ result = newDotExpr(ident("WireType"), ident($wt))
+
+proc genWriteField(field: NimNode): NimNode =
+ result = newStmtList()
+
+ let
+ number = getFieldNumber(field)
+ writer = ident("write" & $getFieldType(field))
+ fname = newDotExpr(ident("message"), ident(getFieldName(field)))
+ wiretype = ident(wiretype(field))
+
+ if not isRepeated(field):
+ result.add quote do:
+ writeTag(stream, `number`, `wiretype`)
+ `writer`(stream, `fname`)
+ else:
+ if isPacked(field):
+ result.add quote do:
+ writeTag(stream, `number`, WireType.LengthDelimited)
+ writeVarInt(stream, packedFieldSize(`fname`, `wiretype`))
+ for value in `fname`:
+ `writer`(stream, value)
+ else:
+ result.add quote do:
+ for value in `fname`:
+ writeTag(stream, `number`, `wiretype`)
+ `writer`(stream, value)
+
+proc generateWriteMessageProc(desc: NimNode): NimNode =
+ let body = newStmtList()
+
+ let name = getMessageName(desc)
+
+ for field in fields(desc):
+ add(body, nnkIfStmt.newTree(
+ nnkElifBranch.newTree(
+ newCall(ident("has" & capitalizeAscii(getFieldName(field))),
+ ident("message")),
+ genWriteField(field)
+ )
+ ))
+
+ result = newProc(postfix(ident("write" & name), "*"),
+ @[newEmptyNode(),
+ newIdentDefs(ident("stream"), ident("ProtobufStream")),
+ newIdentDefs(ident("message"), ident(name))],
+ body)
+
+proc generateReadMessageProc(desc: NimNode): NimNode =
+ let name = getMessageName(desc)
+
+ let resultId = ident("result")
+
+ let body = newStmtList(
+ newCall(ident("new"), resultId)
+ )
+
+ let tagid = ident("tag")
+ let wiretypeId = ident("wiretype")
+
+ body.add quote do:
+ while not atEnd(stream):
+ let
+ `tagId` = readTag(stream)
+ `wiretypeId` = getTagWireType(`tagId`)
+ case getTagFieldNumber(`tagId`)
+
+ let caseNode = body[^1][1][1]
+
+ # TODO: check wiretypes and fail if it doesn't match
+ for field in fields(desc):
+ let number = getFieldNumber(field)
+ if isRepeated(field):
+ let adder = ident("add" & capitalizeAscii(getFieldName(field)))
+ let reader = ident("read" & $getFieldType(field))
+ if isNumeric(getFieldType(field)):
+ add(caseNode, nnkOfBranch.newTree(newLit(number), quote do:
+ if `wiretypeId` == WireType.LengthDelimited:
+ # TODO: do this only if it makes sense, i.e. with primitives?
+ let
+ size = readVarint(stream)
+ start = getPosition(stream).uint64
+ var consumed = 0'u64
+ while consumed < size:
+ `adder`(`resultId`, `reader`(stream))
+ consumed = getPosition(stream).uint64 - start
+ if consumed != size:
+ raise newException(Exception, "packed field size mismatch")
+ else:
+ `adder`(`resultId`, `reader`(stream))
+ ))
+ else:
+ add(caseNode, nnkOfBranch.newTree(newLit(number), quote do:
+ `adder`(`resultId`, `reader`(stream))
+ ))
+ else:
+ let setter = ident("set" & capitalizeAscii(getFieldName(field)))
+ let reader = ident("read" & $getFieldType(field))
+ add(caseNode, nnkOfBranch.newTree(newLit(number), quote do:
+ `setter`(`resultId`, `reader`(stream))
+ ))
+
+ # TODO: generate code to skip unknown fields
+ add(caseNode, nnkElse.newTree(quote do:
+ raise newException(Exception, "unknown field")
+ ))
+
+ result = newProc(postfix(ident("read" & name), "*"),
+ @[ident(name), newIdentDefs(ident("stream"), ident("ProtobufStream"))],
+ body)
+
+macro generateMessageProcs*(x: typed): typed =
+ let
+ desc = getImpl(symbol(x))
+
+ result = newStmtList(
+ generateNewMessageProc(desc),
+ )
+
+ for field in fields(desc):
+ add(result, generateClearFieldProc(desc, field))
+ add(result, generateHasFieldProc(desc, field))
+ add(result, generateSetFieldProc(desc, field))
+
+ if isRepeated(field):
+ add(result, generateAddToFieldProc(desc, field))
+
+ add(result, generateWriteMessageProc(desc))
+ add(result, generateReadMessageProc(desc))
+
+ when defined(debug):
+ hint(repr(result))
diff --git a/src/protobuf/stream.nim b/src/protobuf/stream.nim
new file mode 100644
index 0000000..4c026b4
--- /dev/null
+++ b/src/protobuf/stream.nim
@@ -0,0 +1,311 @@
+import endians
+import streams
+
+import types
+
+export streams
+
+const
+ MaximumVarintBytes = 10
+ WireTypeBits = 3
+ WireTypeMask = (1 shl WireTypeBits) - 1
+
+type
+ ProtobufStreamObj* = object of StreamObj
+ stream: Stream
+
+ ProtobufStream* = ref ProtobufStreamObj
+
+ Tag* = distinct uint32
+
+proc pbClose(s: Stream) =
+ close(ProtobufStream(s).stream)
+ ProtobufStream(s).stream = nil
+
+proc pbAtEnd(s: Stream): bool =
+ result = atEnd(ProtobufStream(s).stream)
+
+proc pbSetPosition(s: Stream, pos: int) =
+ setPosition(ProtobufStream(s).stream, pos)
+
+proc pbGetPosition(s: Stream): int =
+ result = getPosition(ProtobufStream(s).stream)
+
+proc pbReadData(s: Stream, buffer: pointer, bufLen: int): int =
+ result = readData(ProtobufStream(s).stream, buffer, bufLen)
+
+proc pbPeekData(s: Stream, buffer: pointer, bufLen: int): int =
+ result = peekData(ProtobufStream(s).stream, buffer, bufLen)
+
+proc pbWriteData(s: Stream, buffer: pointer, bufLen: int) =
+ writeData(ProtobufStream(s).stream, buffer, bufLen)
+
+proc pbFlush(s: Stream) =
+ flush(ProtobufStream(s).stream)
+
+proc newProtobufStream*(stream: Stream): ProtobufStream =
+ new(result)
+
+ result.closeImpl = pbClose
+ result.atEndImpl = pbAtEnd
+ result.setPositionImpl = pbSetPosition
+ result.getPositionImpl = pbGetPosition
+ result.readDataImpl = pbReadData
+ result.peekDataImpl = pbPeekData
+ result.writeDataImpl = pbWriteData
+ result.flushImpl = pbFlush
+
+ result.stream = stream
+
+proc readByte(stream: ProtobufStream): byte =
+ result = readInt8(stream).byte
+
+proc writeByte(stream: ProtobufStream, b: byte) =
+ var x: byte
+ shallowCopy(x, b)
+ writeData(stream, addr(x), sizeof(x))
+
+proc readVarint*(stream: ProtobufStream): uint64 =
+ var
+ count = 0
+
+ result = 0
+
+ while true:
+ if count == MaximumVarintBytes:
+ raise newException(Exception, "invalid varint (<= 10 bytes)")
+
+ let b = readByte(stream)
+
+ result = result or ((b.uint64 and 0x7f) shl (7 * count))
+
+ inc(count)
+
+ if (b and 0x80) == 0:
+ break
+
+proc writeVarint*(stream: ProtobufStream, n: uint64) =
+ var value = n
+ while value >= 0x80'u64:
+ writeByte(stream, (value or 0x80).byte)
+ value = value shr 7
+ writeByte(stream, value.byte)
+
+proc zigzagEncode*(n: int32): uint32 =
+ {.emit:["result = ((NU32)n << 1) ^ (NU32)(n >> 31);"].}
+
+proc zigzagDecode*(n: uint32): int32 =
+ {.emit:["result = (NI32) ((n >> 1) ^ (~(n & 1) + 1));"].}
+
+proc zigzagEncode*(n: int64): uint64 =
+ {.emit:["result = ((NU64)n << 1) ^ (NU64)(n >> 63);"].}
+
+proc zigzagDecode*(n: uint64): int64 =
+ {.emit:["result = (NI64) ((n >> 1) ^ (~(n & 1) + 1));"].}
+
+template makeTag*(fieldNumber: int, wireType: WireType): Tag =
+ ((fieldNumber shl 3).uint32 or wireType.uint32).Tag
+
+template getTagWireType*(tag: Tag): WireType =
+ (tag.uint32 and WireTypeMask).WireType
+
+template getTagFieldNumber*(tag: Tag): int =
+ (tag.uint32 shr 3).int
+
+proc writeTag*(stream: ProtobufStream, tag: Tag) =
+ writeVarint(stream, tag.uint32)
+
+proc writeTag*(stream: ProtobufStream, fieldNumber: int, wireType: WireType) =
+ writeTag(stream, makeTag(fieldNumber, wireType))
+
+proc readTag*(stream: ProtobufStream): Tag =
+ result = readVarint(stream).Tag
+
+proc writeInt32*(stream: ProtobufStream, n: int32) =
+ writeVarint(stream, n.uint64)
+
+proc readInt32*(stream: ProtobufStream): int32 =
+ result = readVarint(stream).int32
+
+proc writeSInt32*(stream: ProtobufStream, n: int32) =
+ writeVarint(stream, zigzagEncode(n))
+
+proc readSInt32*(stream: ProtobufStream): int32 =
+ result = zigzagDecode(readVarint(stream).uint32)
+
+proc writeUInt32*(stream: ProtobufStream, n: uint32) =
+ writeVarint(stream, n)
+
+proc readUInt32*(stream: ProtobufStream): uint32 =
+ result = readVarint(stream).uint32
+
+proc writeInt64*(stream: ProtobufStream, n: int64) =
+ writeVarint(stream, n.uint64)
+
+proc readInt64*(stream: ProtobufStream): int64 =
+ result = readVarint(stream).int64
+
+proc writeSInt64*(stream: ProtobufStream, n: int64) =
+ writeVarint(stream, zigzagEncode(n))
+
+proc readSInt64*(stream: ProtobufStream): int64 =
+ result = zigzagDecode(readVarint(stream))
+
+proc writeUInt64*(stream: ProtobufStream, n: uint64) =
+ writeVarint(stream, n)
+
+proc readUInt64*(stream: ProtobufStream): uint64 =
+ result = readVarint(stream)
+
+proc writeBool*(stream: ProtobufStream, value: bool) =
+ writeVarint(stream, value.uint32)
+
+proc readBool*(stream: ProtobufStream): bool =
+ result = readVarint(stream).bool
+
+proc writeFixed64*(stream: ProtobufStream, value: fixed64) =
+ var
+ input = value
+ output: fixed64
+
+ littleEndian64(addr(output), addr(input))
+
+ write(stream, output)
+
+proc readFixed64*(stream: ProtobufStream): fixed64 =
+ var tmp: fixed64
+ discard readData(stream, addr(tmp), sizeof(tmp))
+ littleEndian64(addr(result), addr(tmp))
+
+proc writeSFixed64*(stream: ProtobufStream, value: sfixed64) =
+ writeFixed64(stream, cast[fixed64](value))
+
+proc readSFixed64*(stream: ProtobufStream): sfixed64 =
+ result = cast[sfixed64](readFixed64(stream))
+
+proc writeDouble*(stream: ProtobufStream, value: float64) =
+ var
+ input = value
+ output: float64
+
+ littleEndian64(addr(output), addr(input))
+
+ write(stream, output)
+
+proc readDouble*(stream: ProtobufStream): float64 =
+ var tmp: uint64
+ discard readData(stream, addr(tmp), sizeof(tmp))
+ littleEndian64(addr(tmp), addr(result))
+
+proc writeFixed32*(stream: ProtobufStream, value: fixed32) =
+ var
+ input = value
+ output: fixed32
+
+ littleEndian32(addr(output), addr(input))
+
+ write(stream, output)
+
+proc readFixed32*(stream: ProtobufStream): fixed32 =
+ var tmp: uint32
+ discard readData(stream, addr(tmp), sizeof(tmp))
+ littleEndian32(addr(tmp), addr(result))
+
+proc writeSFixed32*(stream: ProtobufStream, value: sfixed32) =
+ writeFixed32(stream, cast[fixed32](value))
+
+proc readSFixed32*(stream: ProtobufStream): sfixed32 =
+ result = cast[sfixed32](readFixed32(stream))
+
+proc writeFloat*(stream: ProtobufStream, value: float32) =
+ var
+ input = value
+ output: float32
+
+ littleEndian32(addr(output), addr(input))
+
+ write(stream, output)
+
+proc readFloat*(stream: ProtobufStream): float32 =
+ var tmp: float32
+ discard readData(stream, addr(tmp), sizeof(tmp))
+ littleEndian32(addr(tmp), addr(result))
+
+proc writeString*(stream: ProtobufStream, s: string) =
+ writeUInt64(stream, len(s).uint64)
+ write(stream, s)
+
+proc readString*(stream: ProtobufStream): string =
+ # TODO: use something else than readStr to get rid of int cast?
+ let size = readUInt64(stream).int
+ result = readStr(stream, size)
+
+proc readEnum*[T](stream: ProtobufStream): T =
+ result = T(readUInt32(stream))
+
+proc writeEnum*[T](stream: ProtobufStream, value: T) =
+ writeUInt32(stream, uint32(value))
+
+proc sizeOfVarint[T](value: T): uint64 =
+ var tmp = uint64(value)
+ while tmp >= 0x80'u64:
+ tmp = tmp shr 7
+ inc(result)
+ inc(result)
+
+proc packedFieldSize*[T](values: seq[T], wiretype: WireType): uint64 =
+ case wiretype
+ of WireType.Fixed32:
+ result = uint64(len(values)) * 4
+ of WireType.Fixed64:
+ result = uint64(len(values)) * 8
+ of WireType.Varint:
+ for value in values:
+ result += sizeOfVarint(value)
+ else:
+ discard
+
+proc sizeOfString*(s: string): uint64 =
+ result = sizeOfVarint(len(s).uint64) + len(s).uint64
+
+proc sizeOfDouble*(value: float64): uint64 =
+ result = 8
+
+proc sizeOfFloat*(value: float32): uint64 =
+ result = 4
+
+proc sizeOfInt64*(value: int64): uint64 =
+ result = sizeOfVarint(value)
+
+proc sizeOfUInt64*(value: uint64): uint64 =
+ result = sizeOfVarint(value)
+
+proc sizeOfInt32*(value: int32): uint64 =
+ result = sizeOfVarint(value)
+
+proc sizeOfFixed64*(value: uint64): uint64 =
+ result = 8
+
+proc sizeOfFixed32*(value: uint32): uint64 =
+ result = 4
+
+proc sizeOfBool*(value: bool): uint64 =
+ result = sizeOfVarint(value)
+
+proc sizeOfUInt32*(value: uint32): uint64 =
+ result = sizeOfVarint(value)
+
+proc sizeOfSFixed32*(value: int32): uint64 =
+ result = 4
+
+proc sizeOfSFixed64*(value: int64): uint64 =
+ result = 8
+
+proc sizeOfSInt32*(value: int32): uint64 =
+ result = sizeOfVarint(zigzagEncode(value))
+
+proc sizeOfSInt64*(value: int64): uint64 =
+ result = sizeOfVarint(zigzagEncode(value))
+
+proc sizeOfEnum*[T](value: T): uint64 =
+ result = sizeOfUInt32(value.uint32)
diff --git a/src/protobuf/types.nim b/src/protobuf/types.nim
new file mode 100644
index 0000000..fdd26db
--- /dev/null
+++ b/src/protobuf/types.nim
@@ -0,0 +1,80 @@
+type
+ fixed32* = distinct uint32
+ fixed64* = distinct uint64
+ sfixed32* = distinct int32
+ sfixed64* = distinct int64
+ sint32* = distinct int32
+ sint64* = distinct int64
+ bytes* = distinct string
+
+ # int32 # varint, no zigzag
+ # int64 # varint, no zigzag
+ # uint32 # varint, no zigzag
+ # uint64 # varint, no zigzag
+ # fixed32 # 32-bit uint
+ # fixed64 # 64-bit uint
+ # sfixed32 # 32-bit int
+ # sfixed64 # 64-bit int
+ # sint32 # varint, zigzag
+ # sint64 # varint, zigzag
+ # float32 # fixed32
+ # float64 # fixed64
+
+ WireType* {.pure.} = enum
+ Varint = 0
+ Fixed64 = 1
+ LengthDelimited = 2
+ StartGroup = 3
+ EndGroup = 4
+ Fixed32 = 5
+
+ FieldType* {.pure.} = enum
+ Double = 1
+ Float
+ Int64
+ UInt64
+ Int32
+ Fixed64
+ Fixed32
+ Bool
+ String
+ Group
+ Message
+ Bytes
+ UInt32
+ Enum
+ SFixed32
+ SFixed64
+ SInt32
+ SInt64
+
+proc wiretype*(ft: FieldType): WireType =
+ case ft
+ of FieldType.Double: result = WireType.Fixed64
+ of FieldType.Float: result = WireType.Fixed32
+ of FieldType.Int64: result = WireType.Varint
+ of FieldType.UInt64: result = WireType.Varint
+ of FieldType.Int32: result = WireType.Varint
+ of FieldType.Fixed64: result = WireType.Fixed64
+ of FieldType.Fixed32: result = WireType.Fixed32
+ of FieldType.Bool: result = WireType.Varint
+ of FieldType.String: result = WireType.LengthDelimited
+ of FieldType.Group: result = WireType.LengthDelimited # ???
+ of FieldType.Message: result = WireType.LengthDelimited
+ of FieldType.Bytes: result = WireType.LengthDelimited
+ of FieldType.UInt32: result = WireType.Varint
+ of FieldType.Enum: result = WireType.Varint
+ of FieldType.SFixed32: result = WireType.Fixed32
+ of FieldType.SFixed64: result = WireType.Fixed64
+ of FieldType.SInt32: result = WireType.Varint
+ of FieldType.SInt64: result = WireType.Varint
+
+proc isNumeric*(wiretype: WireType): bool =
+ case wiretype
+ of WireType.Varint: result = true
+ of WireType.Fixed64: result = true
+ of WireType.Fixed32: result = true
+ else: result = false
+
+proc isNumeric*(ft: FieldType): bool =
+ result = isNumeric(wiretype(ft))