package cbornode import ( "encoding/json" "errors" "io" "math" "strconv" "strings" blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" node "github.com/ipfs/go-ipld-format" mh "github.com/multiformats/go-multihash" ) // CBORTagLink is the integer used to represent tags in CBOR. const CBORTagLink = 42 // Node represents an IPLD node. type Node struct { obj interface{} tree []string links []*node.Link raw []byte cid cid.Cid } // Compile time check to make sure Node implements the node.Node interface var _ node.Node = (*Node)(nil) var ( // ErrNoSuchLink is returned when no link with the given name was found. ErrNoSuchLink = errors.New("no such link found") ErrNonLink = errors.New("non-link found at given path") ErrInvalidLink = errors.New("link value should have been bytes") ErrInvalidKeys = errors.New("map keys must be strings") ErrArrayOutOfRange = errors.New("array index out of range") ErrNoLinks = errors.New("tried to resolve through object that had no links") ErrEmptyLink = errors.New("link value was empty") ErrInvalidMultibase = errors.New("invalid multibase on IPLD link") ErrNonStringLink = errors.New("link should have been a string") ) // DecodeBlock decodes a CBOR encoded Block into an IPLD Node. // // This method *does not* canonicalize and *will* preserve the CID. As a matter // of fact, it will assume that `block.Cid()` returns the correct CID and will // make no effort to validate this assumption. // // In general, you should not be calling this method directly. Instead, you // should be calling the `Decode` method from the `go-ipld-format` package. That // method will pick the right decoder based on the Block's CID. // // Note: This function keeps a reference to `block` and assumes that it is // immutable. func DecodeBlock(block blocks.Block) (node.Node, error) { return decodeBlock(block) } func decodeBlock(block blocks.Block) (*Node, error) { var m interface{} if err := DecodeInto(block.RawData(), &m); err != nil { return nil, err } return newObject(block, m) } func newObject(block blocks.Block, m interface{}) (*Node, error) { tree, links, err := compute(m) if err != nil { return nil, err } return &Node{ obj: m, tree: tree, links: links, raw: block.RawData(), cid: block.Cid(), }, nil } var _ node.DecodeBlockFunc = DecodeBlock // Decode decodes a CBOR object into an IPLD Node. // // If passed a non-canonical CBOR node, this function will canonicalize it. // Therefore, `bytes.Equal(b, Decode(b).RawData())` may not hold. If you already // have a CID for this data and want to ensure that it doesn't change, you // should use `DecodeBlock`. // mhType is multihash code to use for hashing, for example mh.SHA2_256 // // Note: This function does not hold onto `b`. You may reuse it. func Decode(b []byte, mhType uint64, mhLen int) (*Node, error) { var m interface{} if err := DecodeInto(b, &m); err != nil { return nil, err } // We throw away `b` here to ensure that we canonicalize the encoded // CBOR object. return WrapObject(m, mhType, mhLen) } // DecodeInto decodes a serialized IPLD cbor object into the given object. func DecodeInto(b []byte, v interface{}) error { return unmarshaller.Unmarshal(b, v) } // WrapObject converts an arbitrary object into a Node. func WrapObject(m interface{}, mhType uint64, mhLen int) (*Node, error) { data, err := marshaller.Marshal(m) if err != nil { return nil, err } var obj interface{} err = cloner.Clone(m, &obj) if err != nil { return nil, err } if mhType == math.MaxUint64 { mhType = mh.SHA2_256 } hash, err := mh.Sum(data, mhType, mhLen) if err != nil { return nil, err } c := cid.NewCidV1(cid.DagCBOR, hash) block, err := blocks.NewBlockWithCid(data, c) if err != nil { // TODO: Shouldn't this just panic? return nil, err } // No need to deserialize. We can just deep copy. return newObject(block, obj) } // Resolve resolves a given path, and returns the object found at the end, as well // as the possible tail of the path that was not resolved. func (n *Node) Resolve(path []string) (interface{}, []string, error) { var cur interface{} = n.obj for i, val := range path { switch curv := cur.(type) { case map[string]interface{}: next, ok := curv[val] if !ok { return nil, nil, ErrNoSuchLink } cur = next case map[interface{}]interface{}: next, ok := curv[val] if !ok { return nil, nil, ErrNoSuchLink } cur = next case []interface{}: n, err := strconv.Atoi(val) if err != nil { return nil, nil, err } if n < 0 || n >= len(curv) { return nil, nil, ErrArrayOutOfRange } cur = curv[n] case cid.Cid: return &node.Link{Cid: curv}, path[i:], nil default: return nil, nil, ErrNoLinks } } lnk, ok := cur.(cid.Cid) if ok { return &node.Link{Cid: lnk}, nil, nil } jsonish, err := convertToJSONIsh(cur) if err != nil { return nil, nil, err } return jsonish, nil, nil } // Copy creates a copy of the Node. func (n *Node) Copy() node.Node { links := make([]*node.Link, len(n.links)) copy(links, n.links) raw := make([]byte, len(n.raw)) copy(raw, n.raw) tree := make([]string, len(n.tree)) copy(tree, n.tree) return &Node{ obj: copyObj(n.obj), links: links, raw: raw, tree: tree, cid: n.cid, } } func copyObj(i interface{}) interface{} { switch i := i.(type) { case map[string]interface{}: out := make(map[string]interface{}) for k, v := range i { out[k] = copyObj(v) } return out case map[interface{}]interface{}: out := make(map[interface{}]interface{}) for k, v := range i { out[k] = copyObj(v) } return out case []interface{}: var out []interface{} for _, v := range i { out = append(out, copyObj(v)) } return out default: // TODO: do not be lazy // being lazy for now // use caution return i } } // ResolveLink resolves a path and returns the raw Link at the end, as well as // the possible tail of the path that was not resolved. func (n *Node) ResolveLink(path []string) (*node.Link, []string, error) { obj, rest, err := n.Resolve(path) if err != nil { return nil, nil, err } lnk, ok := obj.(*node.Link) if !ok { return nil, rest, ErrNonLink } return lnk, rest, nil } // Tree returns a flattend array of paths at the given path for the given depth. func (n *Node) Tree(path string, depth int) []string { if path == "" && depth == -1 { return n.tree } var out []string for _, t := range n.tree { if !strings.HasPrefix(t, path) { continue } sub := strings.TrimLeft(t[len(path):], "/") if sub == "" { continue } if depth < 0 { out = append(out, sub) continue } parts := strings.Split(sub, "/") if len(parts) <= depth { out = append(out, sub) } } return out } func compute(obj interface{}) (tree []string, links []*node.Link, err error) { err = traverse(obj, "", func(name string, val interface{}) error { if name != "" { tree = append(tree, name[1:]) } if lnk, ok := val.(cid.Cid); ok { links = append(links, &node.Link{Cid: lnk}) } return nil }) if err != nil { return nil, nil, err } return tree, links, nil } // Links lists all known links of the Node. func (n *Node) Links() []*node.Link { return n.links } func traverse(obj interface{}, cur string, cb func(string, interface{}) error) error { if err := cb(cur, obj); err != nil { return err } switch obj := obj.(type) { case map[string]interface{}: for k, v := range obj { this := cur + "/" + k if err := traverse(v, this, cb); err != nil { return err } } return nil case map[interface{}]interface{}: for k, v := range obj { ks, ok := k.(string) if !ok { return errors.New("map key was not a string") } this := cur + "/" + ks if err := traverse(v, this, cb); err != nil { return err } } return nil case []interface{}: for i, v := range obj { this := cur + "/" + strconv.Itoa(i) if err := traverse(v, this, cb); err != nil { return err } } return nil default: return nil } } // RawData returns the raw bytes that represent the Node as serialized CBOR. func (n *Node) RawData() []byte { return n.raw } // Cid returns the canonical Cid of the NOde. func (n *Node) Cid() cid.Cid { return n.cid } // Loggable returns a loggable representation of the Node. func (n *Node) Loggable() map[string]interface{} { return map[string]interface{}{ "node_type": "cbor", "cid": n.Cid(), } } // Size returns the size of the binary representation of the Node. func (n *Node) Size() (uint64, error) { return uint64(len(n.RawData())), nil } // Stat returns stats about the Node. // TODO: implement? func (n *Node) Stat() (*node.NodeStat, error) { return &node.NodeStat{}, nil } // String returns the string representation of the CID of the Node. func (n *Node) String() string { return n.Cid().String() } // MarshalJSON converts the Node into its JSON representation. func (n *Node) MarshalJSON() ([]byte, error) { out, err := convertToJSONIsh(n.obj) if err != nil { return nil, err } return json.Marshal(out) } // DumpObject marshals any object into its CBOR serialized byte representation // TODO: rename func DumpObject(obj interface{}) (out []byte, err error) { return marshaller.Marshal(obj) } func toSaneMap(n map[interface{}]interface{}) (interface{}, error) { if lnk, ok := n["/"]; ok && len(n) == 1 { lnkb, ok := lnk.([]byte) if !ok { return nil, ErrInvalidLink } c, err := cid.Cast(lnkb) if err != nil { return nil, err } return map[string]interface{}{"/": c}, nil } out := make(map[string]interface{}) for k, v := range n { ks, ok := k.(string) if !ok { return nil, ErrInvalidKeys } obj, err := convertToJSONIsh(v) if err != nil { return nil, err } out[ks] = obj } return out, nil } func convertToJSONIsh(v interface{}) (interface{}, error) { switch v := v.(type) { case map[interface{}]interface{}: return toSaneMap(v) case []interface{}: var out []interface{} if len(v) == 0 && v != nil { return []interface{}{}, nil } for _, i := range v { obj, err := convertToJSONIsh(i) if err != nil { return nil, err } out = append(out, obj) } return out, nil default: return v, nil } } // FromJSON converts incoming JSON into a Node. func FromJSON(r io.Reader, mhType uint64, mhLen int) (*Node, error) { var m interface{} err := json.NewDecoder(r).Decode(&m) if err != nil { return nil, err } obj, err := convertToCborIshObj(m) if err != nil { return nil, err } return WrapObject(obj, mhType, mhLen) } func convertToCborIshObj(i interface{}) (interface{}, error) { switch v := i.(type) { case map[string]interface{}: if len(v) == 0 && v != nil { return v, nil } if lnk, ok := v["/"]; ok && len(v) == 1 { // special case for links vstr, ok := lnk.(string) if !ok { return nil, ErrNonStringLink } return cid.Decode(vstr) } for a, b := range v { val, err := convertToCborIshObj(b) if err != nil { return nil, err } v[a] = val } return v, nil case []interface{}: if len(v) == 0 && v != nil { return v, nil } var out []interface{} for _, o := range v { obj, err := convertToCborIshObj(o) if err != nil { return nil, err } out = append(out, obj) } return out, nil default: return v, nil } } func castBytesToCid(x []byte) (cid.Cid, error) { if len(x) == 0 { return cid.Cid{}, ErrEmptyLink } // TODO: manually doing multibase checking here since our deps don't // support binary multibase yet if x[0] != 0 { return cid.Cid{}, ErrInvalidMultibase } c, err := cid.Cast(x[1:]) if err != nil { return cid.Cid{}, ErrInvalidLink } return c, nil } func castCidToBytes(link cid.Cid) ([]byte, error) { if !link.Defined() { return nil, ErrEmptyLink } return append([]byte{0}, link.Bytes()...), nil }