Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions protobuf_serialization/codec.nim
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type
fixed32 | sfixed32 | pfloat

# Mappings of proto type to wire type
# https://protobuf.dev/programming-guides/encoding/#cheat-sheet
SomeVarint* =
pint32 | pint64 | puint32 | puint64 | sint32 | sint64 | pbool | penum
SomeFixed64* = fixed64 | sfixed64 | pdouble
Expand Down Expand Up @@ -246,6 +247,7 @@ proc skipValue*[T: SomeFixed32 | SomeFixed64](input: InputStream, _: type T) {.r
static: doAssert sizeof(T) in {4, 8}
input.skipBytes(sizeof(T))

# https://protobuf.dev/programming-guides/encoding/#cheat-sheet
# https://protobuf.dev/programming-guides/encoding/#length-types
# https://protobuf.dev/programming-guides/proto-limits/#total
proc readLength*(input: InputStream): int {.raises: [SerializationError, IOError].} =
Expand Down
9 changes: 9 additions & 0 deletions tests/conformance/conformance_nim.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# nim-protobuf-serialization
# Copyright (c) 2026 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

import os
import ../../protobuf_serialization
import ../../protobuf_serialization/files/type_generator
Expand Down
11 changes: 11 additions & 0 deletions tests/test_all.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# nim-protobuf-serialization
# Copyright (c) 2026 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

{.warning[UnusedImport]: off}

import ../protobuf_serialization

import
test_all_types,
test_bool,
test_codec,
test_fixed,
Expand All @@ -12,5 +22,6 @@ import
test_repeated,
test_protobuf2_semantics,
test_thirty_three_fields,
test_truncation,
test_wire_type_mismatch,
files/test_proto3
165 changes: 165 additions & 0 deletions tests/test_all_types.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import unittest2

import ./utils, ../protobuf_serialization

type
StringObj {.proto3.} = object
x {.fieldNumber: 1.}: string

BytesObj {.proto3.} = object
x {.fieldNumber: 1.}: seq[byte]

PInt32Obj {.proto3.} = object
x {.fieldNumber: 1, pint.}: int32

PUInt32Obj {.proto3.} = object
x {.fieldNumber: 1, pint.}: uint32

PInt64Obj {.proto3.} = object
x {.fieldNumber: 1, pint.}: int64

PUInt64Obj {.proto3.} = object
x {.fieldNumber: 1, pint.}: uint64

SInt32Obj {.proto3.} = object
x {.fieldNumber: 1, sint.}: int32

SInt64Obj {.proto3.} = object
x {.fieldNumber: 1, sint.}: int64

FixedInt32Obj {.proto3.} = object
x {.fieldNumber: 1, fixed.}: int32

FixedInt64Obj {.proto3.} = object
x {.fieldNumber: 1, fixed.}: int64

FixedUInt32Obj {.proto3.} = object
x {.fieldNumber: 1, fixed.}: uint32

FixedUInt64Obj {.proto3.} = object
x {.fieldNumber: 1, fixed.}: uint64

Float32Obj {.proto3.} = object
x {.fieldNumber: 1.}: float32

Float64Obj {.proto3.} = object
x {.fieldNumber: 1.}: float64

suite "Test String Encoding/Decoding":
test "Non-empty string values":
# echo "0a0161" | xxd -r -p | protoc --decode=StringObj test_all_types.proto
# echo 'x: "a"' | protoc --encode=StringObj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(StringObj(x: "a"), "0a0161")
roundtrip(StringObj(x: "hi"), "0a026869")
roundtrip(StringObj(x: "abc"), "0a03616263")
roundtrip(StringObj(x: "ü"), "0a02c3bc")
roundtrip(StringObj(x: "水"), "0a03e6b0b4")
roundtrip(StringObj(x: "𐅑"), "0a04f0908591")

test "Empty string is default":
check Protobuf.encode(StringObj()).len == 0

suite "Test Bytes Encoding/Decoding":
test "Non-empty byte sequences":
# echo "0a0101" | xxd -r -p | protoc --decode=BytesObj test_all_types.proto
# echo 'x: "\001"' | protoc --encode=BytesObj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(BytesObj(x: @[0x01'u8]), "0a0101")
roundtrip(BytesObj(x: @[0x01'u8, 0x02, 0x03]), "0a03010203")
roundtrip(BytesObj(x: @[0xFF'u8, 0x00, 0xFF]), "0a03ff00ff")

test "Empty bytes is default":
check Protobuf.encode(BytesObj()).len == 0

suite "Test pint Encoding/Decoding":
test "pint int32 single-byte varint":
# echo "0801" | xxd -r -p | protoc --decode=PInt32Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=PInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(PInt32Obj(x: 1'i32), "0801")
roundtrip(PInt32Obj(x: 127'i32), "087f")

test "pint int32 multi-byte varint":
# echo "088001" | xxd -r -p | protoc --decode=PInt32Obj test_all_types.proto
# echo 'x: 128' | protoc --encode=PInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(PInt32Obj(x: 128'i32), "088001")
roundtrip(PInt32Obj(x: 300'i32), "08ac02")
roundtrip(PInt32Obj(x: int32.high), "08ffffffff07")

test "pint uint32 max value":
# echo "08ffffffff0f" | xxd -r -p | protoc --decode=PUInt32Obj test_all_types.proto
# echo 'x: 4294967295' | protoc --encode=PUInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(PUInt32Obj(x: uint32.high), "08ffffffff0f")

test "pint int64 max value":
# echo "08ffffffffffffffff7f" | xxd -r -p | protoc --decode=PInt64Obj test_all_types.proto
# echo 'x: 9223372036854775807' | protoc --encode=PInt64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(PInt64Obj(x: int64.high), "08ffffffffffffffff7f")

test "pint uint64 max value":
# echo "08ffffffffffffffffff01" | xxd -r -p | protoc --decode=PUInt64Obj test_all_types.proto
# echo 'x: 18446744073709551615' | protoc --encode=PUInt64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(PUInt64Obj(x: uint64.high), "08ffffffffffffffffff01")

suite "Test sint Encoding/Decoding":
test "sint int32 positive values":
# echo "0802" | xxd -r -p | protoc --decode=SInt32Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=SInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(SInt32Obj(x: 1'i32), "0802")
roundtrip(SInt32Obj(x: 63'i32), "087e")

test "sint int32 negative values":
# echo "0801" | xxd -r -p | protoc --decode=SInt32Obj test_all_types.proto
# echo 'x: -1' | protoc --encode=SInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(SInt32Obj(x: -1'i32), "0801")
roundtrip(SInt32Obj(x: -2'i32), "0803")

test "sint int32 boundary values":
# echo "08feffffff0f" | xxd -r -p | protoc --decode=SInt32Obj test_all_types.proto
# echo 'x: 2147483647' | protoc --encode=SInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(SInt32Obj(x: int32.high), "08feffffff0f")
roundtrip(SInt32Obj(x: int32.low), "08ffffffff0f")

test "sint int64 negative values":
# echo "0801" | xxd -r -p | protoc --decode=SInt64Obj test_all_types.proto
# echo 'x: -1' | protoc --encode=SInt64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(SInt64Obj(x: -1'i64), "0801")
roundtrip(SInt64Obj(x: int64.low), "08ffffffffffffffffff01")

suite "Test Fixed-Width Encoding/Decoding":
test "fixed int32 (sfixed32)":
# echo "0d01000000" | xxd -r -p | protoc --decode=FixedInt32Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=FixedInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(FixedInt32Obj(x: 1'i32), "0d01000000")
roundtrip(FixedInt32Obj(x: -1'i32), "0dffffffff")
roundtrip(FixedInt32Obj(x: int32.high), "0dffffff7f")
roundtrip(FixedInt32Obj(x: int32.low), "0d00000080")

test "fixed int64 (sfixed64)":
# echo "090100000000000000" | xxd -r -p | protoc --decode=FixedInt64Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=FixedInt64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(FixedInt64Obj(x: 1'i64), "090100000000000000")
roundtrip(FixedInt64Obj(x: -1'i64), "09ffffffffffffffff")

test "fixed uint32 (fixed32)":
# echo "0d01000000" | xxd -r -p | protoc --decode=FixedUInt32Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=FixedUInt32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(FixedUInt32Obj(x: 1'u32), "0d01000000")
roundtrip(FixedUInt32Obj(x: uint32.high), "0dffffffff")

test "fixed uint64 (fixed64)":
# echo "090100000000000000" | xxd -r -p | protoc --decode=FixedUInt64Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=FixedUInt64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(FixedUInt64Obj(x: 1'u64), "090100000000000000")
roundtrip(FixedUInt64Obj(x: uint64.high), "09ffffffffffffffff")

suite "Test Float Encoding/Decoding":
test "float32 values":
# echo "0d0000803f" | xxd -r -p | protoc --decode=Float32Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=Float32Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(Float32Obj(x: 1.0'f32), "0d0000803f")
roundtrip(Float32Obj(x: -1.0'f32), "0d000080bf")

test "float64 values":
# echo "09000000000000f03f" | xxd -r -p | protoc --decode=Float64Obj test_all_types.proto
# echo 'x: 1' | protoc --encode=Float64Obj test_all_types.proto | hexdump -ve '1/1 "%.2x"'
roundtrip(Float64Obj(x: 1.0'f64), "09000000000000f03f")
roundtrip(Float64Obj(x: -1.0'f64), "09000000000000f0bf")
57 changes: 57 additions & 0 deletions tests/test_all_types.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
syntax = "proto3";

message StringObj {
string x = 1;
}

message BytesObj {
bytes x = 1;
}

message PInt32Obj {
int32 x = 1;
}

message PUInt32Obj {
uint32 x = 1;
}

message PInt64Obj {
int64 x = 1;
}

message PUInt64Obj {
uint64 x = 1;
}

message SInt32Obj {
sint32 x = 1;
}

message SInt64Obj {
sint64 x = 1;
}

message FixedInt32Obj {
sfixed32 x = 1;
}

message FixedInt64Obj {
sfixed64 x = 1;
}

message FixedUInt32Obj {
fixed32 x = 1;
}

message FixedUInt64Obj {
fixed64 x = 1;
}

message Float32Obj {
float x = 1;
}

message Float64Obj {
double x = 1;
}
46 changes: 30 additions & 16 deletions tests/test_bool.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# nim-protobuf-serialization
# Copyright (c) 2026 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.

import unittest2

import ../protobuf_serialization
import ./utils, ../protobuf_serialization

type
PIntType {.proto3.} = object
Expand All @@ -15,28 +24,33 @@ type
BoolType {.proto3.} = object
x {.fieldNumber: 1.}: bool

proc writeRead[W, R](toWrite: W, value: R) =
let encoded = Protobuf.encode(toWrite)
check:
encoded.len == Protobuf.computeSize(toWrite)
Protobuf.decode(encoded, R) == value

suite "Test Boolean Encoding/Decoding":
test "Can encode/decode boolean without subtype specification":
writeRead(BoolType(x: true), BoolType(x: true))
writeRead(BoolType(x: false), BoolType(x: false))
# echo "0801" | xxd -r -p | protoc --decode=BoolType test_bool.proto
# x: true
# echo "x: true" | protoc --encode=BoolType test_bool.proto | hexdump -ve '1/1 "%.2x"'
# 0801
roundtrip(BoolType(x: true), BoolType(x: true), "0801")
roundtrip(BoolType(x: false), BoolType(x: false), "")

#Skipping subtype specification only works when every encoding has the same truthiness.
#That's what this tests. It should be noted 1 encodes as 1/1/2 for the following.
test "Can encode/decode boolean as signed VarInt":
writeRead(PIntType(x: 1), BoolType(x: true))
writeRead(PIntType(x: 0), BoolType(x: false))
# echo "x: 1" | protoc --encode=PIntType test_bool.proto | hexdump -ve '1/1 "%.2x"'
# 0801
roundtrip(PIntType(x: 1), BoolType(x: true), "0801")
roundtrip(PIntType(x: 0), BoolType(x: false), "")

test "Can encode/decode boolean as unsigned VarInt":
writeRead(UIntType(x: 1), BoolType(x: true))
writeRead(UIntType(x: 0), BoolType(x: false))
# echo "x: 1" | protoc --encode=UIntType test_bool.proto | hexdump -ve '1/1 "%.2x"'
# 0801
roundtrip(UIntType(x: 1), BoolType(x: true), "0801")
roundtrip(UIntType(x: 0), BoolType(x: false), "")

test "Can encode/decode boolean as zig-zagged VarInt":
# TODO 1 encodes as 2 in zig-zah - should we truncate? see `readVarint`
writeRead(SIntType(x: 1), BoolType(x: true))
writeRead(SIntType(x: 0), BoolType(x: false))
# echo "x: 1" | protoc --encode=SIntType test_bool.proto | hexdump -ve '1/1 "%.2x"'
# 0802
# echo "0802" | xxd -r -p | protoc --decode=BoolType test_bool.proto
# x: true
roundtrip(SIntType(x: 1), BoolType(x: true), "0802")
roundtrip(SIntType(x: 0), BoolType(x: false), "")
17 changes: 17 additions & 0 deletions tests/test_bool.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";

message PIntType {
int32 x = 1;
}

message UIntType {
uint32 x = 1;
}

message SIntType {
sint32 x = 1;
}

message BoolType {
bool x = 1;
}
4 changes: 2 additions & 2 deletions tests/test_codec.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nim-Libp2p
# Copyright (c) 2022 Status Research & Development GmbH
# nim-protobuf-serialization
# Copyright (c) 2026 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
Expand Down
Loading
Loading