Skip to content

Commit

Permalink
feat: representation of full uint64 range in & out of cbor
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed May 12, 2022
1 parent 37f875b commit fd593b3
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 15 deletions.
32 changes: 26 additions & 6 deletions codec/dagcbor/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
// except for the `case datamodel.Kind_Link` block,
// which is dag-cbor's special sauce for schemafree links.

type uintNode interface {
UInt() uint64
Negative() bool
}

// EncodeOptions can be used to customize the behavior of an encoding function.
// The Encode method on this struct fits the codec.Encoder function interface.
type EncodeOptions struct {
Expand Down Expand Up @@ -99,13 +104,28 @@ func marshal(n datamodel.Node, tk *tok.Token, sink shared.TokenSink, options Enc
_, err = sink.Step(tk)
return err
case datamodel.Kind_Int:
v, err := n.AsInt()
if err != nil {
return err
var v uint64
var negative bool
if uin, ok := n.(uintNode); ok {
v = uin.UInt()
negative = uin.Negative()
} else {
i, err := n.AsInt()
if err != nil {
return err
}
sign := (i >> 63)
v = uint64((i ^ sign) - sign)
negative = sign == 1
}
tk.Type = tok.TInt
tk.Int = int64(v)
_, err = sink.Step(tk)
if negative {
tk.Type = tok.TInt
tk.Int = -int64(v)
} else {
tk.Type = tok.TUint
tk.Uint = v
}
_, err := sink.Step(tk)
return err
case datamodel.Kind_Float:
v, err := n.AsFloat()
Expand Down
3 changes: 2 additions & 1 deletion codec/dagcbor/unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime/node/basicnode"
)

var (
Expand Down Expand Up @@ -275,7 +276,7 @@ func unmarshal2(na datamodel.NodeAssembler, tokSrc shared.TokenSource, tk *tok.T
if *gas < 0 {
return ErrAllocationBudgetExceeded
}
return na.AssignInt(int64(tk.Uint)) // FIXME overflow check
return na.AssignNode(basicnode.NewUInt(tk.Uint, false))
case tok.TFloat64:
*gas -= 1
if *gas < 0 {
Expand Down
27 changes: 27 additions & 0 deletions codec/dagcbor/unmarshal_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dagcbor

import (
"bytes"
"encoding/hex"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -72,3 +74,28 @@ func TestFunBlocks(t *testing.T) {
qt.Assert(t, err, qt.Equals, ErrTrailingBytes)
})
}

func TestInts(t *testing.T) {
buf, err := hex.DecodeString("1bffffffffffffffff") // max uint64
qt.Assert(t, err, qt.IsNil)
nb := basicnode.Prototype.Any.NewBuilder()
err = Decode(nb, bytes.NewReader(buf))
qt.Assert(t, err, qt.IsNil)
n := nb.Build()

// the overflowed AsInt() int64 cast
ii, err := n.AsInt()
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, ii, qt.Equals, int64(-1)) // NOPE!

// get real, underlying value
uin, ok := n.(uintNode)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, uin.UInt(), qt.Equals, uint64(1<<64-1))
qt.Assert(t, uin.Negative(), qt.IsFalse)

var byts bytes.Buffer
err = Encode(n, &byts)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, hex.EncodeToString(byts.Bytes()), qt.Equals, "1bffffffffffffffff")
}
39 changes: 33 additions & 6 deletions node/basicnode/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,34 @@ import (
)

var (
_ datamodel.Node = plainInt(0)
_ datamodel.Node = plainInt{0, false}
_ datamodel.NodePrototype = Prototype__Int{}
_ datamodel.NodeBuilder = &plainInt__Builder{}
_ datamodel.NodeAssembler = &plainInt__Assembler{}
)

func newPlainInt(value int64) *plainInt {
sign := (value >> 63)
return &plainInt{
value: uint64((value ^ sign) - sign),
negative: sign == 1,
}
}

func NewInt(value int64) datamodel.Node {
v := plainInt(value)
return newPlainInt(value)
}

func NewUInt(value uint64, negative bool) datamodel.Node {
v := plainInt{value, negative}
return &v
}

// plainInt is a simple boxed int that complies with datamodel.Node.
type plainInt int64
type plainInt struct {
value uint64
negative bool
}

// -- Node interface methods -->

Expand Down Expand Up @@ -56,7 +71,10 @@ func (plainInt) AsBool() (bool, error) {
return mixins.Int{TypeName: "int"}.AsBool()
}
func (n plainInt) AsInt() (int64, error) {
return int64(n), nil
if n.negative {
return -int64(n.value), nil
}
return int64(n.value), nil
}
func (plainInt) AsFloat() (float64, error) {
return mixins.Int{TypeName: "int"}.AsFloat()
Expand All @@ -74,6 +92,15 @@ func (plainInt) Prototype() datamodel.NodePrototype {
return Prototype__Int{}
}

// special methods for plainInt to allow accessing the uint64 range

func (n plainInt) UInt() uint64 {
return n.value
}
func (n plainInt) Negative() bool {
return n.negative
}

// -- NodePrototype -->

type Prototype__Int struct{}
Expand Down Expand Up @@ -116,7 +143,7 @@ func (plainInt__Assembler) AssignBool(bool) error {
return mixins.IntAssembler{TypeName: "int"}.AssignBool(false)
}
func (na *plainInt__Assembler) AssignInt(v int64) error {
*na.w = plainInt(v)
*na.w = *newPlainInt(v)
return nil
}
func (plainInt__Assembler) AssignFloat(float64) error {
Expand All @@ -135,7 +162,7 @@ func (na *plainInt__Assembler) AssignNode(v datamodel.Node) error {
if v2, err := v.AsInt(); err != nil {
return err
} else {
*na.w = plainInt(v2)
*na.w = *newPlainInt(v2)
return nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion node/basicnode/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func (lva *plainList__ValueAssembler) AssignBool(v bool) error {
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignInt(v int64) error {
vb := plainInt(v)
vb := *newPlainInt(v)
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignFloat(v float64) error {
Expand Down
2 changes: 1 addition & 1 deletion node/basicnode/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func (mva *plainMap__ValueAssembler) AssignBool(v bool) error {
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignInt(v int64) error {
vb := plainInt(v)
vb := *newPlainInt(v)
return mva.AssignNode(&vb)
}
func (mva *plainMap__ValueAssembler) AssignFloat(v float64) error {
Expand Down

0 comments on commit fd593b3

Please sign in to comment.