package cid import ( "encoding/binary" "fmt" mbase "github.com/multiformats/go-multibase" mh "github.com/multiformats/go-multihash" ) //================= // def & accessors //================= var _ Cid = CidStruct{} //var _ map[CidStruct]struct{} = nil // Will not compile! See struct def docs. //var _ map[Cid]struct{} = map[Cid]struct{}{CidStruct{}: struct{}{}} // Legal to compile... // but you'll get panics: "runtime error: hash of unhashable type cid.CidStruct" // CidStruct represents a CID in a struct format. // // This format complies with the exact same Cid interface as the CidStr // implementation, but completely pre-parses the Cid metadata. // CidStruct is a tad quicker in case of repeatedly accessed fields, // but requires more reshuffling to parse and to serialize. // CidStruct is not usable as a map key, because it contains a Multihash // reference, which is a slice, and thus not "comparable" as a primitive. // // Beware of zero-valued CidStruct: it is difficult to distinguish an // incorrectly-initialized "invalid" CidStruct from one representing a v0 cid. type CidStruct struct { version uint64 codec uint64 hash mh.Multihash } // EmptyCidStruct is a constant for a zero/uninitialized/sentinelvalue cid; // it is declared mainly for readability in checks for sentinel values. // // Note: it's not actually a const; the compiler does not allow const structs. var EmptyCidStruct = CidStruct{} func (c CidStruct) Version() uint64 { return c.version } func (c CidStruct) Multicodec() uint64 { return c.codec } func (c CidStruct) Multihash() mh.Multihash { return c.hash } // String returns the default string representation of a Cid. // Currently, Base58 is used as the encoding for the multibase string. func (c CidStruct) String() string { switch c.Version() { case 0: return c.Multihash().B58String() case 1: mbstr, err := mbase.Encode(mbase.Base58BTC, c.Bytes()) if err != nil { panic("should not error with hardcoded mbase: " + err.Error()) } return mbstr default: panic("not possible to reach this point") } } // Bytes produces a raw binary format of the CID. func (c CidStruct) Bytes() []byte { switch c.version { case 0: return []byte(c.hash) case 1: // 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)] default: panic("not possible to reach this point") } } // Prefix builds and returns a Prefix out of a Cid. func (c CidStruct) 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, } } //================================== // parsers & validators & factories //================================== // CidStructParse takes a binary byte slice, parses it, and returns either // a valid CidStruct, or the zero CidStruct and an error. // // For CidV1, the data buffer is in the form: // // // // CidV0 are also supported. In particular, data buffers starting // with length 34 bytes, which starts with bytes [18,32...] are considered // binary multihashes. // // The multicodec bytes are not parsed to verify they're a valid varint; // no further reification is performed. // // Multibase encoding should already have been unwrapped before parsing; // if you have a multibase-enveloped string, use CidStructDecode instead. // // CidStructParse is the inverse of Cid.Bytes(). func CidStructParse(data []byte) (CidStruct, error) { if len(data) == 34 && data[0] == 18 && data[1] == 32 { h, err := mh.Cast(data) if err != nil { return EmptyCidStruct, err } return CidStruct{ codec: DagProtobuf, version: 0, hash: h, }, nil } vers, n := binary.Uvarint(data) if err := uvError(n); err != nil { return EmptyCidStruct, err } if vers != 0 && vers != 1 { return EmptyCidStruct, fmt.Errorf("invalid cid version number: %d", vers) } codec, cn := binary.Uvarint(data[n:]) if err := uvError(cn); err != nil { return EmptyCidStruct, err } rest := data[n+cn:] h, err := mh.Cast(rest) if err != nil { return EmptyCidStruct, err } return CidStruct{ version: vers, codec: codec, hash: h, }, nil }