| 1 | package commands |
| 2 | |
| 3 | import ( |
| 4 | "cmp" |
| 5 | "encoding/hex" |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | "io" |
| 9 | "slices" |
| 10 | "strings" |
| 11 | "unicode" |
| 12 | |
| 13 | verifcid "github.com/ipfs/boxo/verifcid" |
| 14 | cid "github.com/ipfs/go-cid" |
| 15 | cidutil "github.com/ipfs/go-cidutil" |
| 16 | cmds "github.com/ipfs/go-ipfs-cmds" |
| 17 | ipldmulticodec "github.com/ipld/go-ipld-prime/multicodec" |
| 18 | peer "github.com/libp2p/go-libp2p/core/peer" |
| 19 | mbase "github.com/multiformats/go-multibase" |
| 20 | mc "github.com/multiformats/go-multicodec" |
| 21 | mhash "github.com/multiformats/go-multihash" |
| 22 | ) |
| 23 | |
| 24 | var CidCmd = &cmds.Command{ |
| 25 | Helptext: cmds.HelpText{ |
| 26 | Tagline: "Convert and discover properties of CIDs", |
| 27 | }, |
| 28 | Subcommands: map[string]*cmds.Command{ |
| 29 | "inspect": inspectCmd, |
| 30 | "format": cidFmtCmd, |
| 31 | "base32": base32Cmd, |
| 32 | "bases": basesCmd, |
| 33 | "codecs": codecsCmd, |
| 34 | "hashes": hashesCmd, |
| 35 | }, |
| 36 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 37 | } |
| 38 | |
| 39 | const ( |
| 40 | cidFormatOptionName = "f" |
| 41 | cidToVersionOptionName = "v" |
| 42 | cidCodecOptionName = "mc" |
| 43 | cidMultibaseOptionName = "b" |
| 44 | ) |
| 45 | |
| 46 | var cidFmtCmd = &cmds.Command{ |
| 47 | Helptext: cmds.HelpText{ |
| 48 | Tagline: "Format and convert a CID in various useful ways.", |
| 49 | LongDescription: ` |
| 50 | Format and converts <cid>'s in various useful ways. |
| 51 | |
| 52 | For a human-readable breakdown of a CID, see 'ipfs cid inspect'. |
| 53 | |
| 54 | The optional format string is a printf style format string: |
| 55 | ` + cidutil.FormatRef, |
| 56 | }, |
| 57 | Arguments: []cmds.Argument{ |
| 58 | cmds.StringArg("cid", true, true, "CIDs to format.").EnableStdin(), |
| 59 | }, |
| 60 | Options: []cmds.Option{ |
| 61 | cmds.StringOption(cidFormatOptionName, "Printf style format string.").WithDefault("%s"), |
| 62 | cmds.StringOption(cidToVersionOptionName, "CID version to convert to."), |
| 63 | cmds.StringOption(cidCodecOptionName, "CID multicodec to convert to."), |
| 64 | cmds.StringOption(cidMultibaseOptionName, "Multibase to display CID in."), |
| 65 | }, |
| 66 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 67 | fmtStr, _ := req.Options[cidFormatOptionName].(string) |
| 68 | verStr, _ := req.Options[cidToVersionOptionName].(string) |
| 69 | codecStr, _ := req.Options[cidCodecOptionName].(string) |
| 70 | baseStr, _ := req.Options[cidMultibaseOptionName].(string) |
| 71 | |
| 72 | opts := cidFormatOpts{} |
| 73 | |
| 74 | if strings.IndexByte(fmtStr, '%') == -1 { |
| 75 | return fmt.Errorf("invalid format string: %q", fmtStr) |
| 76 | } |
| 77 | opts.fmtStr = fmtStr |
| 78 | |
| 79 | if codecStr != "" { |
| 80 | var codec mc.Code |
| 81 | err := codec.Set(codecStr) |
| 82 | if err != nil { |
| 83 | return err |
| 84 | } |
| 85 | opts.newCodec = uint64(codec) |
| 86 | } // otherwise, leave it as 0 (not a valid IPLD codec) |
| 87 | |
| 88 | switch verStr { |
| 89 | case "": |
| 90 | if baseStr != "" { |
| 91 | opts.verConv = toCidV1 |
| 92 | } |
| 93 | case "0": |
| 94 | if opts.newCodec != 0 && opts.newCodec != cid.DagProtobuf { |
| 95 | return errors.New("cannot convert to CIDv0 with any codec other than dag-pb") |
| 96 | } |
| 97 | if baseStr != "" && baseStr != "base58btc" { |
| 98 | return errors.New("cannot convert to CIDv0 with any multibase other than the implicit base58btc") |
| 99 | } |
| 100 | opts.verConv = toCidV0 |
| 101 | case "1": |
| 102 | opts.verConv = toCidV1 |
| 103 | default: |
| 104 | return fmt.Errorf("invalid cid version: %q", verStr) |
| 105 | } |
| 106 | |
| 107 | if baseStr != "" { |
| 108 | encoder, err := mbase.EncoderByName(baseStr) |
| 109 | if err != nil { |
| 110 | return err |
| 111 | } |
| 112 | opts.newBase = encoder.Encoding() |
| 113 | } else { |
| 114 | opts.newBase = mbase.Encoding(-1) |
| 115 | } |
| 116 | |
| 117 | return emitCids(req, resp, opts) |
| 118 | }, |
| 119 | PostRun: cmds.PostRunMap{ |
| 120 | cmds.CLI: streamResult(func(v any, out io.Writer) nonFatalError { |
| 121 | r := v.(*CidFormatRes) |
| 122 | if r.ErrorMsg != "" { |
| 123 | return nonFatalError(fmt.Sprintf("%s: %s", r.CidStr, r.ErrorMsg)) |
| 124 | } |
| 125 | fmt.Fprintf(out, "%s\n", r.Formatted) |
| 126 | return "" |
| 127 | }), |
| 128 | }, |
| 129 | Type: CidFormatRes{}, |
| 130 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 131 | } |
| 132 | |
| 133 | type CidFormatRes struct { |
| 134 | CidStr string // Original Cid String passed in |
| 135 | Formatted string // Formatted Result |
| 136 | ErrorMsg string // Error |
| 137 | } |
| 138 | |
| 139 | var base32Cmd = &cmds.Command{ |
| 140 | Helptext: cmds.HelpText{ |
| 141 | Tagline: "Convert CIDs to Base32 CID version 1.", |
| 142 | ShortDescription: ` |
| 143 | 'ipfs cid base32' normalizes passed CIDs to their canonical case-insensitive encoding. |
| 144 | Useful when processing third-party CIDs which could come with arbitrary formats. |
| 145 | `, |
| 146 | }, |
| 147 | Arguments: []cmds.Argument{ |
| 148 | cmds.StringArg("cid", true, true, "CIDs to convert.").EnableStdin(), |
| 149 | }, |
| 150 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 151 | opts := cidFormatOpts{ |
| 152 | fmtStr: "%s", |
| 153 | newBase: mbase.Encoding(mbase.Base32), |
| 154 | verConv: toCidV1, |
| 155 | } |
| 156 | return emitCids(req, resp, opts) |
| 157 | }, |
| 158 | PostRun: cidFmtCmd.PostRun, |
| 159 | Type: cidFmtCmd.Type, |
| 160 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 161 | } |
| 162 | |
| 163 | type cidFormatOpts struct { |
| 164 | fmtStr string |
| 165 | newBase mbase.Encoding |
| 166 | verConv func(cid cid.Cid) (cid.Cid, error) |
| 167 | newCodec uint64 |
| 168 | } |
| 169 | |
| 170 | type argumentIterator struct { |
| 171 | args []string |
| 172 | body cmds.StdinArguments |
| 173 | } |
| 174 | |
| 175 | func (i *argumentIterator) next() (string, bool) { |
| 176 | if len(i.args) > 0 { |
| 177 | arg := i.args[0] |
| 178 | i.args = i.args[1:] |
| 179 | return arg, true |
| 180 | } |
| 181 | if i.body == nil || !i.body.Scan() { |
| 182 | return "", false |
| 183 | } |
| 184 | return strings.TrimSpace(i.body.Argument()), true |
| 185 | } |
| 186 | |
| 187 | func (i *argumentIterator) err() error { |
| 188 | if i.body == nil { |
| 189 | return nil |
| 190 | } |
| 191 | return i.body.Err() |
| 192 | } |
| 193 | |
| 194 | func emitCids(req *cmds.Request, resp cmds.ResponseEmitter, opts cidFormatOpts) error { |
| 195 | itr := argumentIterator{req.Arguments, req.BodyArgs()} |
| 196 | var emitErr error |
| 197 | for emitErr == nil { |
| 198 | cidStr, ok := itr.next() |
| 199 | if !ok { |
| 200 | break |
| 201 | } |
| 202 | res := &CidFormatRes{CidStr: cidStr} |
| 203 | c, err := cid.Decode(cidStr) |
| 204 | if err != nil { |
| 205 | res.ErrorMsg = err.Error() |
| 206 | emitErr = resp.Emit(res) |
| 207 | continue |
| 208 | } |
| 209 | |
| 210 | if opts.newCodec != 0 && opts.newCodec != c.Type() { |
| 211 | c = cid.NewCidV1(opts.newCodec, c.Hash()) |
| 212 | } |
| 213 | |
| 214 | if opts.verConv != nil { |
| 215 | c, err = opts.verConv(c) |
| 216 | if err != nil { |
| 217 | res.ErrorMsg = err.Error() |
| 218 | emitErr = resp.Emit(res) |
| 219 | continue |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | base := opts.newBase |
| 224 | if base == -1 { |
| 225 | if c.Version() == 0 { |
| 226 | base = mbase.Base58BTC |
| 227 | } else { |
| 228 | base, _ = cid.ExtractEncoding(cidStr) |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | str, err := cidutil.Format(opts.fmtStr, base, c) |
| 233 | if _, ok := err.(cidutil.FormatStringError); ok { |
| 234 | // no point in continuing if there is a problem with the format string |
| 235 | return err |
| 236 | } |
| 237 | if err != nil { |
| 238 | res.ErrorMsg = err.Error() |
| 239 | } else { |
| 240 | res.Formatted = str |
| 241 | } |
| 242 | emitErr = resp.Emit(res) |
| 243 | } |
| 244 | if emitErr != nil { |
| 245 | return emitErr |
| 246 | } |
| 247 | err := itr.err() |
| 248 | if err != nil { |
| 249 | return err |
| 250 | } |
| 251 | return nil |
| 252 | } |
| 253 | |
| 254 | func toCidV0(c cid.Cid) (cid.Cid, error) { |
| 255 | if c.Type() != cid.DagProtobuf { |
| 256 | return cid.Cid{}, fmt.Errorf("can't convert non-dag-pb nodes to cidv0") |
| 257 | } |
| 258 | return cid.NewCidV0(c.Hash()), nil |
| 259 | } |
| 260 | |
| 261 | func toCidV1(c cid.Cid) (cid.Cid, error) { |
| 262 | return cid.NewCidV1(c.Type(), c.Hash()), nil |
| 263 | } |
| 264 | |
| 265 | type CodeAndName struct { |
| 266 | Code int |
| 267 | Name string |
| 268 | } |
| 269 | |
| 270 | const ( |
| 271 | prefixOptionName = "prefix" |
| 272 | numericOptionName = "numeric" |
| 273 | ) |
| 274 | |
| 275 | var basesCmd = &cmds.Command{ |
| 276 | Helptext: cmds.HelpText{ |
| 277 | Tagline: "List available multibase encodings.", |
| 278 | ShortDescription: ` |
| 279 | 'ipfs cid bases' relies on https://github.com/multiformats/go-multibase |
| 280 | `, |
| 281 | }, |
| 282 | Options: []cmds.Option{ |
| 283 | cmds.BoolOption(prefixOptionName, "also include the single letter prefixes in addition to the code"), |
| 284 | cmds.BoolOption(numericOptionName, "also include numeric codes"), |
| 285 | }, |
| 286 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 287 | var res []CodeAndName |
| 288 | // use EncodingToStr in case at some point there are multiple names for a given code |
| 289 | for code, name := range mbase.EncodingToStr { |
| 290 | res = append(res, CodeAndName{int(code), name}) |
| 291 | } |
| 292 | return cmds.EmitOnce(resp, res) |
| 293 | }, |
| 294 | Encoders: cmds.EncoderMap{ |
| 295 | cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, val []CodeAndName) error { |
| 296 | prefixes, _ := req.Options[prefixOptionName].(bool) |
| 297 | numeric, _ := req.Options[numericOptionName].(bool) |
| 298 | multibaseSorter{val}.Sort() |
| 299 | for _, v := range val { |
| 300 | code := v.Code |
| 301 | if !unicode.IsPrint(rune(code)) { |
| 302 | // don't display non-printable prefixes |
| 303 | code = ' ' |
| 304 | } |
| 305 | switch { |
| 306 | case prefixes && numeric: |
| 307 | fmt.Fprintf(w, "%c %7d %s\n", code, v.Code, v.Name) |
| 308 | case prefixes: |
| 309 | fmt.Fprintf(w, "%c %s\n", code, v.Name) |
| 310 | case numeric: |
| 311 | fmt.Fprintf(w, "%7d %s\n", v.Code, v.Name) |
| 312 | default: |
| 313 | fmt.Fprintf(w, "%s\n", v.Name) |
| 314 | } |
| 315 | } |
| 316 | return nil |
| 317 | }), |
| 318 | }, |
| 319 | Type: []CodeAndName{}, |
| 320 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 321 | } |
| 322 | |
| 323 | const ( |
| 324 | codecsNumericOptionName = "numeric" |
| 325 | codecsSupportedOptionName = "supported" |
| 326 | ) |
| 327 | |
| 328 | var codecsCmd = &cmds.Command{ |
| 329 | Helptext: cmds.HelpText{ |
| 330 | Tagline: "List available CID multicodecs.", |
| 331 | ShortDescription: ` |
| 332 | 'ipfs cid codecs' relies on https://github.com/multiformats/go-multicodec |
| 333 | `, |
| 334 | }, |
| 335 | Options: []cmds.Option{ |
| 336 | cmds.BoolOption(codecsNumericOptionName, "n", "also include numeric codes"), |
| 337 | cmds.BoolOption(codecsSupportedOptionName, "s", "list only codecs supported by go-ipfs commands"), |
| 338 | }, |
| 339 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 340 | listSupported, _ := req.Options[codecsSupportedOptionName].(bool) |
| 341 | supportedCodecs := make(map[uint64]struct{}) |
| 342 | if listSupported { |
| 343 | for _, code := range ipldmulticodec.ListEncoders() { |
| 344 | supportedCodecs[code] = struct{}{} |
| 345 | } |
| 346 | for _, code := range ipldmulticodec.ListDecoders() { |
| 347 | supportedCodecs[code] = struct{}{} |
| 348 | } |
| 349 | // add libp2p-key |
| 350 | supportedCodecs[uint64(mc.Libp2pKey)] = struct{}{} |
| 351 | } |
| 352 | |
| 353 | var res []CodeAndName |
| 354 | for _, code := range mc.KnownCodes() { |
| 355 | if code.Tag() == "ipld" { |
| 356 | if listSupported { |
| 357 | if _, ok := supportedCodecs[uint64(code)]; !ok { |
| 358 | continue |
| 359 | } |
| 360 | } |
| 361 | res = append(res, CodeAndName{int(code), mc.Code(code).String()}) |
| 362 | } |
| 363 | } |
| 364 | return cmds.EmitOnce(resp, res) |
| 365 | }, |
| 366 | Encoders: cmds.EncoderMap{ |
| 367 | cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, val []CodeAndName) error { |
| 368 | numeric, _ := req.Options[codecsNumericOptionName].(bool) |
| 369 | codeAndNameSorter{val}.Sort() |
| 370 | for _, v := range val { |
| 371 | if numeric { |
| 372 | fmt.Fprintf(w, "%5d %s\n", v.Code, v.Name) |
| 373 | } else { |
| 374 | fmt.Fprintf(w, "%s\n", v.Name) |
| 375 | } |
| 376 | } |
| 377 | return nil |
| 378 | }), |
| 379 | }, |
| 380 | Type: []CodeAndName{}, |
| 381 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 382 | } |
| 383 | |
| 384 | var hashesCmd = &cmds.Command{ |
| 385 | Helptext: cmds.HelpText{ |
| 386 | Tagline: "List available multihashes.", |
| 387 | ShortDescription: ` |
| 388 | 'ipfs cid hashes' relies on https://github.com/multiformats/go-multihash |
| 389 | `, |
| 390 | }, |
| 391 | Options: codecsCmd.Options, |
| 392 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 393 | var res []CodeAndName |
| 394 | // use mhash.Codes in case at some point there are multiple names for a given code |
| 395 | for code, name := range mhash.Codes { |
| 396 | if !verifcid.DefaultAllowlist.IsAllowed(code) { |
| 397 | continue |
| 398 | } |
| 399 | res = append(res, CodeAndName{int(code), name}) |
| 400 | } |
| 401 | return cmds.EmitOnce(resp, res) |
| 402 | }, |
| 403 | Encoders: codecsCmd.Encoders, |
| 404 | Type: codecsCmd.Type, |
| 405 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 406 | } |
| 407 | |
| 408 | // CidInspectRes represents the response from the inspect command. |
| 409 | type CidInspectRes struct { |
| 410 | Cid string `json:"cid"` |
| 411 | Version int `json:"version"` |
| 412 | Multibase CidInspectBase `json:"multibase"` |
| 413 | Multicodec CidInspectCodec `json:"multicodec"` |
| 414 | Multihash CidInspectHash `json:"multihash"` |
| 415 | CidV0 string `json:"cidV0,omitempty"` |
| 416 | CidV1 string `json:"cidV1"` |
| 417 | ErrorMsg string `json:"errorMsg,omitempty"` |
| 418 | } |
| 419 | |
| 420 | type CidInspectBase struct { |
| 421 | Prefix string `json:"prefix"` |
| 422 | Name string `json:"name"` |
| 423 | } |
| 424 | |
| 425 | type CidInspectCodec struct { |
| 426 | Code uint64 `json:"code"` |
| 427 | Name string `json:"name"` |
| 428 | } |
| 429 | |
| 430 | type CidInspectHash struct { |
| 431 | Code uint64 `json:"code"` |
| 432 | Name string `json:"name"` |
| 433 | Length int `json:"length"` |
| 434 | Digest string `json:"digest"` |
| 435 | } |
| 436 | |
| 437 | var inspectCmd = &cmds.Command{ |
| 438 | Helptext: cmds.HelpText{ |
| 439 | Tagline: "Inspect and display detailed information about a CID.", |
| 440 | ShortDescription: ` |
| 441 | 'ipfs cid inspect' breaks down a CID and displays its components: |
| 442 | - CID version (0 or 1) |
| 443 | - Multibase encoding (explicit for CIDv1, implicit for CIDv0) |
| 444 | - Multicodec (DAG type) |
| 445 | - Multihash (hash algorithm, length, and digest) |
| 446 | - Equivalent CIDv0 and CIDv1 representations |
| 447 | |
| 448 | For CIDv0, multibase, multicodec, and multihash are marked as |
| 449 | implicit because they are not explicitly encoded in the binary. |
| 450 | |
| 451 | If a PeerID string is provided instead of a CID, a helpful error |
| 452 | with the equivalent CID representation is returned. |
| 453 | |
| 454 | Use --enc=json for machine-readable output same as the HTTP RPC API. |
| 455 | `, |
| 456 | }, |
| 457 | Arguments: []cmds.Argument{ |
| 458 | cmds.StringArg("cid", true, false, "CID to inspect.").EnableStdin(), |
| 459 | }, |
| 460 | Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error { |
| 461 | cidStr := req.Arguments[0] |
| 462 | |
| 463 | c, err := cid.Decode(cidStr) |
| 464 | if err != nil { |
| 465 | errMsg := fmt.Sprintf("invalid CID: %s", err) |
| 466 | // PeerID fallback: try peer.Decode for legacy PeerIDs (12D3KooW..., Qm...) |
| 467 | if pid, pidErr := peer.Decode(cidStr); pidErr == nil { |
| 468 | pidCid := peer.ToCid(pid) |
| 469 | cidV1, _ := pidCid.StringOfBase(mbase.Base36) |
| 470 | errMsg += fmt.Sprintf("\nNote: the value is a PeerID; inspect its CID representation instead:\n %s", cidV1) |
| 471 | } |
| 472 | return cmds.EmitOnce(resp, &CidInspectRes{Cid: cidStr, ErrorMsg: errMsg}) |
| 473 | } |
| 474 | |
| 475 | res := &CidInspectRes{ |
| 476 | Cid: cidStr, |
| 477 | Version: int(c.Version()), |
| 478 | } |
| 479 | |
| 480 | // Multibase: always populated; CIDv0 uses implicit base58btc |
| 481 | if c.Version() == 0 { |
| 482 | res.Multibase = CidInspectBase{Prefix: "z", Name: "base58btc"} |
| 483 | } else { |
| 484 | baseCode, _ := cid.ExtractEncoding(cidStr) |
| 485 | res.Multibase = CidInspectBase{ |
| 486 | Prefix: string(rune(baseCode)), |
| 487 | Name: mbase.EncodingToStr[baseCode], |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | // Multicodec |
| 492 | codecName := mc.Code(c.Type()).String() |
| 493 | if codecName == "" || strings.HasPrefix(codecName, "Code(") { |
| 494 | codecName = "unknown" |
| 495 | } |
| 496 | res.Multicodec = CidInspectCodec{Code: c.Type(), Name: codecName} |
| 497 | |
| 498 | // Multihash |
| 499 | dmh, err := mhash.Decode(c.Hash()) |
| 500 | if err != nil { |
| 501 | return cmds.EmitOnce(resp, &CidInspectRes{ |
| 502 | Cid: cidStr, |
| 503 | ErrorMsg: fmt.Sprintf("failed to decode multihash: %s", err), |
| 504 | }) |
| 505 | } |
| 506 | hashName := mhash.Codes[dmh.Code] |
| 507 | if hashName == "" { |
| 508 | hashName = "unknown" |
| 509 | } |
| 510 | res.Multihash = CidInspectHash{ |
| 511 | Code: dmh.Code, |
| 512 | Name: hashName, |
| 513 | Length: dmh.Length, |
| 514 | Digest: hex.EncodeToString(dmh.Digest), |
| 515 | } |
| 516 | |
| 517 | // CIDv0: only possible with dag-pb + sha2-256-256 |
| 518 | if c.Type() == cid.DagProtobuf && dmh.Code == mhash.SHA2_256 && dmh.Length == 32 { |
| 519 | res.CidV0 = cid.NewCidV0(c.Hash()).String() |
| 520 | } |
| 521 | |
| 522 | // CIDv1: use base36 for libp2p-key, base32 for everything else |
| 523 | v1 := cid.NewCidV1(c.Type(), c.Hash()) |
| 524 | v1Base := mbase.Encoding(mbase.Base32) |
| 525 | if c.Type() == uint64(mc.Libp2pKey) { |
| 526 | v1Base = mbase.Base36 |
| 527 | } |
| 528 | v1Str, err := v1.StringOfBase(v1Base) |
| 529 | if err != nil { |
| 530 | v1Str = v1.String() |
| 531 | } |
| 532 | res.CidV1 = v1Str |
| 533 | |
| 534 | return cmds.EmitOnce(resp, res) |
| 535 | }, |
| 536 | Encoders: cmds.EncoderMap{ |
| 537 | cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, res *CidInspectRes) error { |
| 538 | if res.ErrorMsg != "" { |
| 539 | return fmt.Errorf("%s", res.ErrorMsg) |
| 540 | } |
| 541 | |
| 542 | implicit := "" |
| 543 | if res.Version == 0 { |
| 544 | implicit = ", implicit" |
| 545 | } |
| 546 | |
| 547 | fmt.Fprintf(w, "CID: %s\n", res.Cid) |
| 548 | fmt.Fprintf(w, "Version: %d\n", res.Version) |
| 549 | if res.Version == 0 { |
| 550 | fmt.Fprintf(w, "Multibase: %s (implicit)\n", res.Multibase.Name) |
| 551 | } else { |
| 552 | fmt.Fprintf(w, "Multibase: %s (%s)\n", res.Multibase.Name, res.Multibase.Prefix) |
| 553 | } |
| 554 | fmt.Fprintf(w, "Multicodec: %s (0x%x%s)\n", res.Multicodec.Name, res.Multicodec.Code, implicit) |
| 555 | fmt.Fprintf(w, "Multihash: %s (0x%x%s)\n", res.Multihash.Name, res.Multihash.Code, implicit) |
| 556 | fmt.Fprintf(w, " Length: %d bytes\n", res.Multihash.Length) |
| 557 | fmt.Fprintf(w, " Digest: %s\n", res.Multihash.Digest) |
| 558 | |
| 559 | if res.CidV0 != "" { |
| 560 | fmt.Fprintf(w, "CIDv0: %s\n", res.CidV0) |
| 561 | } else if res.Multicodec.Code != cid.DagProtobuf { |
| 562 | fmt.Fprintf(w, "CIDv0: not possible, requires dag-pb (0x70), got %s (0x%x)\n", |
| 563 | res.Multicodec.Name, res.Multicodec.Code) |
| 564 | } else if res.Multihash.Code != mhash.SHA2_256 { |
| 565 | fmt.Fprintf(w, "CIDv0: not possible, requires sha2-256 (0x12), got %s (0x%x)\n", |
| 566 | res.Multihash.Name, res.Multihash.Code) |
| 567 | } else if res.Multihash.Length != 32 { |
| 568 | fmt.Fprintf(w, "CIDv0: not possible, requires 32-byte digest, got %d\n", |
| 569 | res.Multihash.Length) |
| 570 | } |
| 571 | |
| 572 | fmt.Fprintf(w, "CIDv1: %s\n", res.CidV1) |
| 573 | |
| 574 | return nil |
| 575 | }), |
| 576 | }, |
| 577 | Type: CidInspectRes{}, |
| 578 | Extra: CreateCmdExtras(SetDoesNotUseRepo(true)), |
| 579 | } |
| 580 | |
| 581 | type multibaseSorter struct { |
| 582 | data []CodeAndName |
| 583 | } |
| 584 | |
| 585 | func (s multibaseSorter) Sort() { |
| 586 | slices.SortFunc(s.data, func(a, b CodeAndName) int { |
| 587 | if n := cmp.Compare(unicode.ToLower(rune(a.Code)), unicode.ToLower(rune(b.Code))); n != 0 { |
| 588 | return n |
| 589 | } |
| 590 | // lowercase letters should come before uppercase |
| 591 | return cmp.Compare(b.Code, a.Code) |
| 592 | }) |
| 593 | } |
| 594 | |
| 595 | type codeAndNameSorter struct { |
| 596 | data []CodeAndName |
| 597 | } |
| 598 | |
| 599 | func (s codeAndNameSorter) Sort() { |
| 600 | slices.SortFunc(s.data, func(a, b CodeAndName) int { |
| 601 | return cmp.Compare(a.Code, b.Code) |
| 602 | }) |
| 603 | } |