| 1 | package harness |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | |
| 7 | mdag "github.com/ipfs/boxo/ipld/merkledag" |
| 8 | ft "github.com/ipfs/boxo/ipld/unixfs" |
| 9 | pb "github.com/ipfs/boxo/ipld/unixfs/pb" |
| 10 | ) |
| 11 | |
| 12 | // UnixFSDataType returns the UnixFS DataType for the given CID by fetching the |
| 13 | // raw block and parsing the protobuf. This directly checks the Type field in |
| 14 | // the UnixFS Data message (https://specs.ipfs.tech/unixfs/#data). |
| 15 | // |
| 16 | // Common types: |
| 17 | // - ft.TDirectory (1) = basic flat directory |
| 18 | // - ft.THAMTShard (5) = HAMT sharded directory |
| 19 | func (n *Node) UnixFSDataType(cid string) (pb.Data_DataType, error) { |
| 20 | log.Debugf("node %d block get %s", n.ID, cid) |
| 21 | |
| 22 | var blockData bytes.Buffer |
| 23 | res := n.Runner.MustRun(RunRequest{ |
| 24 | Path: n.IPFSBin, |
| 25 | Args: []string{"block", "get", cid}, |
| 26 | CmdOpts: []CmdOpt{RunWithStdout(&blockData)}, |
| 27 | }) |
| 28 | if res.Err != nil { |
| 29 | return 0, res.Err |
| 30 | } |
| 31 | |
| 32 | // Parse dag-pb block |
| 33 | protoNode, err := mdag.DecodeProtobuf(blockData.Bytes()) |
| 34 | if err != nil { |
| 35 | return 0, err |
| 36 | } |
| 37 | |
| 38 | // Parse UnixFS data |
| 39 | fsNode, err := ft.FSNodeFromBytes(protoNode.Data()) |
| 40 | if err != nil { |
| 41 | return 0, err |
| 42 | } |
| 43 | |
| 44 | return fsNode.Type(), nil |
| 45 | } |
| 46 | |
| 47 | // UnixFSHAMTFanout returns the fanout value for a HAMT shard directory. |
| 48 | // This is only valid for HAMT shards (THAMTShard type). |
| 49 | func (n *Node) UnixFSHAMTFanout(cid string) (uint64, error) { |
| 50 | log.Debugf("node %d block get %s for fanout", n.ID, cid) |
| 51 | |
| 52 | var blockData bytes.Buffer |
| 53 | res := n.Runner.MustRun(RunRequest{ |
| 54 | Path: n.IPFSBin, |
| 55 | Args: []string{"block", "get", cid}, |
| 56 | CmdOpts: []CmdOpt{RunWithStdout(&blockData)}, |
| 57 | }) |
| 58 | if res.Err != nil { |
| 59 | return 0, res.Err |
| 60 | } |
| 61 | |
| 62 | // Parse dag-pb block |
| 63 | protoNode, err := mdag.DecodeProtobuf(blockData.Bytes()) |
| 64 | if err != nil { |
| 65 | return 0, err |
| 66 | } |
| 67 | |
| 68 | // Parse UnixFS data |
| 69 | fsNode, err := ft.FSNodeFromBytes(protoNode.Data()) |
| 70 | if err != nil { |
| 71 | return 0, err |
| 72 | } |
| 73 | |
| 74 | return fsNode.Fanout(), nil |
| 75 | } |
| 76 | |
| 77 | // InspectPBNode uses dag-json output of 'ipfs dag get' to inspect |
| 78 | // "Logical Format" of DAG-PB as defined in |
| 79 | // https://web.archive.org/web/20250403194752/https://ipld.io/specs/codecs/dag-pb/spec/#logical-format |
| 80 | // (mainly used for inspecting Links without depending on any libraries) |
| 81 | func (n *Node) InspectPBNode(cid string) (PBNode, error) { |
| 82 | log.Debugf("node %d dag get %s as dag-json", n.ID, cid) |
| 83 | |
| 84 | var root PBNode |
| 85 | var dagJsonOutput bytes.Buffer |
| 86 | res := n.Runner.MustRun(RunRequest{ |
| 87 | Path: n.IPFSBin, |
| 88 | Args: []string{"dag", "get", "--output-codec=dag-json", cid}, |
| 89 | CmdOpts: []CmdOpt{RunWithStdout(&dagJsonOutput)}, |
| 90 | }) |
| 91 | if res.Err != nil { |
| 92 | return root, res.Err |
| 93 | } |
| 94 | |
| 95 | err := json.Unmarshal(dagJsonOutput.Bytes(), &root) |
| 96 | if err != nil { |
| 97 | return root, err |
| 98 | } |
| 99 | return root, nil |
| 100 | } |
| 101 | |
| 102 | // Define structs to match the JSON for |
| 103 | type PBHash struct { |
| 104 | Slash string `json:"/"` |
| 105 | } |
| 106 | |
| 107 | type PBLink struct { |
| 108 | Hash PBHash `json:"Hash"` |
| 109 | Name string `json:"Name"` |
| 110 | Tsize int `json:"Tsize"` |
| 111 | } |
| 112 | |
| 113 | type PBData struct { |
| 114 | Slash struct { |
| 115 | Bytes string `json:"bytes"` |
| 116 | } `json:"/"` |
| 117 | } |
| 118 | |
| 119 | type PBNode struct { |
| 120 | Data PBData `json:"Data"` |
| 121 | Links []PBLink `json:"Links"` |
| 122 | } |