Skip to content

Commit ae98932

Browse files
jolo18lidel
andauthored
feat(cmd): add 'ipfs cid inspect' command (#11241)
* feat(cmd): add 'ipfs cid inspect' command Adds a new subcommand to inspect and display detailed CID information including version, multibase encoding, multicodec, and multihash components. Also shows equivalent CIDv0/CIDv1 representations. Example output: CID: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi Version: 1 Multibase: base32 (b) Multicodec: dag-pb (0x70) Multihash: sha2-256 (0x12) Length: 32 bytes Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi Supports --enc=json for machine-readable output. Fixes #11205 * refactor: tidy up CidInspectRes struct and add '/cid/inspect' route to tests This commit refines the CidInspectRes struct for better readability and consistency. Additionally, it includes the '/cid/inspect' route in the command tests to ensure comprehensive coverage of the new CID inspection functionality. * docs: update changelog for v0.42 to include new `ipfs cid inspect` command Added a section highlighting the new `ipfs cid inspect <cid>` command, detailing its functionality to display comprehensive CID information, including version, encoding, and hash details. The command supports machine-readable output and operates offline. * feat(cmd): improve ipfs cid inspect - multibase: always shown (implicit for CIDv0), prefix as string - multicodec/multihash: annotated (implicit) for CIDv0 - digest: uppercase hex with 0x prefix - cidV0: empty in JSON when not possible, text encoder explains why - cidV1: base36 for libp2p-key codec, base32 otherwise - errors: ErrorMsg kept for HTTP RPC API, text encoder returns non-zero exit - PeerID fallback: helpful hint with equivalent CID on invalid input - unknown codec/hash: graceful "unknown" label - stdin support via .EnableStdin() - inspect listed first in subcommands, cid format points to inspect - cli tests for all cases including JSON, PeerID, unknown codec * chore: move cid inspect changelog to v0.41 - digest: bare lowercase hex (no 0x prefix), matching sha256sum --------- Co-authored-by: Marcin Rataj <lidel@lidel.org>
1 parent d843bd8 commit ae98932

File tree

4 files changed

+370
-5
lines changed

4 files changed

+370
-5
lines changed

core/commands/cid.go

Lines changed: 183 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"cmp"
5+
"encoding/hex"
56
"errors"
67
"fmt"
78
"io"
@@ -14,6 +15,7 @@ import (
1415
cidutil "github.com/ipfs/go-cidutil"
1516
cmds "github.com/ipfs/go-ipfs-cmds"
1617
ipldmulticodec "github.com/ipld/go-ipld-prime/multicodec"
18+
peer "github.com/libp2p/go-libp2p/core/peer"
1719
mbase "github.com/multiformats/go-multibase"
1820
mc "github.com/multiformats/go-multicodec"
1921
mhash "github.com/multiformats/go-multihash"
@@ -24,11 +26,12 @@ var CidCmd = &cmds.Command{
2426
Tagline: "Convert and discover properties of CIDs",
2527
},
2628
Subcommands: map[string]*cmds.Command{
27-
"format": cidFmtCmd,
28-
"base32": base32Cmd,
29-
"bases": basesCmd,
30-
"codecs": codecsCmd,
31-
"hashes": hashesCmd,
29+
"inspect": inspectCmd,
30+
"format": cidFmtCmd,
31+
"base32": base32Cmd,
32+
"bases": basesCmd,
33+
"codecs": codecsCmd,
34+
"hashes": hashesCmd,
3235
},
3336
Extra: CreateCmdExtras(SetDoesNotUseRepo(true)),
3437
}
@@ -46,6 +49,8 @@ var cidFmtCmd = &cmds.Command{
4649
LongDescription: `
4750
Format and converts <cid>'s in various useful ways.
4851
52+
For a human-readable breakdown of a CID, see 'ipfs cid inspect'.
53+
4954
The optional format string is a printf style format string:
5055
` + cidutil.FormatRef,
5156
},
@@ -400,6 +405,179 @@ var hashesCmd = &cmds.Command{
400405
Extra: CreateCmdExtras(SetDoesNotUseRepo(true)),
401406
}
402407

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+
403581
type multibaseSorter struct {
404582
data []CodeAndName
405583
}

core/commands/commands_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func TestCommands(t *testing.T) {
4040
"/cid/codecs",
4141
"/cid/format",
4242
"/cid/hashes",
43+
"/cid/inspect",
4344
"/commands",
4445
"/commands/completion",
4546
"/commands/completion/bash",

docs/changelogs/v0.41.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
1111
- [Overview](#overview)
1212
- [🔦 Highlights](#-highlights)
1313
- [🗑️ Faster Provide Queue Disk Reclamation](#-faster-provide-queue-disk-reclamation)
14+
- [✨ New `ipfs cid inspect` command](#-new-ipfs-cid-inspect-command)
1415
- [🖥️ WebUI Improvements](#-webui-improvements)
1516
- [🔧 Correct provider addresses for custom HTTP routing](#-correct-provider-addresses-for-custom-http-routing)
1617
- [📦️ Dependency updates](#-dependency-updates)
@@ -42,6 +43,25 @@ To learn more, see [kubo#11096](https://github.com/ipfs/kubo/issues/11096),
4243
[kubo#11198](https://github.com/ipfs/kubo/pull/11198), and
4344
[go-libp2p-kad-dht#1233](https://github.com/libp2p/go-libp2p-kad-dht/pull/1233).
4445

46+
#### ✨ New `ipfs cid inspect` command
47+
48+
New subcommand for breaking down a CID into its components. Works offline, supports `--enc=json`.
49+
50+
```console
51+
$ ipfs cid inspect bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
52+
CID: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
53+
Version: 1
54+
Multibase: base32 (b)
55+
Multicodec: dag-pb (0x70)
56+
Multihash: sha2-256 (0x12)
57+
Length: 32 bytes
58+
Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a
59+
CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR
60+
CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
61+
```
62+
63+
See `ipfs cid --help` for all CID-related commands.
64+
4565
#### 🖥️ WebUI Improvements
4666

4767
IPFS Web UI has been updated to [v4.12.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.12.0).

0 commit comments

Comments
 (0)