diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 7dacb8a..8db060b 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -133,27 +133,11 @@ func errorMsg(fmtStr string, a ...interface{}) { } func decode(v string) (mb.Encoding, *c.Cid, error) { - if len(v) < 2 { - return 0, nil, c.ErrCidTooShort - } - - if len(v) == 46 && v[:2] == "Qm" { - hash, err := mh.FromB58String(v) - if err != nil { - return 0, nil, err - } - - return mb.Base58BTC, c.NewCidV0(hash), nil - } - - base, data, err := mb.Decode(v) + cid, err := c.Decode(v) if err != nil { - return 0, nil, err + return -1, cid, err } - - cid, err := c.Cast(data) - - return base, cid, err + return cid.Base().Encoding(), cid, err } const ERR_STR = "!ERROR!" diff --git a/cid.go b/cid.go index 7859f75..72770e9 100644 --- a/cid.go +++ b/cid.go @@ -31,6 +31,9 @@ import ( mh "github.com/multiformats/go-multihash" ) +// DefaultBase is the default base to use when encoding CidV1 +var DefaultBase mbase.Encoding = mbase.Base58BTC + // UnsupportedVersionString just holds an error message const UnsupportedVersionString = "" @@ -134,6 +137,7 @@ var CodecToStr = map[uint64]string{ // NewCidV1 should be used preferentially. func NewCidV0(mhash mh.Multihash) *Cid { return &Cid{ + base: -1, version: 0, codec: DagProtobuf, hash: mhash, @@ -144,6 +148,7 @@ func NewCidV0(mhash mh.Multihash) *Cid { // content type. func NewCidV1(codecType uint64, mhash mh.Multihash) *Cid { return &Cid{ + base: -1, version: 1, codec: codecType, hash: mhash, @@ -175,6 +180,7 @@ func NewPrefixV1(codecType uint64, mhType uint64) Prefix { // identifier. It is formed by a Version, a Codec (which indicates // a multicodec-packed content type) and a Multihash. type Cid struct { + base mbase.Encoding // -1 if not defined version uint64 codec uint64 hash mh.Multihash @@ -226,12 +232,17 @@ func Decode(v string) (*Cid, error) { return NewCidV0(hash), nil } - _, data, err := mbase.Decode(v) + base, data, err := mbase.Decode(v) if err != nil { return nil, err } - return Cast(data) + c, err := Cast(data) + if err != nil { + return nil, err + } + c.base = base + return c, nil } func uvError(read int) error { @@ -263,11 +274,7 @@ func Cast(data []byte) (*Cid, error) { return nil, err } - return &Cid{ - codec: DagProtobuf, - version: 0, - hash: h, - }, nil + return NewCidV0(h), nil } vers, n := binary.Uvarint(data) @@ -291,6 +298,7 @@ func Cast(data []byte) (*Cid, error) { } return &Cid{ + base: -1, version: vers, codec: codec, hash: h, @@ -302,27 +310,56 @@ func (c *Cid) Type() uint64 { return c.codec } -// String returns the default string representation of a -// Cid. Currently, Base58 is used as the encoding for the -// multibase string. +func (c *Cid) HaveBase() bool { + return c.base != -1 +} + +func (c *Cid) Base() mbase.Encoder { + base := c.base + if c.base == -1 { + base = DefaultBase + if c.version == 0 { + base = mbase.Base58BTC + } + } + encoder, err := mbase.NewEncoder(base) + if err != nil { + panic(err) // should not happen + } + return encoder +} + +// WithBase changes the Multibase that associated with the Cid. If +// the Cid is version 0 then the multibase is ignored when conversting +// to a string, but the value is still associated with the Cid. This +// is useful when, for example, converting a CidV0 to CidV1. +func (c *Cid) WithBase(b mbase.Encoder) *Cid { + c2 := *c + c2.base = b.Encoding() + return &c2 +} + +// ResetBase resets the base to the default value +func (c *Cid) ResetBase() *Cid { + c2 := *c + c2.base = -1 + return &c2 +} + +// String returns the string representation of a Cid. func (c *Cid) String() string { switch c.version { case 0: return c.hash.B58String() case 1: - mbstr, err := mbase.Encode(mbase.Base58BTC, c.bytesV1()) - if err != nil { - panic("should not error with hardcoded mbase: " + err.Error()) - } - - return mbstr + return c.Base().Encode(c.bytesV1()) default: panic("not possible to reach this point") } } -// String returns the string representation of a Cid -// encoded is selected base +// StringOfBase returns the string representation of a Cid encoded is +// selected base. Deprecated use: WithBase(...).String() func (c *Cid) StringOfBase(base mbase.Encoding) (string, error) { switch c.version { case 0: @@ -376,6 +413,8 @@ func (c *Cid) bytesV1() []byte { // Equals checks that two Cids are the same. // In order for two Cids to be considered equal, the // Version, the Codec and the Multihash must match. +// Two Cids can be equal even if they have different +// multibases associated with them. func (c *Cid) Equals(o *Cid) bool { return c.codec == o.codec && c.version == o.version && @@ -404,6 +443,7 @@ func (c *Cid) UnmarshalJSON(b []byte) error { return err } + c.base = out.base c.version = out.version c.hash = out.hash c.codec = out.codec diff --git a/cid_test.go b/cid_test.go index ed690d8..ad3bcc1 100644 --- a/cid_test.go +++ b/cid_test.go @@ -163,23 +163,53 @@ func TestEmptyString(t *testing.T) { } func TestV0Handling(t *testing.T) { + origDef := DefaultBase old := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" + DefaultBase = mbase.Base58BTC + t.Run("DefaultBase=base58btc", func (t *testing.T) {testHandling(t, 0, old, old)}) + DefaultBase = mbase.Base32 + t.Run("DefaultBase=base32", func (t *testing.T) {testHandling(t, 0, old, old)}) + DefaultBase = origDef +} + +func TestV1Handling(t *testing.T) { + origDef := DefaultBase + new := "zdj7Wkkhxcu2rsiN6GUyHCLsSLL47kdUNfjbFqBUUhMFTZKBi" + base32 := "bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku" + DefaultBase = mbase.Base58BTC + t.Run("DefaultBase=base58btc", func (t *testing.T) {testHandling(t, 1, new, new)}) + DefaultBase = mbase.Base32 + t.Run("DefaultBase=base32", func (t *testing.T) {testHandling(t, 1, new, base32)}) + DefaultBase = origDef +} - cid, err := Decode(old) +func testHandling(t *testing.T, v uint64, cidStr, cidStr2 string) { + cid, err := Decode(cidStr) if err != nil { t.Fatal(err) } - if cid.version != 0 { - t.Fatal("should have gotten version 0 cid") + if cid.version != v { + t.Fatalf("should have gotten version %d cid", v) + } + + if v == 0 { + if cid.hash.B58String() != cidStr { + t.Fatal("marshaling roundtrip failed: B58String()") + } + } + + if cid.String() != cidStr { + t.Fatal("marshaling roundtrip failed: String()") } - if cid.hash.B58String() != old { - t.Fatal("marshaling roundtrip failed") + if cid.Base().Encoding() != mbase.Base58BTC { + t.Fatal("base wrong") } - if cid.String() != old { - t.Fatal("marshaling roundtrip failed") + cid = cid.ResetBase() + if cid.String() != cidStr2 { + t.Fatal("marshaling roundtrip failed after ResetBase()") } } @@ -355,7 +385,7 @@ func TestHexDecode(t *testing.T) { t.Fatal(err) } - if c.String() != "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" { + if c.String() != hexcid { t.Fatal("hash value failed to round trip decoding from hex") } } @@ -382,7 +412,7 @@ func TestFromJson(t *testing.T) { } if c.String() != cval { - t.Fatal("json parsing failed") + t.Fatalf("json parsing failed: %s != %s", c.String(), cval) } } diff --git a/package.json b/package.json index 4dcadb0..e6ea612 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ }, { "author": "whyrusleeping", - "hash": "QmexBtiTTEwwn42Yi6ouKt6VqzpA6wjJgiW1oh9VfaRrup", + "hash": "QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR", "name": "go-multibase", - "version": "0.2.6" + "version": "0.2.7" } ], "gxVersion": "0.8.0",