diff --git a/protobuf_serialization.nim b/protobuf_serialization.nim index 4b2a270..8cbf33b 100644 --- a/protobuf_serialization.nim +++ b/protobuf_serialization.nim @@ -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() \ No newline at end of file diff --git a/protobuf_serialization.nimble b/protobuf_serialization.nimble index 90b1304..8512041 100644 --- a/protobuf_serialization.nimble +++ b/protobuf_serialization.nimble @@ -11,4 +11,4 @@ skipDirs = @["tests"] # Dependencies -requires "nim >= 1.0.6" +requires "nim >= 1.0.6", "faststreams"