Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Use string as the internal representation and allow an associated multibase. #65

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 131 additions & 85 deletions cid.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ import (
mh "github.com/multiformats/go-multihash"
)

// Cid represents a self-describing content adressed
// identifier.
type Cid struct {
CidString
base mbase.Encoding // -1 if not defined
}

// CidString is a representation of a Cid as a binary string
type CidString string

// UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>"

const DefaultBase = mbase.Base58BTC

var (
// ErrVarintBuffSmall means that a buffer passed to the cid parser was not
// long enough, or did not contain an invalid cid
Expand Down Expand Up @@ -133,21 +145,23 @@ var CodecToStr = map[uint64]string{
// compatibility with the plain-multihash format used used in IPFS.
// NewCidV1 should be used preferentially.
func NewCidV0(mhash mh.Multihash) *Cid {
return &Cid{
version: 0,
codec: DagProtobuf,
hash: mhash,
}
return &Cid{CidString(mhash), -1}
}

// NewCidV1 returns a new Cid using the given multicodec-packed
// content type.
func NewCidV1(codecType uint64, mhash mh.Multihash) *Cid {
return &Cid{
version: 1,
codec: codecType,
hash: mhash,
hashlen := len(mhash)
// two 8 bytes (max) numbers plus hash
buf := make([]byte, 2*binary.MaxVarintLen64+hashlen)
n := binary.PutUvarint(buf, 1)
n += binary.PutUvarint(buf[n:], codecType)
cn := copy(buf[n:], mhash)
if cn != hashlen {
panic("copy hash length is inconsistent")
}

return &Cid{CidString(buf[:n+hashlen]), -1}
}

// NewPrefixV0 returns a CIDv0 prefix with the specified multihash type.
Expand All @@ -171,15 +185,6 @@ func NewPrefixV1(codecType uint64, mhType uint64) Prefix {
}
}

// Cid represents a self-describing content adressed
// identifier. It is formed by a Version, a Codec (which indicates
// a multicodec-packed content type) and a Multihash.
type Cid struct {
version uint64
codec uint64
hash mh.Multihash
}

// Parse is a short-hand function to perform Decode, Cast etc... on
// a generic interface{} type.
func Parse(v interface{}) (*Cid, error) {
Expand Down Expand Up @@ -226,12 +231,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
}

return &Cid{c.CidString, base}, nil
}

func uvError(read int) error {
Expand Down Expand Up @@ -263,11 +273,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)
Expand All @@ -279,7 +285,7 @@ func Cast(data []byte) (*Cid, error) {
return nil, fmt.Errorf("invalid cid version number: %d", vers)
}

codec, cn := binary.Uvarint(data[n:])
_, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return nil, err
}
Expand All @@ -290,96 +296,132 @@ func Cast(data []byte) (*Cid, error) {
return nil, err
}

return &Cid{
version: vers,
codec: codec,
hash: h,
}, nil
return &Cid{CidString(data[0 : n+cn+len(h)]), -1}, nil
}

// Version returns the Cid version
func (c CidString) Version() int {
if len(c) == 34 && c[0] == 18 && c[1] == 32 {
return 0
}
return 1
}

// Type returns the multicodec-packed content type of a Cid.
func (c *Cid) Type() uint64 {
return c.codec
func (c CidString) Type() uint64 {
if c.Version() == 0 {
return DagProtobuf
}
bytes := []byte(c)
_, n := binary.Uvarint(bytes)
codec, _ := binary.Uvarint(bytes[n:])
return codec
}

func (c CidString) WithBase(b mbase.Encoder) *Cid {
return &Cid{c, b.Encoding()}
}

func (c CidString) Base() (mbase.Encoder, bool) {
encoder, _ := mbase.NewEncoder(DefaultBase)
return encoder, false
}

func (c Cid) Base() (mbase.Encoder, bool) {
if c.base == -1 {
return c.CidString.Base()
}
encoder, err := mbase.NewEncoder(c.base)
if err != nil {
panic(err) // should not happen
}
return encoder, true
}

// String returns the default string representation of a
// Cid. Currently, Base58 is used as the encoding for the
// multibase string.
func (c *Cid) String() string {
switch c.version {
func (c CidString) String() string {
switch c.Version() {
case 0:
return c.hash.B58String()
return c.Hash().B58String()
case 1:
mbstr, err := mbase.Encode(mbase.Base58BTC, c.bytesV1())
mbstr, err := mbase.Encode(DefaultBase, c.Bytes())
if err != nil {
panic("should not error with hardcoded mbase: " + err.Error())
panic("should not error with default mbase: " + err.Error())
}

return mbstr
default:
panic("not possible to reach this point")
}
}

// String returns the string representation of a Cid.
func (c Cid) String() string {
if c.base == -1 || c.Version() == 0 {
return c.CidString.String()
}
mbstr, err := mbase.Encode(c.base, c.Bytes())
if err != nil {
panic(err) // should not happen
}
return mbstr
}

// String returns the string representation of a Cid
// encoded is selected base
func (c *Cid) StringOfBase(base mbase.Encoding) (string, error) {
switch c.version {
func (c CidString) StringOfBase(base mbase.Encoding) (string, error) {
switch c.Version() {
case 0:
if base != mbase.Base58BTC {
return "", ErrInvalidEncoding
}
return c.hash.B58String(), nil
return c.Hash().B58String(), nil
case 1:
return mbase.Encode(base, c.bytesV1())
return mbase.Encode(base, c.Bytes())
default:
panic("not possible to reach this point")
}
}

// Hash returns the multihash contained by a Cid.
func (c *Cid) Hash() mh.Multihash {
return c.hash
func (c CidString) Hash() mh.Multihash {
if c.Version() == 0 {
return mh.Multihash([]byte(c))
}

bytes := []byte(c)
// skip version length
_, n1 := binary.Uvarint(bytes)
// skip codec length
_, n2 := binary.Uvarint(bytes[n1:])

return mh.Multihash(bytes[n1+n2:])
}

// Bytes returns the byte representation of a Cid.
// The output of bytes can be parsed back into a Cid
// with Cast().
func (c *Cid) Bytes() []byte {
switch c.version {
case 0:
return c.bytesV0()
case 1:
return c.bytesV1()
default:
panic("not possible to reach this point")
}
}

func (c *Cid) bytesV0() []byte {
return []byte(c.hash)
}

func (c *Cid) bytesV1() []byte {
// two 8 bytes (max) numbers plus hash
buf := make([]byte, 2*binary.MaxVarintLen64+len(c.hash))
n := binary.PutUvarint(buf, c.version)
n += binary.PutUvarint(buf[n:], c.codec)
cn := copy(buf[n:], c.hash)
if cn != len(c.hash) {
panic("copy hash length is inconsistent")
}

return buf[:n+len(c.hash)]
func (c CidString) Bytes() []byte {
return []byte(c)
}

// 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.
func (c *Cid) Equals(o *Cid) bool {
return c.codec == o.codec &&
c.version == o.version &&
bytes.Equal(c.hash, o.hash)
func (c CidString) Equals(c0 *Cid) bool {
return c.KeyString() == c0.KeyString()
}

// UnmarshalJSON parses the JSON representation of a Cid.
func (c *CidString) UnmarshalJSON(b []byte) error {
c2 := &Cid{}
err := c2.UnmarshalJSON(b)
if err != nil {
return err
}
*c = c2.CidString
return nil
}

// UnmarshalJSON parses the JSON representation of a Cid.
Expand All @@ -404,9 +446,9 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
return err
}

c.version = out.version
c.hash = out.hash
c.codec = out.codec
c.CidString = out.CidString
c.base = out.base

return nil
}

Expand All @@ -416,31 +458,35 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
//
// Note that this formatting comes from the IPLD specification
// (https://github.com/ipld/specs/tree/master/ipld)
func (c CidString) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("{\"/\":\"%s\"}", c.String())), nil
}

func (c Cid) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("{\"/\":\"%s\"}", c.String())), nil
}

// KeyString casts the result of cid.Bytes() as a string, and returns it.
func (c *Cid) KeyString() string {
return string(c.Bytes())
func (c CidString) KeyString() CidString {
return c
}

// Loggable returns a Loggable (as defined by
// https://godoc.org/github.com/ipfs/go-log).
func (c *Cid) Loggable() map[string]interface{} {
func (c CidString) Loggable() map[string]interface{} {
return map[string]interface{}{
"cid": c,
}
}

// Prefix builds and returns a Prefix out of a Cid.
func (c *Cid) Prefix() Prefix {
dec, _ := mh.Decode(c.hash) // assuming we got a valid multiaddr, this will not error
func (c CidString) Prefix() Prefix {
dec, _ := mh.Decode(c.Hash()) // assuming we got a valid multiaddr, this will not error
return Prefix{
MhType: dec.Code,
MhLength: dec.Length,
Version: c.version,
Codec: c.codec,
Version: uint64(c.Version()),
Codec: c.Type(),
}
}

Expand Down
Loading