Unverified Commit 9f788466 authored by Rod Vagg's avatar Rod Vagg

untangle marshal

parent c93d8a66
...@@ -88,6 +88,18 @@ func validate(t *testing.T, actual ipld.Node, expected *pb.PBNode) { ...@@ -88,6 +88,18 @@ func validate(t *testing.T, actual ipld.Node, expected *pb.PBNode) {
func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) { func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) {
byts, _ := hex.DecodeString(bytsHex) byts, _ := hex.DecodeString(bytsHex)
roundTrip := func(t *testing.T, node ipld.Node) {
var buf bytes.Buffer
if err := Marshal(node, &buf); err != nil {
t.Fatal(err)
}
// fmt.Printf("CMP\n\tFrom: %v\n\tTo: %v\n", hex.EncodeToString(byts), hex.EncodeToString(buf.Bytes()))
if bytes.Compare(buf.Bytes(), byts) != 0 {
t.Fatal("Round-trip resulted in different bytes")
}
}
t.Run("basicnode", func(t *testing.T) { t.Run("basicnode", func(t *testing.T) {
nb := basicnode.Prototype__Map{}.NewBuilder() nb := basicnode.Prototype__Map{}.NewBuilder()
err := Unmarshal(nb, bytes.NewReader(byts)) err := Unmarshal(nb, bytes.NewReader(byts))
...@@ -97,15 +109,7 @@ func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) { ...@@ -97,15 +109,7 @@ func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) {
node := nb.Build() node := nb.Build()
validate(t, node, expected) validate(t, node, expected)
roundTrip(t, node)
var buf bytes.Buffer
if err := Marshal(node, &buf); err != nil {
t.Fatal(err)
}
if bytes.Compare(buf.Bytes(), byts) != 0 {
t.Fatal("Round-trip resulted in different bytes")
}
}) })
t.Run("typed", func(t *testing.T) { t.Run("typed", func(t *testing.T) {
...@@ -114,7 +118,9 @@ func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) { ...@@ -114,7 +118,9 @@ func runTest(t *testing.T, bytsHex string, expected *pb.PBNode) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
validate(t, nb.Build(), expected) node := nb.Build()
validate(t, node, expected)
roundTrip(t, node)
}) })
} }
......
...@@ -3,133 +3,195 @@ package dagpb ...@@ -3,133 +3,195 @@ package dagpb
import ( import (
"fmt" "fmt"
"io" "io"
math_bits "math/bits"
"sort"
"github.com/ipfs/go-cid"
ipld "github.com/ipld/go-ipld-prime" ipld "github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid" cidlink "github.com/ipld/go-ipld-prime/linking/cid"
pb "github.com/rvagg/go-dagpb/pb"
) )
type pbLink struct {
hash cid.Cid
name string
hasName bool
tsize uint64
hasTsize bool
}
func Marshal(inNode ipld.Node, out io.Writer) error { func Marshal(inNode ipld.Node, out io.Writer) error {
// Wrap in a typed node for some basic schema form checking
builder := Type.PBNode.NewBuilder() builder := Type.PBNode.NewBuilder()
if err := builder.AssignNode(inNode); err != nil { if err := builder.AssignNode(inNode); err != nil {
return err return err
} }
node := builder.Build() node := builder.Build()
curField := pb.TypeLinks links, err := node.LookupByString("Links")
var linksIter ipld.ListIterator if err != nil {
var link ipld.Node return err
}
tokenSource := func() (pb.Token, error) { if links.Length() > 0 {
if curField == pb.TypeLinks { // collect links into a slice so we can properly sort for encoding
links, err := node.LookupByString("Links") pbLinks := make([]pbLink, links.Length())
if err != nil {
return pb.Token{}, err
}
if links.Length() == 0 {
curField = pb.TypeData
} else {
curField = pb.TypeHash
linksIter = links.ListIterator()
_, link, err = linksIter.Next()
if err != nil {
return pb.Token{}, err
}
}
}
if curField == pb.TypeData {
curField = pb.TypeEnd
d, err := node.LookupByString("Data") linksIter := links.ListIterator()
for !linksIter.Done() {
ii, link, err := linksIter.Next()
if err != nil { if err != nil {
return pb.Token{}, err return err
}
if !d.IsAbsent() {
b, err := d.AsBytes()
if err != nil {
return pb.Token{}, err
}
return pb.Token{Type: pb.TypeData, Bytes: b}, nil
} }
}
if curField == pb.TypeEnd {
return pb.Token{Type: pb.TypeEnd}, nil
}
for {
if curField == pb.TypeHash {
curField = pb.TypeName
{ // Hash
d, err := link.LookupByString("Hash") d, err := link.LookupByString("Hash")
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
// TODO:
// return 0, fmt.Errorf("invalid DAG-PB form (link must have a Hash)")
l, err := d.AsLink() l, err := d.AsLink()
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
if cl, ok := l.(cidlink.Link); ok { if err != nil {
return pb.Token{Type: pb.TypeHash, Cid: &cl.Cid}, nil return err
} }
return pb.Token{}, fmt.Errorf("unexpected Link type [%v]", l) cl, ok := l.(cidlink.Link)
if !ok {
return fmt.Errorf("unexpected Link type [%v]", l)
}
pbLinks[ii].hash = cl.Cid
} }
if curField == pb.TypeName { { // Name
curField = pb.TypeTSize
nameNode, err := link.LookupByString("Name") nameNode, err := link.LookupByString("Name")
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
if !nameNode.IsAbsent() { if !nameNode.IsAbsent() {
name, err := nameNode.AsString() name, err := nameNode.AsString()
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
return pb.Token{Type: pb.TypeName, Bytes: []byte(name)}, nil pbLinks[ii].name = name
pbLinks[ii].hasName = true
} }
} }
if curField == pb.TypeTSize { { // Tsize
curField = pb.TypeLinkEnd
tsizeNode, err := link.LookupByString("Tsize") tsizeNode, err := link.LookupByString("Tsize")
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
if !tsizeNode.IsAbsent() { if !tsizeNode.IsAbsent() {
tsize, err := tsizeNode.AsInt() tsize, err := tsizeNode.AsInt()
if err != nil { if err != nil {
return pb.Token{}, err return err
} }
if tsize < 0 { if tsize < 0 {
return pb.Token{}, fmt.Errorf("Link has negative Tsize value [%v]", tsize) return fmt.Errorf("Link has negative Tsize value [%v]", tsize)
} }
return pb.Token{Type: pb.TypeTSize, Int: uint64(tsize)}, nil utsize := uint64(tsize)
pbLinks[ii].tsize = utsize
pbLinks[ii].hasTsize = true
} }
} }
} // for
if curField == pb.TypeLinkEnd {
if linksIter.Done() { sortLinks(pbLinks)
curField = pb.TypeData for _, link := range pbLinks {
} else { size := link.encodedSize()
curField = pb.TypeHash chunk := make([]byte, size+sizeOfVarint(uint64(size))+1)
var err error chunk[0] = 0x12
_, link, err = linksIter.Next() offset := encodeVarint(chunk, 1, uint64(size))
if err != nil { wrote, err := link.marshal(chunk, offset)
return pb.Token{}, err if err != nil {
} return err
}
return pb.Token{Type: pb.TypeLinkEnd}, nil
} }
if wrote != size {
if curField != pb.TypeHash { return fmt.Errorf("bad PBLink marshal, wrote wrong number of bytes")
return pb.Token{}, fmt.Errorf("unexpected and invalid token state")
} }
out.Write(chunk)
}
} // if links
data, err := node.LookupByString("Data")
if err != nil {
return err
}
if !data.IsAbsent() {
byts, err := data.AsBytes()
if err != nil {
return err
} }
size := uint64(len(byts))
lead := make([]byte, sizeOfVarint(size)+1)
lead[0] = 0xa
encodeVarint(lead, 1, size)
out.Write(lead)
out.Write(byts)
}
return nil
}
func (link pbLink) encodedSize() (n int) {
l := link.hash.ByteLen()
n += 1 + l + sizeOfVarint(uint64(l))
if link.hasName {
l = len(link.name)
n += 1 + l + sizeOfVarint(uint64(l))
}
if link.hasTsize {
n += 1 + sizeOfVarint(uint64(link.tsize))
}
return n
}
func (link pbLink) marshal(data []byte, offset int) (int, error) {
base := offset
data[offset] = 0xa
byts := link.hash.Bytes()
offset = encodeVarint(data, offset+1, uint64(len(byts)))
copy(data[offset:], byts)
offset += len(byts)
if link.hasName {
data[offset] = 0x12
offset = encodeVarint(data, offset+1, uint64(len(link.name)))
copy(data[offset:], link.name)
offset += len(link.name)
} }
if link.hasTsize {
data[offset] = 0x18
offset = encodeVarint(data, offset+1, uint64(link.tsize))
}
return offset - base, nil
}
func encodeVarint(data []byte, offset int, v uint64) int {
for v >= 1<<7 {
data[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
data[offset] = uint8(v)
return offset + 1
}
func sortLinks(links []pbLink) {
sort.Stable(pbLinkSlice(links))
}
type pbLinkSlice []pbLink
func (ls pbLinkSlice) Len() int { return len(ls) }
func (ls pbLinkSlice) Swap(a, b int) { ls[a], ls[b] = ls[b], ls[a] }
func (ls pbLinkSlice) Less(a, b int) bool { return pbLinkLess(ls[a], ls[b]) }
func pbLinkLess(a pbLink, b pbLink) bool {
return a.name < b.name
}
return pb.Marshal(out, tokenSource) func sizeOfVarint(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
} }
...@@ -60,7 +60,11 @@ func NewPBLink(name string, c cid.Cid, tsize uint64) *PBLink { ...@@ -60,7 +60,11 @@ func NewPBLink(name string, c cid.Cid, tsize uint64) *PBLink {
} }
func (node *PBNode) SortLinks() { func (node *PBNode) SortLinks() {
sort.Stable(pbLinkSlice(node.Links)) SortLinks(node.Links)
}
func SortLinks(links []*PBLink) {
sort.Stable(pbLinkSlice(links))
} }
type pbLinkSlice []*PBLink type pbLinkSlice []*PBLink
......
...@@ -11,9 +11,9 @@ import ( ...@@ -11,9 +11,9 @@ import (
// Marshal TODO // Marshal TODO
func Marshal(out io.Writer, tokenSource func() (Token, error)) error { func Marshal(out io.Writer, tokenSource func() (Token, error)) error {
writeLead := func(wire byte, size uint64) { writeLead := func(wire byte, size uint64) {
lead := make([]byte, sizeOfVarint(size)+1) lead := make([]byte, SizeOfVarint(size)+1)
lead[0] = wire lead[0] = wire
encodeVarint(lead, len(lead), size) EncodeVarint(lead, len(lead), size)
out.Write(lead) out.Write(lead)
} }
...@@ -33,10 +33,10 @@ func Marshal(out io.Writer, tokenSource func() (Token, error)) error { ...@@ -33,10 +33,10 @@ func Marshal(out io.Writer, tokenSource func() (Token, error)) error {
writeLead(0xa, uint64(len(tok.Bytes))) writeLead(0xa, uint64(len(tok.Bytes)))
out.Write(tok.Bytes) out.Write(tok.Bytes)
case TypeLinkEnd: case TypeLinkEnd:
l := link.size() l := link.EncodedSize()
writeLead(0x12, uint64(l)) writeLead(0x12, uint64(l))
chunk := make([]byte, l) chunk := make([]byte, l)
wrote, err := link.marshal(chunk) wrote, err := link.Marshal(chunk)
if err != nil { if err != nil {
return err return err
} }
...@@ -59,50 +59,50 @@ func Marshal(out io.Writer, tokenSource func() (Token, error)) error { ...@@ -59,50 +59,50 @@ func Marshal(out io.Writer, tokenSource func() (Token, error)) error {
} }
func MarshalPBNode(node *PBNode) ([]byte, error) { func MarshalPBNode(node *PBNode) ([]byte, error) {
if err := node.validate(); err != nil { if err := node.Validate(); err != nil {
return nil, err return nil, err
} }
size := node.size() size := node.EncodedSize()
data := make([]byte, size) data := make([]byte, size)
i := len(data) i := len(data)
if node.Data != nil { if node.Data != nil {
i -= len(node.Data) i -= len(node.Data)
copy(data[i:], node.Data) copy(data[i:], node.Data)
i = encodeVarint(data, i, uint64(len(node.Data))) - 1 i = EncodeVarint(data, i, uint64(len(node.Data))) - 1
data[i] = 0xa data[i] = 0xa
} }
if len(node.Links) > 0 { if len(node.Links) > 0 {
for index := len(node.Links) - 1; index >= 0; index-- { for index := len(node.Links) - 1; index >= 0; index-- {
size, err := node.Links[index].marshal(data[:i]) size, err := node.Links[index].Marshal(data[:i])
if err != nil { if err != nil {
return nil, err return nil, err
} }
i -= size i -= size
i = encodeVarint(data, i, uint64(size)) - 1 i = EncodeVarint(data, i, uint64(size)) - 1
data[i] = 0x12 data[i] = 0x12
} }
} }
return data[:size], nil return data[:size], nil
} }
func (link *PBLink) marshal(data []byte) (int, error) { func (link *PBLink) Marshal(data []byte) (int, error) {
i := len(data) i := len(data)
if link.Tsize != nil { if link.Tsize != nil {
i = encodeVarint(data, i, uint64(*link.Tsize)) - 1 i = EncodeVarint(data, i, uint64(*link.Tsize)) - 1
data[i] = 0x18 data[i] = 0x18
} }
if link.Name != nil { if link.Name != nil {
i -= len(*link.Name) i -= len(*link.Name)
copy(data[i:], *link.Name) copy(data[i:], *link.Name)
i = encodeVarint(data, i, uint64(len(*link.Name))) - 1 i = EncodeVarint(data, i, uint64(len(*link.Name))) - 1
data[i] = 0x12 data[i] = 0x12
} }
if link.Hash != nil { if link.Hash != nil {
byts := link.Hash.Bytes() byts := link.Hash.Bytes()
i -= len(byts) i -= len(byts)
copy(data[i:], byts) copy(data[i:], byts)
i = encodeVarint(data, i, uint64(len(byts))) - 1 i = EncodeVarint(data, i, uint64(len(byts))) - 1
data[i] = 0xa data[i] = 0xa
} else { } else {
return 0, fmt.Errorf("invalid DAG-PB form (link must have a Hash)") return 0, fmt.Errorf("invalid DAG-PB form (link must have a Hash)")
...@@ -110,7 +110,7 @@ func (link *PBLink) marshal(data []byte) (int, error) { ...@@ -110,7 +110,7 @@ func (link *PBLink) marshal(data []byte) (int, error) {
return len(data) - i, nil return len(data) - i, nil
} }
func (node *PBNode) validate() error { func (node *PBNode) Validate() error {
if node == nil { if node == nil {
return fmt.Errorf("PBNode not defined") return fmt.Errorf("PBNode not defined")
} }
...@@ -132,45 +132,45 @@ func (node *PBNode) validate() error { ...@@ -132,45 +132,45 @@ func (node *PBNode) validate() error {
return nil return nil
} }
func (link *PBLink) size() (n int) { func (link *PBLink) EncodedSize() (n int) {
if link == nil { if link == nil {
return 0 return 0
} }
var l int var l int
if link.Hash != nil { if link.Hash != nil {
l = link.Hash.ByteLen() l = link.Hash.ByteLen()
n += 1 + l + sizeOfVarint(uint64(l)) n += 1 + l + SizeOfVarint(uint64(l))
} }
if link.Name != nil { if link.Name != nil {
l = len(*link.Name) l = len(*link.Name)
n += 1 + l + sizeOfVarint(uint64(l)) n += 1 + l + SizeOfVarint(uint64(l))
} }
if link.Tsize != nil { if link.Tsize != nil {
n += 1 + sizeOfVarint(uint64(*link.Tsize)) n += 1 + SizeOfVarint(uint64(*link.Tsize))
} }
return n return n
} }
func (node *PBNode) size() (n int) { func (node *PBNode) EncodedSize() (n int) {
if node == nil { if node == nil {
return 0 return 0
} }
var l int var l int
if node.Data != nil { if node.Data != nil {
l = len(node.Data) l = len(node.Data)
n += 1 + l + sizeOfVarint(uint64(l)) n += 1 + l + SizeOfVarint(uint64(l))
} }
if len(node.Links) > 0 { if len(node.Links) > 0 {
for _, e := range node.Links { for _, e := range node.Links {
l = e.size() l = e.EncodedSize()
n += 1 + l + sizeOfVarint(uint64(l)) n += 1 + l + SizeOfVarint(uint64(l))
} }
} }
return n return n
} }
func encodeVarint(data []byte, offset int, v uint64) int { func EncodeVarint(data []byte, offset int, v uint64) int {
offset -= sizeOfVarint(v) offset -= SizeOfVarint(v)
base := offset base := offset
for v >= 1<<7 { for v >= 1<<7 {
data[offset] = uint8(v&0x7f | 0x80) data[offset] = uint8(v&0x7f | 0x80)
...@@ -181,6 +181,6 @@ func encodeVarint(data []byte, offset int, v uint64) int { ...@@ -181,6 +181,6 @@ func encodeVarint(data []byte, offset int, v uint64) int {
return base return base
} }
func sizeOfVarint(x uint64) (n int) { func SizeOfVarint(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7 return (math_bits.Len64(x|1) + 6) / 7
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment