From f8a70231411a74b6d69d1b00da157b0e92d3b555 Mon Sep 17 00:00:00 2001 From: Philipp Jahn Date: Tue, 25 Nov 2025 15:21:44 +0100 Subject: [PATCH] added Nint MajorType and convenience method for int64 --- README.md | 1 + major.go | 1 + primitives.go | 52 ++++++++++++++++- primitives_test.go | 136 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24d7500..0022db1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ developed to be used in [`dtn7-go`][dtn7-go], an implementation of the - Supports a selected subset of [CBOR's][cbor] features: - Unsigned Integer + - Negative Integer - Floating-point values - Byte and Text String - Arrays, both of definite and indefinite length diff --git a/major.go b/major.go index 4171ed6..16c508f 100644 --- a/major.go +++ b/major.go @@ -10,6 +10,7 @@ type MajorType = byte const ( UInt MajorType = 0x00 + NInt MajorType = 0x20 ByteString MajorType = 0x40 TextString MajorType = 0x60 Array MajorType = 0x80 diff --git a/primitives.go b/primitives.go index ee6c305..92df9ea 100644 --- a/primitives.go +++ b/primitives.go @@ -1,6 +1,9 @@ package cboring -import "io" +import ( + "fmt" + "io" +) /*** Uint ***/ @@ -14,6 +17,53 @@ func WriteUInt(n uint64, w io.Writer) error { return WriteMajors(UInt, n, w) } +/*** Nint ***/ + +// ReadNInt expects a negative integer at the Reader's position and returns -N - 1, with N the actual number. +// when read into int64 N = int64(^n) check if value is negative +func ReadNInt(r io.Reader) (n uint64, err error) { + return ReadExpectMajors(NInt, r) +} + +// WriteNInt serializes a negative integer into the Writer. n has to be -N - 1, with N the actual number. +// when used on int64 use n = uint64(^N) +func WriteNInt(n uint64, w io.Writer) error { + return WriteMajors(NInt, n, w) +} + +/*** int ***/ + +// ReadInt expects either an unsigned or negative integer at the Reader's position and returns it if the value fits int64. +func ReadInt(r io.Reader) (n int64, err error) { + major, num, err := ReadMajors(r) + n = int64(num) + if n < 0 { + if major == UInt { + err = fmt.Errorf("ReadInt: Returned Integer %d to big for int64", num) + } else if major == NInt { + err = fmt.Errorf("ReadInt: Returned Integer -%d to small for int64", + num+1) // might overflow but highly unlikely + } else { + err = fmt.Errorf("ReadInt: Wrong Major Type: 0x%x instead of 0x00 or 0x20", + major) + } + } else if major == NInt { + n = ^n + } else if major != UInt { + err = fmt.Errorf("ReadInt: Wrong Major Type: 0x%x instead of 0x00 or 0x20", + major) + } + return +} + +// WriteInt serializes an integer into the Writer, either as UInt or NInt. +func WriteInt(n int64, w io.Writer) error { + if n < 0 { + return WriteNInt(uint64(^n), w) + } + return WriteUInt(uint64(n), w) +} + /*** ByteString ***/ // ReadByteStringLen expects a byte string at the Reader's position and returns diff --git a/primitives_test.go b/primitives_test.go index 8856c45..b4829e1 100644 --- a/primitives_test.go +++ b/primitives_test.go @@ -67,6 +67,142 @@ func TestReadUIntError(t *testing.T) { } } +/*** NInt ***/ + +func TestNInt(t *testing.T) { + tests := []struct { + data []byte + numb int64 + }{ + {[]byte{0x20}, -1}, + {[]byte{0x29}, -10}, + {[]byte{0x36}, -23}, + {[]byte{0x37}, -24}, + {[]byte{0x38, 0x18}, -25}, + {[]byte{0x38, 0x63}, -100}, + {[]byte{0x39, 0x03, 0xe7}, -1000}, + {[]byte{0x3a, 0x00, 0x0f, 0x42, 0x3F}, -1000000}, + {[]byte{0x3b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x0F, 0xFF}, -1000000000000}, + {[]byte{0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0}, /* can not represent -2^64 + but underflow would lead to 0, so this is the best representation */ + } + + for _, test := range tests { + // Read + buff := bytes.NewBuffer(test.data) + if n, err := ReadNInt(buff); err != nil { + t.Fatal(err) + } else if int64(^n) != test.numb { + t.Fatalf("Resulting negative int %d is not %d", int64(^n), test.numb) + } + + // Write + buff.Reset() + if err := WriteNInt(uint64(^test.numb), buff); err != nil { + t.Fatal(err) + } + + if bb := buff.Bytes(); !reflect.DeepEqual(bb, test.data) { + t.Fatalf("Serialized data mismatches: %x != %x", bb, test.data) + } + } +} + +func TestReadNIntError(t *testing.T) { + tests := [][]byte{ + // Wrong major type + {0xFF}, + // Wrong additionals for major type 1 + {NInt | 0x1F}, + // Empty stream + {}, + // Incomplete streams + {NInt | 0x18}, {0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + } + + for _, test := range tests { + r := bytes.NewBuffer(test) + if _, err := ReadNInt(r); err == nil { + t.Fatalf("Illegal input %x did not errored", test) + } + } +} + +/*** Int ***/ + +func TestInt(t *testing.T) { + tests := []struct { + data []byte + numb int64 + }{ + {[]byte{0x00}, 0}, + {[]byte{0x01}, 1}, + {[]byte{0x0a}, 10}, + {[]byte{0x17}, 23}, + {[]byte{0x18, 0x18}, 24}, + {[]byte{0x18, 0x19}, 25}, + {[]byte{0x18, 0x64}, 100}, + {[]byte{0x19, 0x03, 0xe8}, 1000}, + {[]byte{0x1a, 0x00, 0x0f, 0x42, 0x40}, 1000000}, + {[]byte{0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}, 1000000000000}, + {[]byte{0x20}, -1}, + {[]byte{0x29}, -10}, + {[]byte{0x36}, -23}, + {[]byte{0x37}, -24}, + {[]byte{0x38, 0x18}, -25}, + {[]byte{0x38, 0x63}, -100}, + {[]byte{0x39, 0x03, 0xe7}, -1000}, + {[]byte{0x3a, 0x00, 0x0f, 0x42, 0x3F}, -1000000}, + {[]byte{0x3b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x0F, 0xFF}, -1000000000000}, + } + + for _, test := range tests { + // Read + buff := bytes.NewBuffer(test.data) + if n, err := ReadInt(buff); err != nil { + t.Fatal(err) + } else if n != test.numb { + t.Fatalf("Resulting int %d is not %d", n, test.numb) + } + + // Write + buff.Reset() + if err := WriteInt(test.numb, buff); err != nil { + t.Fatal(err) + } + + if bb := buff.Bytes(); !reflect.DeepEqual(bb, test.data) { + t.Fatalf("Serialized data mismatches: %x != %x", bb, test.data) + } + } +} + +func TestReadIntError(t *testing.T) { + tests := [][]byte{ + // Wrong major type + {0xFF}, + // Wrong additionals for major type 0 + {0x1F}, + // Wrong additionals for major type 1 + {NInt | 0x1F}, + // Empty stream + {}, + // Incomplete streams + {0x18}, {0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + // Number to big + {0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + // Number to small + {NInt | 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + } + + for _, test := range tests { + r := bytes.NewBuffer(test) + if _, err := ReadInt(r); err == nil { + t.Fatalf("Illegal input %x did not errored", test) + } + } +} + /*** ByteString ***/ func TestByteStringLen(t *testing.T) {