Compare commits

...

51 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
Jeromy
03f8d08574 gx publish 0.7.8 2017-02-02 18:39:03 -08:00
Jeromy
4ff5ee2b99 Update go-multibase, multihash. add test for hex 2017-02-02 18:39:00 -08:00
Jeromy Johnson
2b1b4a8339 Merge pull request #13 from ipfs/feat/gx-update-tree-hnwcen
gx update-tree go-cid go-libp2p-interface-pnet
2016-11-25 10:30:18 -08:00
Lars Gierth
ca0a568b21 gx publish 0.7.7 2016-11-25 18:47:39 +01:00
Jeromy Johnson
896454b8a7 Merge pull request #12 from ipfs/fix/build-failure
fix build failures i introduced
2016-11-23 10:14:26 -08:00
Jeromy
f623f824db fix build failures i introduced 2016-11-23 10:04:56 -08:00
Jeromy
15876e12dc update readme 2016-11-22 11:56:12 -08:00
Jeromy
18f61fe7b9 add readme and default files 2016-11-21 17:16:16 -08:00
Jeromy Johnson
f68586ac44 Merge pull request #9 from ipfs/fix/raw-id
fix raw codec number to be 0x55
2016-11-21 08:47:31 -08:00
Jeromy
1260e85a2d fix raw codec number to be 0x55 2016-11-21 08:39:15 -08:00
Jakub Sztandera
a0eff8c9d1 Merge pull request #8 from ipfs/feat/v0.7.6
MIT license, and go-cid v0.7.6
2016-11-18 01:07:25 +01:00
Lars Gierth
9c8abab049 go-cid v0.7.6 2016-11-18 00:56:28 +01:00
Lars Gierth
ab63d19c4e Add MIT license 2016-11-18 00:50:33 +01:00
Jeromy Johnson
5ccc98a754 Merge pull request #7 from ipfs/feat/fuzz
Add fuzz tests, fix panic
2016-11-17 10:25:44 -08:00
Jakub Sztandera
ba97b640bd Check length of copy to be extra sure we are copying whole thing 2016-11-17 19:24:12 +01:00
Jakub Sztandera
9116bf8025 Fix lengths in prefix too 2016-11-17 19:16:05 +01:00
Jakub Sztandera
c67fe910f2 Test prefix.Bytes() in fuzz tests 2016-11-17 19:03:01 +01:00
Jakub Sztandera
9c3e314588 Use defined MaxVarintLen64 from stdlib 2016-11-17 19:01:33 +01:00
Jakub Sztandera
5da6d87c58 Add test for max lenght varint 2016-11-17 18:53:33 +01:00
Jakub Sztandera
6ce8a80816 Fix panic when length of varints is greater than 8
It can be 16
2016-11-17 18:53:33 +01:00
Jakub Sztandera
5ec5bbcb48 Improve cid_fuzz.go tests 2016-11-17 18:53:33 +01:00
Jakub Sztandera
8aeb1a44a8 Handle UVarit overflows 2016-11-17 18:53:33 +01:00
Jakub Sztandera
832b6a0170 Add basic fuzz test and basic corpus 2016-11-17 18:53:33 +01:00
Jeromy Johnson
eae3431cc9 Merge pull request #4 from ipfs/feat/parse
Add Parse func accepting various types
2016-11-17 09:35:58 -08:00
Lars Gierth
d0e0822854 Add Parse func accepting various types 2016-11-17 17:25:57 +01:00
Jeromy
bffa0300df gx publish 0.7.5 2016-11-10 10:26:33 -08:00
Jeromy
691f8625be add zcash multicodecs to table 2016-11-10 10:26:18 -08:00
Jeromy
177ab398cc gx publish 0.7.3 2016-10-24 17:31:04 -07:00
Jeromy
02ce4e9b23 fix decoding of empty strings 2016-10-24 17:30:53 -07:00
Jeromy
460554fb6e gx publish 0.7.2 2016-10-24 09:16:05 -07:00
Jeromy
5d17ab8c70 add bitcoin tx value 2016-10-24 09:10:06 -07:00
Jeromy
ed8d621d9a gx publish 0.7.0 2016-10-19 17:54:06 -07:00
Jeromy
9a43493ca7 add codes for bitcoin and ethereum 2016-10-19 17:53:39 -07:00
Jeromy
8677934d48 add small test to fuzz cid creation routines 2016-10-09 12:19:14 -07:00
Jeromy
5f11b062c3 gx publish 0.6.0 2016-10-08 21:18:24 -07:00
Jeromy
961f0fe7a8 add 'Prefix' object and some more helper routines 2016-10-08 21:17:59 -07:00
Jeromy
6c7d9e3de2 gx publish 0.5.2 2016-10-05 12:08:42 -07:00
Jeromy
e5a96152bd gx publish 0.5.1 2016-10-05 11:37:05 -07:00
Jeromy
0f69fedd3a gx publish 0.5.0 2016-09-27 06:14:57 -07:00
Jeromy
595cace68a make cid's loggable 2016-09-27 06:14:46 -07:00
13 changed files with 680 additions and 27 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
cid-fuzz.zip

View File

@@ -1 +1 @@
0.3.0: QmfAjb1QYA9SS9TLVJBRZXEVriGaGrRZ3vJSajhLa52aYg 0.7.13: QmZe3S3sXMWar3oPmrd8jHV8NoAWfPfLGFD8PsK3YrYpqv

24
.travis.yml Normal file
View File

@@ -0,0 +1,24 @@
os:
- linux
- osx
language: go
go:
- 1.7
install: true
before_install:
- make deps
script:
- go vet
- $GOPATH/bin/goveralls -service="travis-ci"
cache:
directories:
- $GOPATH/src/gx
notifications:
email: false

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Protocol Labs, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

16
Makefile Normal file
View File

@@ -0,0 +1,16 @@
all: deps
gx:
go get github.com/whyrusleeping/gx
go get github.com/whyrusleeping/gx-go
covertools:
go get github.com/mattn/goveralls
go get golang.org/x/tools/cmd/cover
deps: gx covertools
gx --verbose install --global
gx-go rewrite
publish:
gx-go rewrite --undo

125
README.md Normal file
View File

@@ -0,0 +1,125 @@
go-cid
==================
[![](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/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)
[![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.
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.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Contribute](#contribute)
- [License](#license)
## 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
make deps
```
This will make sure that dependencies are rewritten to known working versions.
### Examples
#### Parsing string input from users
```go
// Create a cid from a marshaled string
c, err := cid.Decode("zdvgqEMYmNeH5fKciougvQcfzMcNjF3Z1tPouJ8C7pc3pe63k")
if err != nil {...}
fmt.Println("Got CID: ", c)
```
#### Creating a CID from scratch
```go
// Create a cid manually by specifying the 'prefix' parameters
pref := cid.Prefix{
Version: 1,
Codec: cid.Raw,
MhType: mh.SHA2_256,
MhLength: -1, // default length
}
// And then feed it some data
c, err := pref.Sum([]byte("Hello World!"))
if err != nil {...}
fmt.Println("Created CID: ", c)
```
#### Check if two CIDs match
```go
// To test if two cid's are equivalent, be sure to use the 'Equals' method:
if c1.Equals(c2) {
fmt.Println("These two refer to the same exact data!")
}
```
#### Check if some data matches a given CID
```go
// To check if some data matches a given cid,
// Get your CIDs prefix, and use that to sum the data in question:
other, err := c.Prefix().Sum(mydata)
if err != nil {...}
if !c.Equals(other) {
fmt.Println("This data is different.")
}
```
## Contribute
PRs are welcome!
Small note: If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
## License
MIT © Jeromy Johnson

290
cid.go
View File

@@ -1,46 +1,146 @@
// 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"
"fmt" "fmt"
"strings"
mh "github.com/jbenet/go-multihash"
mbase "github.com/multiformats/go-multibase" mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
) )
// UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>" const UnsupportedVersionString = "<unsupported cid version>"
const ( var (
Protobuf = iota // ErrVarintBuffSmall means that a buffer passed to the cid parser was not
Raw // long enough, or did not contain an invalid cid
JSON ErrVarintBuffSmall = errors.New("reading varint: buffer too small")
CBOR
// 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")
) )
func NewCidV0(h mh.Multihash) *Cid { // 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 (
Raw = 0x55
DagProtobuf = 0x70
DagCBOR = 0x71
EthBlock = 0x90
EthBlockList = 0x91
EthTxTrie = 0x92
EthTx = 0x93
EthTxReceiptTrie = 0x94
EthTxReceipt = 0x95
EthStateTrie = 0x96
EthAccountSnapshot = 0x97
EthStorageTrie = 0x98
BitcoinBlock = 0xb0
BitcoinTx = 0xb1
ZcashBlock = 0xc0
ZcashTx = 0xc1
)
// 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: Protobuf, 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) {
switch v2 := v.(type) {
case string:
if strings.Contains(v2, "/ipfs/") {
return Decode(strings.Split(v2, "/ipfs/")[1])
}
return Decode(v2)
case []byte:
return Cast(v2)
case mh.Multihash:
return NewCidV0(v2), nil
case *Cid:
return v2, nil
default:
return nil, fmt.Errorf("can't parse %+v as Cid", v2)
}
}
// 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 {
return nil, ErrCidTooShort
}
if len(v) == 46 && v[:2] == "Qm" { if len(v) == 46 && v[:2] == "Qm" {
hash, err := mh.FromB58String(v) hash, err := mh.FromB58String(v)
if err != nil { if err != nil {
@@ -58,6 +158,28 @@ func Decode(v string) (*Cid, error) {
return Cast(data) return Cast(data)
} }
func uvError(read int) error {
switch {
case read == 0:
return ErrVarintBuffSmall
case read < 0:
return ErrVarintTooBig
default:
return nil
}
}
// 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)
@@ -66,18 +188,25 @@ func Cast(data []byte) (*Cid, error) {
} }
return &Cid{ return &Cid{
codec: Protobuf, codec: DagProtobuf,
version: 0, version: 0,
hash: h, hash: h,
}, nil }, nil
} }
vers, n := binary.Uvarint(data) vers, n := binary.Uvarint(data)
if err := uvError(n); err != nil {
return nil, err
}
if vers != 0 && vers != 1 { if vers != 0 && vers != 1 {
return nil, fmt.Errorf("invalid cid version number: %d", vers) return nil, fmt.Errorf("invalid cid version number: %d", vers)
} }
codec, cn := binary.Uvarint(data[n:]) codec, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return nil, err
}
rest := data[n+cn:] rest := data[n+cn:]
h, err := mh.Cast(rest) h, err := mh.Cast(rest)
@@ -92,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:
@@ -112,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:
@@ -132,25 +269,45 @@ func (c *Cid) bytesV0() []byte {
} }
func (c *Cid) bytesV1() []byte { func (c *Cid) bytesV1() []byte {
buf := make([]byte, 8+len(c.hash)) // 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, c.version)
n += binary.PutUvarint(buf[n:], c.codec) n += binary.PutUvarint(buf[n:], c.codec)
copy(buf[n:], c.hash) cn := copy(buf[n:], c.hash)
if cn != len(c.hash) {
panic("copy hash length is inconsistent")
}
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
} }
@@ -161,10 +318,109 @@ 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{} {
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
return Prefix{
MhType: dec.Code,
MhLength: dec.Length,
Version: c.version,
Codec: c.codec,
}
}
// 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 {
Version uint64
Codec uint64
MhType uint64
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) {
hash, err := mh.Sum(data, p.MhType, p.MhLength)
if err != nil {
return nil, err
}
switch p.Version {
case 0:
return NewCidV0(hash), nil
case 1:
return NewCidV1(p.Codec, hash), nil
default:
return nil, fmt.Errorf("invalid cid version")
}
}
// Bytes returns a byte representation of a Prefix. It looks like:
//
// <version><codec><mh-type><mh-length>
func (p Prefix) Bytes() []byte {
buf := make([]byte, 4*binary.MaxVarintLen64)
n := binary.PutUvarint(buf, p.Version)
n += binary.PutUvarint(buf[n:], p.Codec)
n += binary.PutUvarint(buf[n:], uint64(p.MhType))
n += binary.PutUvarint(buf[n:], uint64(p.MhLength))
return buf[:n]
}
// PrefixFromBytes parses a Prefix-byte representation onto a
// Prefix.
func PrefixFromBytes(buf []byte) (Prefix, error) {
r := bytes.NewReader(buf)
vers, err := binary.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
codec, err := binary.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
mhtype, err := binary.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
mhlen, err := binary.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
return Prefix{
Version: vers,
Codec: codec,
MhType: mhtype,
MhLength: int(mhlen),
}, nil
}

37
cid_fuzz.go Normal file
View File

@@ -0,0 +1,37 @@
// +build gofuzz
package cid
func Fuzz(data []byte) int {
cid, err := Cast(data)
if err != nil {
return 0
}
_ = cid.Bytes()
_ = cid.String()
p := cid.Prefix()
_ = p.Bytes()
if !cid.Equals(cid) {
panic("inequality")
}
// json loop
json, err := cid.MarshalJSON()
if err != nil {
panic(err.Error())
}
cid2 := &Cid{}
err = cid2.UnmarshalJSON(json)
if err != nil {
panic(err.Error())
}
if !cid.Equals(cid2) {
panic("json loop not equal")
}
return 1
}

View File

@@ -2,9 +2,13 @@ package cid
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt"
"math/rand"
"strings"
"testing" "testing"
mh "github.com/jbenet/go-multihash" mh "github.com/multiformats/go-multihash"
) )
func assertEqual(t *testing.T, a, b *Cid) { func assertEqual(t *testing.T, a, b *Cid) {
@@ -51,6 +55,13 @@ func TestBasicMarshaling(t *testing.T) {
assertEqual(t, cid, out2) assertEqual(t, cid, out2)
} }
func TestEmptyString(t *testing.T) {
_, err := Decode("")
if err == nil {
t.Fatal("shouldnt be able to parse an empty cid")
}
}
func TestV0Handling(t *testing.T) { func TestV0Handling(t *testing.T) {
old := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" old := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
@@ -79,3 +90,140 @@ func TestV0ErrorCases(t *testing.T) {
t.Fatal("should have failed to decode that ref") t.Fatal("should have failed to decode that ref")
} }
} }
func TestPrefixRoundtrip(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
c := NewCidV1(DagCBOR, hash)
pref := c.Prefix()
c2, err := pref.Sum(data)
if err != nil {
t.Fatal(err)
}
if !c.Equals(c2) {
t.Fatal("output didnt match original")
}
pb := pref.Bytes()
pref2, err := PrefixFromBytes(pb)
if err != nil {
t.Fatal(err)
}
if pref.Version != pref2.Version || pref.Codec != pref2.Codec ||
pref.MhType != pref2.MhType || pref.MhLength != pref2.MhLength {
t.Fatal("input prefix didnt match output")
}
}
func Test16BytesVarint(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
c := NewCidV1(DagCBOR, hash)
c.codec = 1 << 63
_ = c.Bytes()
}
func TestFuzzCid(t *testing.T) {
buf := make([]byte, 128)
for i := 0; i < 200; i++ {
s := rand.Intn(128)
rand.Read(buf[:s])
_, _ = Cast(buf[:s])
}
}
func TestParse(t *testing.T) {
cid, err := Parse(123)
if err == nil {
t.Fatalf("expected error from Parse()")
}
if !strings.Contains(err.Error(), "can't parse 123 as Cid") {
t.Fatalf("expected int error, got %s", err.Error())
}
theHash := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
h, err := mh.FromB58String(theHash)
if err != nil {
t.Fatal(err)
}
assertions := [][]interface{}{
[]interface{}{NewCidV0(h), theHash},
[]interface{}{NewCidV0(h).Bytes(), theHash},
[]interface{}{h, theHash},
[]interface{}{theHash, theHash},
[]interface{}{"/ipfs/" + theHash, theHash},
[]interface{}{"https://ipfs.io/ipfs/" + theHash, theHash},
[]interface{}{"http://localhost:8080/ipfs/" + theHash, theHash},
}
assert := func(arg interface{}, expected string) error {
cid, err = Parse(arg)
if err != nil {
return err
}
if cid.version != 0 {
return fmt.Errorf("expected version 0, got %s", string(cid.version))
}
actual := cid.Hash().B58String()
if actual != expected {
return fmt.Errorf("expected hash %s, got %s", expected, actual)
}
actual = cid.String()
if actual != expected {
return fmt.Errorf("expected string %s, got %s", expected, actual)
}
return nil
}
for _, args := range assertions {
err := assert(args[0], args[1].(string))
if err != nil {
t.Fatal(err)
}
}
}
func TestHexDecode(t *testing.T) {
hexcid := "f015512209d8453505bdc6f269678e16b3e56c2a2948a41f2c792617cc9611ed363c95b63"
c, err := Decode(hexcid)
if err != nil {
t.Fatal(err)
}
if c.String() != "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" {
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")
}
}

1
fuzz-data/corpus/cid0 Normal file
View File

@@ -0,0 +1 @@
 ëgáD1üüÊe<C38A>-D˜/¹q3ø~å(Ä7`8<38>‡n

1
fuzz-data/corpus/cid1 Normal file
View File

@@ -0,0 +1 @@
q -[<5B>ï<EFBFBD>h<EFBFBD>[<5B><10><>

View File

@@ -9,22 +9,22 @@
"gxDependencies": [ "gxDependencies": [
{ {
"author": "whyrusleeping", "author": "whyrusleeping",
"hash": "QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku", "hash": "QmVGtdTZdTFaLsaj2RwdVG8jcjNNcp1DE914DKZ2kHmXHw",
"name": "go-multihash", "name": "go-multihash",
"version": "0.0.0" "version": "1.0.4"
}, },
{ {
"author": "whyrusleeping", "author": "whyrusleeping",
"hash": "QmYiTi9mKBMjfiup7na7PhJK7QEZPdMTJenLdgFYVQ2NUv", "hash": "Qme4T6BE4sQxg7ZouamF5M7Tx1ZFTqzcns7BkyQPXpoT99",
"name": "go-multibase", "name": "go-multibase",
"version": "0.2.0" "version": "0.2.4"
} }
], ],
"gxVersion": "0.8.0", "gxVersion": "0.8.0",
"language": "go", "language": "go",
"license": "", "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.3.0" "version": "0.7.13"
} }

27
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 {
var out []*Cid 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)
@@ -42,3 +52,16 @@ 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 {
for cs := range s.set {
c, _ := Cast([]byte(cs))
err := f(c)
if err != nil {
return err
}
}
return nil
}