Compare commits

...

11 Commits

Author SHA1 Message Date
Jeromy
e310d23e36 gx publish 0.7.13 2017-05-01 17:37:29 -07:00
Jeromy
7e7f6811b4 gx publish 0.7.12 2017-03-24 18:53:57 -07:00
Jakub Sztandera
daa83089b3 Merge pull request #17 from ipfs/doc-improvements
Readme/golint: improve readme with gx instructions. Make golint happy.
2017-03-24 17:10:15 +01:00
Hector Sanjuan
7ef01bd895 Readme/golint: improve readme with gx instructions. Make golint happy.
License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2017-03-24 15:12:07 +01:00
Jeromy
05ed406b31 add correct eth codes 2017-03-20 18:00:06 -07:00
Jeromy
92cac2f002 add an example to the tests to play around with the feature 2017-03-18 19:17:44 -07:00
Jeromy
a22bf1e2bf gx publish 0.7.11 2017-02-05 23:52:16 -08:00
Jeromy
7bcaf9264a do cid json handling in this package 2017-02-05 23:52:06 -08:00
Jeromy
c1db98f9ae gx publish 0.7.10 2017-02-02 19:05:37 -08:00
Jeromy
40c5ff1bfe gx publish 0.7.9 2017-02-02 18:53:37 -08:00
Jeromy
ebda3c73ae actually update multihash package 2017-02-02 18:53:32 -08:00
6 changed files with 232 additions and 44 deletions

View File

@@ -1 +1 @@
0.7.8: QmXm6Di2T89xAeprno3x6nD6jRgtMm2wRy25DX4zc4gk8V 0.7.13: QmZe3S3sXMWar3oPmrd8jHV8NoAWfPfLGFD8PsK3YrYpqv

View File

@@ -4,13 +4,15 @@ go-cid
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![GoDoc](https://godoc.org/github.com/ipfs/go-cid?status.svg)](https://godoc.org/github.com/ipfs/go-cid)
[![Coverage Status](https://coveralls.io/repos/github/ipfs/go-cid/badge.svg?branch=master)](https://coveralls.io/github/ipfs/go-cid?branch=master) [![Coverage Status](https://coveralls.io/repos/github/ipfs/go-cid/badge.svg?branch=master)](https://coveralls.io/github/ipfs/go-cid?branch=master)
[![Travis CI](https://travis-ci.org/ipfs/go-cid.svg?branch=master)](https://travis-ci.org/ipfs/go-cid) [![Travis CI](https://travis-ci.org/ipfs/go-cid.svg?branch=master)](https://travis-ci.org/ipfs/go-cid)
> A package to handle content IDs in go. > A package to handle content IDs in Go.
This is an implementation in go of the [CID spec](https://github.com/ipld/cid). This is an implementation in Go of the [CID spec](https://github.com/ipld/cid).
It is used in go-ipfs and related packages to refer to a typed hunk of data. It is used in `go-ipfs` and related packages to refer to a typed hunk of data.
## Table of Contents ## Table of Contents
@@ -23,20 +25,46 @@ It is used in go-ipfs and related packages to refer to a typed hunk of data.
## Install ## Install
`go-cid` is a standard Go module which can be installed with:
```sh
go get github.com/ipfs/go-cid
```
Note that `go-cid` is packaged with Gx, so it is recommended to use Gx to install and use it (see Usage section).
## Usage
### Using Gx and Gx-go
This module is packaged with [Gx](https://github.com/whyrusleeping/gx). In order to use it in your own project it is recommended that you:
```sh
go get -u github.com/whyrusleeping/gx
go get -u github.com/whyrusleeping/gx-go
cd <your-project-repository>
gx init
gx import github.com/ipfs/go-cid
gx install --global
gx-go --rewrite
```
Please check [Gx](https://github.com/whyrusleeping/gx) and [Gx-go](https://github.com/whyrusleeping/gx-go) documentation for more information.
### Running tests
Before running tests, please run:
```sh ```sh
make deps make deps
``` ```
## Examples This will make sure that dependencies are rewritten to known working versions.
To use CIDs, import it in your code like: ### Examples
```go
import "github.com/ipfs/go-cid"
```
Then, depending on how you want to use it, something like one of the following examples should work: #### Parsing string input from users
### Parsing string input from users
```go ```go
// Create a cid from a marshaled string // Create a cid from a marshaled string
c, err := cid.Decode("zdvgqEMYmNeH5fKciougvQcfzMcNjF3Z1tPouJ8C7pc3pe63k") c, err := cid.Decode("zdvgqEMYmNeH5fKciougvQcfzMcNjF3Z1tPouJ8C7pc3pe63k")
@@ -45,7 +73,8 @@ if err != nil {...}
fmt.Println("Got CID: ", c) fmt.Println("Got CID: ", c)
``` ```
### Creating a CID from scratch #### Creating a CID from scratch
```go ```go
// Create a cid manually by specifying the 'prefix' parameters // Create a cid manually by specifying the 'prefix' parameters
pref := cid.Prefix{ pref := cid.Prefix{
@@ -62,7 +91,8 @@ if err != nil {...}
fmt.Println("Created CID: ", c) fmt.Println("Created CID: ", c)
``` ```
### Check if two CIDs match #### Check if two CIDs match
```go ```go
// To test if two cid's are equivalent, be sure to use the 'Equals' method: // To test if two cid's are equivalent, be sure to use the 'Equals' method:
if c1.Equals(c2) { if c1.Equals(c2) {
@@ -70,7 +100,8 @@ if c1.Equals(c2) {
} }
``` ```
### Check if some data matches a given CID #### Check if some data matches a given CID
```go ```go
// To check if some data matches a given cid, // To check if some data matches a given cid,
// Get your CIDs prefix, and use that to sum the data in question: // Get your CIDs prefix, and use that to sum the data in question:

164
cid.go
View File

@@ -1,8 +1,28 @@
// Package cid implements the Content-IDentifiers specification
// (https://github.com/ipld/cid) in Go. CIDs are
// self-describing content-addressed identifiers useful for
// distributed information systems. CIDs are used in the IPFS
// (https://ipfs.io) project ecosystem.
//
// CIDs have two major versions. A CIDv0 corresponds to a multihash of type
// DagProtobuf, is deprecated and exists for compatibility reasons. Usually,
// CIDv1 should be used.
//
// A CIDv1 has four parts:
//
// <cidv1> ::= <multibase-prefix><cid-version><multicodec-packed-content-type><multihash-content-address>
//
// As shown above, the CID implementation relies heavily on Multiformats,
// particularly Multibase
// (https://github.com/multiformats/go-multibase), Multicodec
// (https://github.com/multiformats/multicodec) and Multihash
// implementations (https://github.com/multiformats/go-multihash).
package cid package cid
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@@ -11,44 +31,81 @@ import (
mh "github.com/multiformats/go-multihash" mh "github.com/multiformats/go-multihash"
) )
// UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>" const UnsupportedVersionString = "<unsupported cid version>"
var (
// ErrVarintBuffSmall means that a buffer passed to the cid parser was not
// long enough, or did not contain an invalid cid
ErrVarintBuffSmall = errors.New("reading varint: buffer too small")
// ErrVarintTooBig means that the varint in the given cid was above the
// limit of 2^64
ErrVarintTooBig = errors.New("reading varint: varint bigger than 64bits" +
" and not supported")
// ErrCidTooShort means that the cid passed to decode was not long
// enough to be a valid Cid
ErrCidTooShort = errors.New("cid too short")
)
// These are multicodec-packed content types. The should match
// the codes described in the authoritative document:
// https://github.com/multiformats/multicodec/blob/master/table.csv
const ( const (
Raw = 0x55 Raw = 0x55
DagProtobuf = 0x70 DagProtobuf = 0x70
DagCBOR = 0x71 DagCBOR = 0x71
EthereumBlock = 0x90 EthBlock = 0x90
EthereumTx = 0x91 EthBlockList = 0x91
BitcoinBlock = 0xb0 EthTxTrie = 0x92
BitcoinTx = 0xb1 EthTx = 0x93
ZcashBlock = 0xc0 EthTxReceiptTrie = 0x94
ZcashTx = 0xc1 EthTxReceipt = 0x95
EthStateTrie = 0x96
EthAccountSnapshot = 0x97
EthStorageTrie = 0x98
BitcoinBlock = 0xb0
BitcoinTx = 0xb1
ZcashBlock = 0xc0
ZcashTx = 0xc1
) )
func NewCidV0(h mh.Multihash) *Cid { // NewCidV0 returns a Cid-wrapped multihash.
// They exist to allow IPFS to work with Cids while keeping
// compatibility with the plain-multihash format used used in IPFS.
// NewCidV1 should be used preferentially.
func NewCidV0(mhash mh.Multihash) *Cid {
return &Cid{ return &Cid{
version: 0, version: 0,
codec: DagProtobuf, codec: DagProtobuf,
hash: h, hash: mhash,
} }
} }
func NewCidV1(c uint64, h mh.Multihash) *Cid { // NewCidV1 returns a new Cid using the given multicodec-packed
// content type.
func NewCidV1(codecType uint64, mhash mh.Multihash) *Cid {
return &Cid{ return &Cid{
version: 1, version: 1,
codec: c, codec: codecType,
hash: h, hash: mhash,
} }
} }
// 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 { type Cid struct {
version uint64 version uint64
codec uint64 codec uint64
hash mh.Multihash 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) { func Parse(v interface{}) (*Cid, error) {
switch v2 := v.(type) { switch v2 := v.(type) {
case string: case string:
@@ -67,9 +124,21 @@ func Parse(v interface{}) (*Cid, error) {
} }
} }
// Decode parses a Cid-encoded string and returns a Cid object.
// For CidV1, a Cid-encoded string is primarily a multibase string:
//
// <multibase-type-code><base-encoded-string>
//
// The base-encoded string represents a:
//
// <version><codec-type><multihash>
//
// Decode will also detect and parse CidV0 strings. Strings
// starting with "Qm" are considered CidV0 and treated directly
// as B58-encoded multihashes.
func Decode(v string) (*Cid, error) { func Decode(v string) (*Cid, error) {
if len(v) < 2 { if len(v) < 2 {
return nil, fmt.Errorf("cid too short") return nil, ErrCidTooShort
} }
if len(v) == 46 && v[:2] == "Qm" { if len(v) == 46 && v[:2] == "Qm" {
@@ -89,12 +158,6 @@ func Decode(v string) (*Cid, error) {
return Cast(data) return Cast(data)
} }
var (
ErrVarintBuffSmall = errors.New("reading varint: buffer to small")
ErrVarintTooBig = errors.New("reading varint: varint bigger than 64bits" +
" and not supported")
)
func uvError(read int) error { func uvError(read int) error {
switch { switch {
case read == 0: case read == 0:
@@ -106,6 +169,17 @@ func uvError(read int) error {
} }
} }
// Cast takes a Cid data slice, parses it and returns a Cid.
// 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.
//
// Please use decode when parsing a regular Cid string, as Cast does not
// expect multibase-encoded data. Cast accepts the output of Cid.Bytes().
func Cast(data []byte) (*Cid, error) { func Cast(data []byte) (*Cid, error) {
if len(data) == 34 && data[0] == 18 && data[1] == 32 { if len(data) == 34 && data[0] == 18 && data[1] == 32 {
h, err := mh.Cast(data) h, err := mh.Cast(data)
@@ -147,10 +221,14 @@ func Cast(data []byte) (*Cid, error) {
}, nil }, nil
} }
// Type returns the multicodec-packed content type of a Cid.
func (c *Cid) Type() uint64 { func (c *Cid) Type() uint64 {
return c.codec 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) String() string { func (c *Cid) String() string {
switch c.version { switch c.version {
case 0: case 0:
@@ -167,10 +245,14 @@ func (c *Cid) String() string {
} }
} }
// Hash returns the multihash contained by a Cid.
func (c *Cid) Hash() mh.Multihash { func (c *Cid) Hash() mh.Multihash {
return c.hash return c.hash
} }
// 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 { func (c *Cid) Bytes() []byte {
switch c.version { switch c.version {
case 0: case 0:
@@ -199,17 +281,33 @@ func (c *Cid) bytesV1() []byte {
return buf[:n+len(c.hash)] return buf[:n+len(c.hash)]
} }
// 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 { func (c *Cid) Equals(o *Cid) bool {
return c.codec == o.codec && return c.codec == o.codec &&
c.version == o.version && c.version == o.version &&
bytes.Equal(c.hash, o.hash) bytes.Equal(c.hash, o.hash)
} }
// UnmarshalJSON parses the JSON representation of a Cid.
func (c *Cid) UnmarshalJSON(b []byte) error { func (c *Cid) UnmarshalJSON(b []byte) error {
if len(b) < 2 { if len(b) < 2 {
return fmt.Errorf("invalid cid json blob") return fmt.Errorf("invalid cid json blob")
} }
out, err := Decode(string(b[1 : len(b)-1])) obj := struct {
CidTarget string `json:"/"`
}{}
err := json.Unmarshal(b, &obj)
if err != nil {
return err
}
if obj.CidTarget == "" {
return fmt.Errorf("cid was incorrectly formatted")
}
out, err := Decode(obj.CidTarget)
if err != nil { if err != nil {
return err return err
} }
@@ -220,20 +318,30 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
return nil return nil
} }
// MarshalJSON procudes a JSON representation of a Cid, which looks as follows:
//
// { "/": "<cid-string>" }
//
// Note that this formatting comes from the IPLD specification
// (https://github.com/ipld/specs/tree/master/ipld)
func (c *Cid) MarshalJSON() ([]byte, error) { func (c *Cid) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", c.String())), nil 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 { func (c *Cid) KeyString() string {
return string(c.Bytes()) return string(c.Bytes())
} }
// Loggable returns a Loggable (as defined by
// https://godoc.org/github.com/ipfs/go-log).
func (c *Cid) Loggable() map[string]interface{} { func (c *Cid) Loggable() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"cid": c, "cid": c,
} }
} }
// Prefix builds and returns a Prefix out of a Cid.
func (c *Cid) Prefix() Prefix { func (c *Cid) Prefix() Prefix {
dec, _ := mh.Decode(c.hash) // assuming we got a valid multiaddr, this will not error dec, _ := mh.Decode(c.hash) // assuming we got a valid multiaddr, this will not error
return Prefix{ return Prefix{
@@ -244,14 +352,19 @@ func (c *Cid) Prefix() Prefix {
} }
} }
// Prefix represents all the metadata of a cid, minus any actual content information // Prefix represents all the metadata of a Cid,
// that is, the Version, the Codec, the Multihash type
// and the Multihash length. It does not contains
// any actual content information.
type Prefix struct { type Prefix struct {
Version uint64 Version uint64
Codec uint64 Codec uint64
MhType int MhType uint64
MhLength int MhLength int
} }
// Sum uses the information in a prefix to perform a multihash.Sum()
// and return a newly constructed Cid with the resulting multihash.
func (p Prefix) Sum(data []byte) (*Cid, error) { func (p Prefix) Sum(data []byte) (*Cid, error) {
hash, err := mh.Sum(data, p.MhType, p.MhLength) hash, err := mh.Sum(data, p.MhType, p.MhLength)
if err != nil { if err != nil {
@@ -268,6 +381,9 @@ func (p Prefix) Sum(data []byte) (*Cid, error) {
} }
} }
// Bytes returns a byte representation of a Prefix. It looks like:
//
// <version><codec><mh-type><mh-length>
func (p Prefix) Bytes() []byte { func (p Prefix) Bytes() []byte {
buf := make([]byte, 4*binary.MaxVarintLen64) buf := make([]byte, 4*binary.MaxVarintLen64)
n := binary.PutUvarint(buf, p.Version) n := binary.PutUvarint(buf, p.Version)
@@ -277,6 +393,8 @@ func (p Prefix) Bytes() []byte {
return buf[:n] return buf[:n]
} }
// PrefixFromBytes parses a Prefix-byte representation onto a
// Prefix.
func PrefixFromBytes(buf []byte) (Prefix, error) { func PrefixFromBytes(buf []byte) (Prefix, error) {
r := bytes.NewReader(buf) r := bytes.NewReader(buf)
vers, err := binary.ReadUvarint(r) vers, err := binary.ReadUvarint(r)
@@ -302,7 +420,7 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
return Prefix{ return Prefix{
Version: vers, Version: vers,
Codec: codec, Codec: codec,
MhType: int(mhtype), MhType: mhtype,
MhLength: int(mhlen), MhLength: int(mhlen),
}, nil }, nil
} }

View File

@@ -2,6 +2,7 @@ package cid
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"strings" "strings"
@@ -200,3 +201,29 @@ func TestHexDecode(t *testing.T) {
t.Fatal("hash value failed to round trip decoding from hex") t.Fatal("hash value failed to round trip decoding from hex")
} }
} }
func ExampleDecode() {
encoded := "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG"
c, err := Decode(encoded)
if err != nil {
fmt.Printf("Error: %s", err)
return
}
fmt.Println(c)
// Output: zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG
}
func TestFromJson(t *testing.T) {
cval := "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG"
jsoncid := []byte(`{"/":"` + cval + `"}`)
var c Cid
err := json.Unmarshal(jsoncid, &c)
if err != nil {
t.Fatal(err)
}
if c.String() != cval {
t.Fatal("json parsing failed")
}
}

View File

@@ -9,15 +9,15 @@
"gxDependencies": [ "gxDependencies": [
{ {
"author": "whyrusleeping", "author": "whyrusleeping",
"hash": "QmYDds3421prZgqKbLpEK7T9Aa2eVdQ7o3YarX1LVLdP2J", "hash": "QmVGtdTZdTFaLsaj2RwdVG8jcjNNcp1DE914DKZ2kHmXHw",
"name": "go-multihash", "name": "go-multihash",
"version": "1.0.0" "version": "1.0.4"
}, },
{ {
"author": "whyrusleeping", "author": "whyrusleeping",
"hash": "QmXzWFN4iLdX1Vq8Sc13mET7aXsHkTyJoMbaJJD3NGRhiJ", "hash": "Qme4T6BE4sQxg7ZouamF5M7Tx1ZFTqzcns7BkyQPXpoT99",
"name": "go-multibase", "name": "go-multibase",
"version": "0.2.1" "version": "0.2.4"
} }
], ],
"gxVersion": "0.8.0", "gxVersion": "0.8.0",
@@ -25,6 +25,6 @@
"license": "MIT", "license": "MIT",
"name": "go-cid", "name": "go-cid",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"", "releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "0.7.8" "version": "0.7.13"
} }

16
set.go
View File

@@ -1,39 +1,49 @@
package cid package cid
// Set is a implementation of a set of Cids, that is, a structure
// to which holds a single copy of every Cids that is added to it.
type Set struct { type Set struct {
set map[string]struct{} set map[string]struct{}
} }
// NewSet initializes and returns a new Set.
func NewSet() *Set { func NewSet() *Set {
return &Set{set: make(map[string]struct{})} return &Set{set: make(map[string]struct{})}
} }
// Add puts a Cid in the Set.
func (s *Set) Add(c *Cid) { func (s *Set) Add(c *Cid) {
s.set[string(c.Bytes())] = struct{}{} s.set[string(c.Bytes())] = struct{}{}
} }
// Has returns if the Set contains a given Cid.
func (s *Set) Has(c *Cid) bool { func (s *Set) Has(c *Cid) bool {
_, ok := s.set[string(c.Bytes())] _, ok := s.set[string(c.Bytes())]
return ok return ok
} }
// Remove deletes a Cid from the Set.
func (s *Set) Remove(c *Cid) { func (s *Set) Remove(c *Cid) {
delete(s.set, string(c.Bytes())) delete(s.set, string(c.Bytes()))
} }
// Len returns how many elements the Set has.
func (s *Set) Len() int { func (s *Set) Len() int {
return len(s.set) return len(s.set)
} }
// Keys returns the Cids in the set.
func (s *Set) Keys() []*Cid { func (s *Set) Keys() []*Cid {
out := make([]*Cid, 0, len(s.set)) out := make([]*Cid, 0, len(s.set))
for k, _ := range s.set { for k := range s.set {
c, _ := Cast([]byte(k)) c, _ := Cast([]byte(k))
out = append(out, c) out = append(out, c)
} }
return out return out
} }
// Visit adds a Cid to the set only if it is
// not in it already.
func (s *Set) Visit(c *Cid) bool { func (s *Set) Visit(c *Cid) bool {
if !s.Has(c) { if !s.Has(c) {
s.Add(c) s.Add(c)
@@ -43,8 +53,10 @@ func (s *Set) Visit(c *Cid) bool {
return false return false
} }
// ForEach allows to run a custom function on each
// Cid in the set.
func (s *Set) ForEach(f func(c *Cid) error) error { func (s *Set) ForEach(f func(c *Cid) error) error {
for cs, _ := range s.set { for cs := range s.set {
c, _ := Cast([]byte(cs)) c, _ := Cast([]byte(cs))
err := f(c) err := f(c)
if err != nil { if err != nil {