diff --git a/hamt/hamt.go b/hamt/hamt.go index 7947a2aa0..0bb907542 100644 --- a/hamt/hamt.go +++ b/hamt/hamt.go @@ -30,6 +30,7 @@ import ( ipld "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" format "github.com/ipfs/go-unixfs" + coreiface "github.com/ipfs/interface-go-ipfs-core" ) const ( @@ -61,6 +62,8 @@ type Shard struct { // leaf node key string val *ipld.Link + + rawData []byte } // NewShard creates a new, empty HAMT shard with the given size. @@ -126,6 +129,7 @@ func NewHamtFromDag(dserv ipld.DAGService, nd ipld.Node) (*Shard, error) { ds.cid = pbnd.Cid() ds.hashFunc = fsn.HashType() ds.builder = pbnd.CidBuilder() + ds.rawData = pbnd.RawData() return ds, nil } @@ -280,17 +284,33 @@ func (ds *Shard) Link() (*ipld.Link, error) { return ipld.MakeLink(nd) } +func hamtChunk(name string, rawData []byte) []byte { + buff := make([]byte, 4+len(name)+len(rawData)) + buff[0] = 1 + buff[1] = byte(len(name) >> 16) + buff[2] = byte(len(name) >> 8) + buff[3] = byte(len(name)) + copy(buff[4:], []byte(name)) + copy(buff[4+len(name):], rawData) + + return buff +} + func (ds *Shard) getValue(ctx context.Context, hv *hashBits, key string, cb func(*Shard) error) error { childIndex, err := hv.Next(ds.tableSizeLg2) if err != nil { return err } + sliceIndex := ds.childer.sliceIndex(childIndex) if ds.childer.has(childIndex) { - child, err := ds.childer.get(ctx, ds.childer.sliceIndex(childIndex)) + child, err := ds.childer.get(ctx, sliceIndex) if err != nil { return err } + if pw, ok := ctx.Value("proxy-preamble").(coreiface.ProofWriter); ok { + pw.WriteChunk(hamtChunk(ds.childer.link(sliceIndex).Name, ds.rawData)) + } if child.isValueNode() { if child.key == key { diff --git a/io/proofreader.go b/io/proofreader.go new file mode 100644 index 000000000..e8a4096f6 --- /dev/null +++ b/io/proofreader.go @@ -0,0 +1,99 @@ +package io + +import ( + "context" + "fmt" + "io" + + ipld "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" + mdag "github.com/ipfs/go-merkledag" + unixfs "github.com/ipfs/go-unixfs" + pb "github.com/ipfs/go-unixfs/pb" + coreiface "github.com/ipfs/interface-go-ipfs-core" +) + +// NewDagReaderWithProof creates a new proof reader object that reads the data +// represented by the given node, using the passed in DAGService for data +// retrieval. +func NewDagReaderWithProof(ctx context.Context, n ipld.Node, serv ipld.NodeGetter) (coreiface.ProofReader, error) { + switch n := n.(type) { + case *mdag.RawNode: + case *mdag.ProtoNode: + fsNode, err := unixfs.FSNodeFromBytes(n.Data()) + if err != nil { + return nil, err + } + + switch fsNode.Type() { + case unixfs.TFile, unixfs.TRaw: + case unixfs.TDirectory, unixfs.THAMTShard: + return nil, ErrIsDir + case unixfs.TSymlink: + return nil, ErrCantReadSymlinks + default: + return nil, unixfs.ErrUnrecognizedType + } + default: + return nil, ErrUnkownNodeType + } + + ctxWithCancel, cancel := context.WithCancel(ctx) + + return &proofReader{ + cancel: cancel, + dagWalker: ipld.NewWalker(ctxWithCancel, ipld.NewNavigableIPLDNode(n, serv)), + }, nil +} + +// proofReader provides a way to easily read the data contained in a dag, in a +// way that it can be presented to an untrusting client. +type proofReader struct { + dagWalker *ipld.Walker + cancel func() +} + +func (pr *proofReader) ReadChunk() ([]byte, error) { + var out []byte + + err := pr.dagWalker.Iterate(func(visitedNode ipld.NavigableNode) error { + node := ipld.ExtractIPLDNode(visitedNode) + + switch node := node.(type) { + case *dag.RawNode: + out = append([]byte{0}, node.RawData()...) + + case *dag.ProtoNode: + fsNode, err := unixfs.FSNodeFromBytes(node.Data()) + if err != nil { + return fmt.Errorf("incorrectly formatted protobuf: %s", err) + } + + switch fsNode.Type() { + case pb.Data_File, pb.Data_Raw: + out = append([]byte{1}, node.RawData()...) + default: + return fmt.Errorf("found %s node in unexpected place", + fsNode.Type().String()) + } + + default: + return unixfs.ErrUnrecognizedType + } + + pr.dagWalker.Pause() + return nil + }) + + if err == ipld.EndOfDag { + return nil, io.EOF + } else if err != nil { + return nil, err + } + return out, nil +} + +func (pr *proofReader) Close() error { + pr.cancel() + return nil +} diff --git a/io/resolve.go b/io/resolve.go index c3dcffc24..c5a3b9b1c 100644 --- a/io/resolve.go +++ b/io/resolve.go @@ -8,6 +8,7 @@ import ( hamt "github.com/ipfs/go-unixfs/hamt" ipld "github.com/ipfs/go-ipld-format" + coreiface "github.com/ipfs/interface-go-ipfs-core" ) // ResolveUnixfsOnce resolves a single hop of a path through a graph in a @@ -16,12 +17,7 @@ func ResolveUnixfsOnce(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, na pn, ok := nd.(*dag.ProtoNode) if ok { fsn, err := ft.FSNodeFromBytes(pn.Data()) - if err != nil { - // Not a unixfs node, use standard object traversal code - return nd.ResolveLink(names) - } - - if fsn.Type() == ft.THAMTShard { + if err == nil && fsn.Type() == ft.THAMTShard { rods := dag.NewReadOnlyDagService(ds) s, err := hamt.NewHamtFromDag(rods, nd) if err != nil { @@ -37,5 +33,8 @@ func ResolveUnixfsOnce(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, na } } + if pw, ok := ctx.Value("proxy-preamble").(coreiface.ProofWriter); ok { + pw.WriteChunk(append([]byte{0}, nd.RawData()...)) + } return nd.ResolveLink(names) }