_rsrch/cidiface is an engineering experiment package to explore the right form for a CID. As such, it's not meant to be actually used, and could result in conflict if used. To avoid accidentally importing it (or even getting suggestions to do so), this package is moved into an "internal" folder, hinting tooling that it's not meant for external consumption.
165 lines
4.4 KiB
Go
165 lines
4.4 KiB
Go
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:
|
|
//
|
|
// <version><codec-type><multihash>
|
|
//
|
|
// 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
|
|
}
|