Compare commits

...

105 Commits

Author SHA1 Message Date
Steven Allen
73e5246a65 gx publish 0.7.25 2018-08-15 08:24:56 -07:00
Steven Allen
83a7594d41 Merge pull request #67 from ipfs/feat/streaming-set
add a streaming CID set
2018-08-11 01:06:53 +00:00
Łukasz Magiera
3655c1cdd4 add a streaming CID set
used in https://github.com/ipfs/go-ipfs/pull/4804
2018-08-10 17:32:43 -07:00
Steven Allen
1543f4a136 Merge pull request #44 from ipfs/feat/bench
add String benchmark
2018-08-10 23:55:30 +00:00
Steven Allen
d6e0b4e5a7 add String benchmark
We call String all over the place so we should make sure it remains fast.
2018-08-10 16:23:25 -07:00
Kevin Atkinson
5eff744da0 gx publish 0.7.24 2018-08-10 17:40:00 -04:00
Kevin Atkinson
a8ae38caae Merge pull request #61 from ipfs/kevina/cid-fmt-enhan
cid-fmt Enhancments
2018-08-10 17:35:25 -04:00
Kevin Atkinson
23f03cb301 Merge pull request #53 from ipfs/kevina/format
Create new Builder interface for creating CIDs.
2018-08-10 17:34:56 -04:00
Kevin Atkinson
1c907dba61 Allocate bytes.Buffer directly on the stack. 2018-08-10 17:27:58 -04:00
Kevin Atkinson
86805e711c Change field names in V1Builder to match Prefix. 2018-08-10 00:13:49 -04:00
Kevin Atkinson
f868375825 Enhance Tests. 2018-08-09 23:55:45 -04:00
Kevin Atkinson
8f7ba15bfb Documentation Cleanups. 2018-08-09 23:55:33 -04:00
Kevin Atkinson
ae25e25d1a Remove PrefixToBuilder as it is needed right now. 2018-08-09 23:14:37 -04:00
Kevin Atkinson
0f09109d9f Replace DecodeV2 with ExtractEncoding 2018-08-09 02:37:09 -04:00
Kevin Atkinson
67951e2c09 Move deprecated function to there own file. 2018-08-09 00:15:04 -04:00
Kevin Atkinson
ad88cb11c5 Rename Format to Builder. 2018-08-09 00:15:04 -04:00
Steven Allen
10944c9d86 Merge pull request #63 from ipfs/tests/set
Add tests for Set type
2018-08-02 19:25:20 +00:00
Hector Sanjuan
b340dd202e Add tests for Set type
License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2018-08-02 11:51:05 +02:00
Kevin Atkinson
c4bfcd0671 Extract most of cid-fmt logic so it can be used as a library. 2018-08-01 04:21:32 -04:00
Kevin Atkinson
36bab4873c Clean up cid-fmt util code. 2018-07-31 22:10:47 -04:00
Kevin Atkinson
056eac16ae Fix fmtRef string. 2018-07-31 17:12:16 -04:00
Kevin Atkinson
038b7f7cc9 Use new Encoder in multibase package.
This allows multibase codes to be specified by the name rather than just
the prefix char.
2018-07-31 17:07:49 -04:00
Kevin Atkinson
019d945bf5 Use lookup table in go-multibase now that it is supported. 2018-07-31 16:33:41 -04:00
Kevin Atkinson
799731b9e5 gx update go-multibase to 0.2.7 2018-07-31 16:33:41 -04:00
Hector Sanjuan
06f861b665 Merge pull request #59 from ipfs/0.7.23
gx publish 0.7.23
2018-07-28 00:44:12 +02:00
Kevin Atkinson
88cd5dcebf Add WithCodec and GetCodec methods to format interface. 2018-07-24 22:28:19 -04:00
Hector Sanjuan
9949dd29e5 gx publish 0.7.23
License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2018-07-24 11:09:24 +02:00
Steven Allen
75d3ffe549 Merge pull request #58 from ipfs/feat/decred-codec
Add Decred codecs
2018-07-23 18:57:13 +00:00
Hector Sanjuan
8028fee095 Add Decred codecs
0xe0 and 0xe1 have been assigned to Decred block and tx in the
multicodecs table. https://github.com/multiformats/multicodec/pull/78

License: MIT
Signed-off-by: Hector Sanjuan <hector@protocol.ai>
2018-07-23 19:28:53 +02:00
Steven Allen
bd441bb43e gx publish 0.7.22 2018-07-12 10:21:27 +02:00
Steven Allen
10a4d040b4 Merge pull request #55 from ipfs/bug/marshal
Use value receiver for MarshalJSON so we can marshal values
2018-07-12 08:19:51 +00:00
Fritz Schneider
d204c18f7a make test a little more clear 2018-07-11 11:24:59 -10:00
Fritz Schneider
b41162260a fix typo 2018-07-11 11:21:16 -10:00
Fritz Schneider
9cb0b7bcae use value receiver 2018-07-11 11:20:53 -10:00
Kevin Atkinson
6f951560f5 Update deprecated note to reflect code review. 2018-06-28 23:08:17 -04:00
Kevin Atkinson
5d8ad3eb9c Create new Format interface for creating CIDs. 2018-06-28 22:48:56 -04:00
Steven Allen
5b04f30433 gx publish 0.7.21 2018-06-08 20:25:20 -07:00
Steven Allen
078355866b gx publish 0.7.20 2018-01-19 20:51:37 -08:00
Steven Allen
1805dd530f gx publish 0.7.19 2017-12-05 00:06:03 +00:00
Steven Allen
2055d2e652 Merge pull request #36 from ipfs/feat/cid-prefix-constructors
Add CID Prefix constructors.
2017-09-07 11:35:56 -07:00
Steven Allen
63d4b33fcf Add CID Prefix constructors.
One can now generate a CID by calling:

```go
cid.NewPrefixV1(cid.DagCBOR, mh.SHA_256).Sum(data)
```

Lots of code was already doing this by manually constructing CID Prefixes but
that tends to be a bit verbose and error prone.
2017-08-30 12:42:46 -07:00
Steven Allen
19c1c0e32e gx publish 0.7.18 2017-08-28 20:25:09 -07:00
Steven Allen
088f141a17 Merge pull request #33 from ipfs/gx/update-multibase
gx: update multibase
2017-08-28 20:22:53 -07:00
Steven Allen
758714796c gx: update multibase
And fix test case.
2017-08-28 20:17:55 -07:00
Steven Allen
ed3563b69e Merge pull request #31 from ipfs/kevina/cid-fmt
Implement 'cid-fmt' utility.
2017-08-28 16:56:05 -07:00
Kevin Atkinson
db11d7248a Use an exit code of 1 on non-fatal errors and 2 on fatal errors. 2017-08-15 17:23:44 -04:00
Kevin Atkinson
68abb41a9b Add ability to change CID version. 2017-08-15 15:34:15 -04:00
Kevin Atkinson
7333c60a00 Implement 'cid-fmt' utility. 2017-08-15 14:26:24 -04:00
Kevin Atkinson
f62e35b87a Implement basic 'cid-fmt' utility.
Currently only implements printing the Cid prefix.
2017-08-15 03:46:48 -04:00
Jeromy Johnson
5652e6f751 Merge pull request #29 from Stebalien/gx/publish-0.7.17
gx publish 0.7.17
2017-07-10 12:45:42 -07:00
Steven Allen
fa428a337b gx publish 0.7.17 2017-07-07 19:34:27 -07:00
Jeromy Johnson
e0f0e24c2d Merge pull request #28 from ipfs/gx/publish-0.7.16
gx publish 0.7.16
2017-07-04 09:56:40 -07:00
Łukasz Magiera
1aa1093d28 gx publish 0.7.16 2017-07-04 18:45:18 +02:00
Jeromy Johnson
f4f9847100 Merge pull request #27 from ipfs/feat/codecov
Use Codecov instead of Coveralls
2017-07-03 16:33:39 -07:00
Jakub Sztandera
83b0d39470 Disable comments 2017-07-01 11:19:22 +02:00
Jakub Sztandera
b7772ebfe3 Use Codecov instead of Coveralls
As we do in most other repos.
2017-07-01 11:11:48 +02:00
Jeromy Johnson
e449699cc1 Merge pull request #26 from hermanjunge/feat/codecs-map
Added map of string to codecs
2017-06-30 11:52:50 -07:00
Herman Junge
7d345d4817 Switch from byte to uint64 2017-06-30 10:00:21 -04:00
Herman Junge
3c03b9e581 Improve tests 2017-06-30 03:01:26 -04:00
Herman Junge
f6990413f7 Added map of string to codecs 2017-06-29 21:00:23 -04:00
Jeromy Johnson
3f7f6c6a1d Merge pull request #21 from ipfs/gx/0.7.15
gx publish 0.7.15
2017-06-19 12:12:57 -07:00
Łukasz Magiera
05eac5f7ca gx publish 0.7.15 2017-06-19 18:31:26 +02:00
Jakub Sztandera
1f385a8224 Merge pull request #20 from ipfs/feat/strofbase
Implement StringOfBase function
2017-06-19 17:58:20 +02:00
Łukasz Magiera
b42583c8bf Add StringOfBase function 2017-06-19 17:25:51 +02:00
Jeromy
45ce89d41a gx publish 0.7.14 2017-06-13 11:33:50 -07:00
Jeromy Johnson
a20203df14 Merge pull request #18 from magik6k/patch-1
Add git-raw code
2017-06-09 11:55:35 -07:00
Łukasz Magiera
d4ebfff511 Add git-raw code 2017-06-07 20:08:56 +02:00
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
22 changed files with 1561 additions and 36 deletions

1
.gitignore vendored Normal file
View File

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

View File

@@ -1 +1 @@
0.7.3: QmXfiyr2RWEXpVDdaYnD2HNiBk6UBddsvEP4RPfXb6nGqY
0.7.25: QmYjnkEL7i731PirfVH1sis89evN7jt4otSHw5D2xXXwUV

24
.travis.yml Normal file
View File

@@ -0,0 +1,24 @@
sudo: false
language: go
go:
- 'tip'
install:
- go get github.com/whyrusleeping/gx
- go get github.com/whyrusleeping/gx-go
- gx install --global
script:
- gx test -v -race -coverprofile=coverage.txt -covermode=atomic .
after_success:
- bash <(curl -s https://codecov.io/bash)
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

74
builder.go Normal file
View File

@@ -0,0 +1,74 @@
package cid
import (
mh "github.com/multiformats/go-multihash"
)
type Builder interface {
Sum(data []byte) (*Cid, error)
GetCodec() uint64
WithCodec(uint64) Builder
}
type V0Builder struct{}
type V1Builder struct {
Codec uint64
MhType uint64
MhLength int // MhLength <= 0 means the default length
}
func (p Prefix) GetCodec() uint64 {
return p.Codec
}
func (p Prefix) WithCodec(c uint64) Builder {
if c == p.Codec {
return p
}
p.Codec = c
if c != DagProtobuf {
p.Version = 1
}
return p
}
func (p V0Builder) Sum(data []byte) (*Cid, error) {
hash, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
return nil, err
}
return NewCidV0(hash), nil
}
func (p V0Builder) GetCodec() uint64 {
return DagProtobuf
}
func (p V0Builder) WithCodec(c uint64) Builder {
if c == DagProtobuf {
return p
}
return V1Builder{Codec: c, MhType: mh.SHA2_256}
}
func (p V1Builder) Sum(data []byte) (*Cid, error) {
mhLen := p.MhLength
if mhLen <= 0 {
mhLen = -1
}
hash, err := mh.Sum(data, p.MhType, mhLen)
if err != nil {
return nil, err
}
return NewCidV1(p.Codec, hash), nil
}
func (p V1Builder) GetCodec() uint64 {
return p.Codec
}
func (p V1Builder) WithCodec(c uint64) Builder {
p.Codec = c
return p
}

92
builder_test.go Normal file
View File

@@ -0,0 +1,92 @@
package cid
import (
"testing"
mh "github.com/multiformats/go-multihash"
)
func TestV0Builder(t *testing.T) {
data := []byte("this is some test content")
// Construct c1
format := V0Builder{}
c1, err := format.Sum(data)
if err != nil {
t.Fatal(err)
}
// Construct c2
hash, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
t.Fatal(err)
}
c2 := NewCidV0(hash)
if !c1.Equals(c2) {
t.Fatal("cids mismatch")
}
if c1.Prefix() != c2.Prefix() {
t.Fatal("prefixes mismatch")
}
}
func TestV1Builder(t *testing.T) {
data := []byte("this is some test content")
// Construct c1
format := V1Builder{Codec: DagCBOR, MhType: mh.SHA2_256}
c1, err := format.Sum(data)
if err != nil {
t.Fatal(err)
}
// Construct c2
hash, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
t.Fatal(err)
}
c2 := NewCidV1(DagCBOR, hash)
if !c1.Equals(c2) {
t.Fatal("cids mismatch")
}
if c1.Prefix() != c2.Prefix() {
t.Fatal("prefixes mismatch")
}
}
func TestCodecChange(t *testing.T) {
t.Run("Prefix-CidV0", func(t *testing.T) {
p := Prefix{Version: 0, Codec: DagProtobuf, MhType: mh.SHA2_256, MhLength: mh.DefaultLengths[mh.SHA2_256]}
testCodecChange(t, p)
})
t.Run("Prefix-CidV1", func(t *testing.T) {
p := Prefix{Version: 1, Codec: DagProtobuf, MhType: mh.SHA2_256, MhLength: mh.DefaultLengths[mh.SHA2_256]}
testCodecChange(t, p)
})
t.Run("V0Builder", func(t *testing.T) {
testCodecChange(t, V0Builder{})
})
t.Run("V1Builder", func(t *testing.T) {
testCodecChange(t, V1Builder{Codec: DagProtobuf, MhType: mh.SHA2_256})
})
}
func testCodecChange(t *testing.T, b Builder) {
data := []byte("this is some test content")
if b.GetCodec() != DagProtobuf {
t.Fatal("original builder not using Protobuf codec")
}
b = b.WithCodec(Raw)
c, err := b.Sum(data)
if err != nil {
t.Fatal(err)
}
if c.Type() != Raw {
t.Fatal("new cid codec did not change to Raw")
}
}

128
cid-fmt/main.go Normal file
View File

@@ -0,0 +1,128 @@
package main
import (
"fmt"
"os"
"strings"
c "github.com/ipfs/go-cid"
mb "github.com/multiformats/go-multibase"
)
func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", c.FormatRef)
os.Exit(2)
}
func main() {
if len(os.Args) < 2 {
usage()
}
newBase := mb.Encoding(-1)
var verConv func(cid *c.Cid) (*c.Cid, error)
args := os.Args[1:]
outer:
for {
switch args[0] {
case "-b":
if len(args) < 2 {
usage()
}
encoder, err := mb.EncoderByName(args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
os.Exit(2)
}
newBase = encoder.Encoding()
args = args[2:]
case "-v":
if len(args) < 2 {
usage()
}
switch args[1] {
case "0":
verConv = toCidV0
case "1":
verConv = toCidV1
default:
fmt.Fprintf(os.Stderr, "Error: Invalid cid version: %s\n", args[1])
os.Exit(2)
}
args = args[2:]
default:
break outer
}
}
if len(args) < 2 {
usage()
}
fmtStr := args[0]
switch fmtStr {
case "prefix":
fmtStr = "%P"
default:
if strings.IndexByte(fmtStr, '%') == -1 {
fmt.Fprintf(os.Stderr, "Error: Invalid format string: %s\n", fmtStr)
os.Exit(2)
}
}
for _, cidStr := range args[1:] {
cid, err := c.Decode(cidStr)
if err != nil {
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on a bad cid
continue
}
base := newBase
if newBase == -1 {
base, _ = c.ExtractEncoding(cidStr)
}
if verConv != nil {
cid, err = verConv(cid)
if err != nil {
fmt.Fprintf(os.Stdout, "!ERROR!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on a bad conversion
continue
}
}
str, err := c.Format(fmtStr, base, cid)
switch err.(type) {
case c.FormatStringError:
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(2)
default:
fmt.Fprintf(os.Stdout, "!ERROR!\n")
errorMsg("%s: %v", cidStr, err)
// Don't abort on cid specific errors
continue
case nil:
// no error
}
fmt.Fprintf(os.Stdout, "%s\n", str)
}
os.Exit(exitCode)
}
var exitCode = 0
func errorMsg(fmtStr string, a ...interface{}) {
fmt.Fprintf(os.Stderr, "Error: ")
fmt.Fprintf(os.Stderr, fmtStr, a...)
fmt.Fprintf(os.Stderr, "\n")
exitCode = 1
}
func toCidV0(cid *c.Cid) (*c.Cid, error) {
if cid.Type() != c.DagProtobuf {
return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0")
}
return c.NewCidV0(cid.Hash()), nil
}
func toCidV1(cid *c.Cid) (*c.Cid, error) {
return c.NewCidV1(cid.Type(), cid.Hash()), nil
}

45
cid-fmt/main_test.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"fmt"
"testing"
c "github.com/ipfs/go-cid"
)
func TestCidConv(t *testing.T) {
cidv0 := "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"
cidv1 := "zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi"
cid, err := c.Decode(cidv0)
if err != nil {
t.Fatal(err)
}
cid, err = toCidV1(cid)
if err != nil {
t.Fatal(err)
}
if cid.String() != cidv1 {
t.Fatal("conversion failure")
}
cid, err = toCidV0(cid)
if err != nil {
t.Fatal(err)
}
cidStr := cid.String()
if cidStr != cidv0 {
t.Error(fmt.Sprintf("conversion failure, expected: %s; but got: %s", cidv0, cidStr))
}
}
func TestBadCidConv(t *testing.T) {
// this cid is a raw leaf and should not be able to convert to cidv0
cidv1 := "zb2rhhzX7uSKrtQ2ZZXFAabKiKFYZrJqKY2KE1cJ8yre2GSWZ"
cid, err := c.Decode(cidv1)
if err != nil {
t.Fatal(err)
}
cid, err = toCidV0(cid)
if err == nil {
t.Fatal("expected failure")
}
}

315
cid.go
View File

@@ -1,53 +1,199 @@
// 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
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"strings"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
// UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>"
const (
Protobuf = 0x70
CBOR = 0x71
Raw = 0x72
JSON = 0x73
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")
EthereumBlock = 0x90
EthereumTx = 0x91
BitcoinBlock = 0xb0
BitcoinTx = 0xb1
// 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")
// ErrInvalidEncoding means that selected encoding is not supported
// by this Cid version
ErrInvalidEncoding = errors.New("invalid base encoding")
)
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
GitRaw = 0x78
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
DecredBlock = 0xe0
DecredTx = 0xe1
)
// Codecs maps the name of a codec to its type
var Codecs = map[string]uint64{
"v0": DagProtobuf,
"raw": Raw,
"protobuf": DagProtobuf,
"cbor": DagCBOR,
"git-raw": GitRaw,
"eth-block": EthBlock,
"eth-block-list": EthBlockList,
"eth-tx-trie": EthTxTrie,
"eth-tx": EthTx,
"eth-tx-receipt-trie": EthTxReceiptTrie,
"eth-tx-receipt": EthTxReceipt,
"eth-state-trie": EthStateTrie,
"eth-account-snapshot": EthAccountSnapshot,
"eth-storage-trie": EthStorageTrie,
"bitcoin-block": BitcoinBlock,
"bitcoin-tx": BitcoinTx,
"zcash-block": ZcashBlock,
"zcash-tx": ZcashTx,
"decred-block": DecredBlock,
"decred-tx": DecredTx,
}
// CodecToStr maps the numeric codec to its name
var CodecToStr = map[uint64]string{
Raw: "raw",
DagProtobuf: "protobuf",
DagCBOR: "cbor",
GitRaw: "git-raw",
EthBlock: "eth-block",
EthBlockList: "eth-block-list",
EthTxTrie: "eth-tx-trie",
EthTx: "eth-tx",
EthTxReceiptTrie: "eth-tx-receipt-trie",
EthTxReceipt: "eth-tx-receipt",
EthStateTrie: "eth-state-trie",
EthAccountSnapshot: "eth-account-snapshot",
EthStorageTrie: "eth-storage-trie",
BitcoinBlock: "bitcoin-block",
BitcoinTx: "bitcoin-tx",
ZcashBlock: "zcash-block",
ZcashTx: "zcash-tx",
DecredBlock: "decred-block",
DecredTx: "decred-tx",
}
// 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{
version: 0,
codec: Protobuf,
hash: h,
codec: DagProtobuf,
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{
version: 1,
codec: c,
hash: h,
codec: codecType,
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 {
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) {
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) {
if len(v) < 2 {
return nil, fmt.Errorf("cid too short")
return nil, ErrCidTooShort
}
if len(v) == 46 && v[:2] == "Qm" {
@@ -67,6 +213,50 @@ func Decode(v string) (*Cid, error) {
return Cast(data)
}
// Extract the encoding from a Cid. If Decode on the same string did
// not return an error neither will this function.
func ExtractEncoding(v string) (mbase.Encoding, error) {
if len(v) < 2 {
return -1, ErrCidTooShort
}
if len(v) == 46 && v[:2] == "Qm" {
return mbase.Base58BTC, nil
}
encoding := mbase.Encoding(v[0])
// check encoding is valid
_, err := mbase.NewEncoder(encoding)
if err != nil {
return -1, err
}
return encoding, nil
}
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) {
if len(data) == 34 && data[0] == 18 && data[1] == 32 {
h, err := mh.Cast(data)
@@ -75,18 +265,25 @@ func Cast(data []byte) (*Cid, error) {
}
return &Cid{
codec: Protobuf,
codec: DagProtobuf,
version: 0,
hash: h,
}, nil
}
vers, n := binary.Uvarint(data)
if err := uvError(n); err != nil {
return nil, err
}
if vers != 0 && vers != 1 {
return nil, fmt.Errorf("invalid cid version number: %d", vers)
}
codec, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return nil, err
}
rest := data[n+cn:]
h, err := mh.Cast(rest)
@@ -101,10 +298,14 @@ func Cast(data []byte) (*Cid, error) {
}, nil
}
// Type returns the multicodec-packed content type of a Cid.
func (c *Cid) Type() uint64 {
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 {
switch c.version {
case 0:
@@ -121,10 +322,30 @@ func (c *Cid) String() string {
}
}
// String returns the string representation of a Cid
// encoded is selected base
func (c *Cid) StringOfBase(base mbase.Encoding) (string, error) {
switch c.version {
case 0:
if base != mbase.Base58BTC {
return "", ErrInvalidEncoding
}
return c.hash.B58String(), nil
case 1:
return mbase.Encode(base, c.bytesV1())
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
}
// 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:
@@ -141,25 +362,45 @@ func (c *Cid) bytesV0() []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[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)]
}
// 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)
}
// UnmarshalJSON parses the JSON representation of a Cid.
func (c *Cid) UnmarshalJSON(b []byte) error {
if len(b) < 2 {
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 {
return err
}
@@ -170,20 +411,30 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
return nil
}
func (c *Cid) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", c.String())), 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) {
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())
}
// 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{
@@ -194,14 +445,21 @@ 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.
// NOTE: The use -1 in MhLength to mean default length is deprecated,
// use the V0Builder or V1Builder structures instead
type Prefix struct {
Version uint64
Codec uint64
MhType int
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 {
@@ -218,8 +476,11 @@ 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 {
buf := make([]byte, 16)
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))
@@ -227,6 +488,8 @@ func (p Prefix) Bytes() []byte {
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)
@@ -252,7 +515,7 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
return Prefix{
Version: vers,
Codec: codec,
MhType: int(mhtype),
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,12 +2,41 @@ package cid
import (
"bytes"
"encoding/json"
"fmt"
"math/rand"
"strings"
"testing"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
// Copying the "silly test" idea from
// https://github.com/multiformats/go-multihash/blob/7aa9f26a231c6f34f4e9fad52bf580fd36627285/multihash_test.go#L13
// Makes it so changing the table accidentally has to happen twice.
var tCodecs = map[uint64]string{
Raw: "raw",
DagProtobuf: "protobuf",
DagCBOR: "cbor",
GitRaw: "git-raw",
EthBlock: "eth-block",
EthBlockList: "eth-block-list",
EthTxTrie: "eth-tx-trie",
EthTx: "eth-tx",
EthTxReceiptTrie: "eth-tx-receipt-trie",
EthTxReceipt: "eth-tx-receipt",
EthStateTrie: "eth-state-trie",
EthAccountSnapshot: "eth-account-snapshot",
EthStorageTrie: "eth-storage-trie",
BitcoinBlock: "bitcoin-block",
BitcoinTx: "bitcoin-tx",
ZcashBlock: "zcash-block",
ZcashTx: "zcash-tx",
DecredBlock: "decred-block",
DecredTx: "decred-tx",
}
func assertEqual(t *testing.T, a, b *Cid) {
if a.codec != b.codec {
t.Fatal("mismatch on type")
@@ -22,6 +51,26 @@ func assertEqual(t *testing.T, a, b *Cid) {
}
}
func TestTable(t *testing.T) {
if len(tCodecs) != len(Codecs)-1 {
t.Errorf("Item count mismatch in the Table of Codec. Should be %d, got %d", len(tCodecs)+1, len(Codecs))
}
for k, v := range tCodecs {
if Codecs[v] != k {
t.Errorf("Table mismatch: 0x%x %s", k, v)
}
}
}
// The table returns cid.DagProtobuf for "v0"
// so we test it apart
func TestTableForV0(t *testing.T) {
if Codecs["v0"] != DagProtobuf {
t.Error("Table mismatch: Codecs[\"v0\"] should resolve to DagProtobuf (0x70)")
}
}
func TestBasicMarshaling(t *testing.T) {
h, err := mh.Sum([]byte("TEST"), mh.SHA3, 4)
if err != nil {
@@ -52,6 +101,60 @@ func TestBasicMarshaling(t *testing.T) {
assertEqual(t, cid, out2)
}
func TestBasesMarshaling(t *testing.T) {
h, err := mh.Sum([]byte("TEST"), mh.SHA3, 4)
if err != nil {
t.Fatal(err)
}
cid := &Cid{
codec: 7,
version: 1,
hash: h,
}
data := cid.Bytes()
out, err := Cast(data)
if err != nil {
t.Fatal(err)
}
assertEqual(t, cid, out)
testBases := []mbase.Encoding{
mbase.Base16,
mbase.Base32,
mbase.Base32hex,
mbase.Base32pad,
mbase.Base32hexPad,
mbase.Base58BTC,
mbase.Base58Flickr,
mbase.Base64pad,
mbase.Base64urlPad,
mbase.Base64url,
mbase.Base64,
}
for _, b := range testBases {
s, err := cid.StringOfBase(b)
if err != nil {
t.Fatal(err)
}
if s[0] != byte(b) {
t.Fatal("Invalid multibase header")
}
out2, err := Decode(s)
if err != nil {
t.Fatal(err)
}
assertEqual(t, cid, out2)
}
}
func TestEmptyString(t *testing.T) {
_, err := Decode("")
if err == nil {
@@ -88,10 +191,68 @@ func TestV0ErrorCases(t *testing.T) {
}
}
func TestNewPrefixV1(t *testing.T) {
data := []byte("this is some test content")
// Construct c1
prefix := NewPrefixV1(DagCBOR, mh.SHA2_256)
c1, err := prefix.Sum(data)
if err != nil {
t.Fatal(err)
}
if c1.Prefix() != prefix {
t.Fatal("prefix not preserved")
}
// Construct c2
hash, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
t.Fatal(err)
}
c2 := NewCidV1(DagCBOR, hash)
if !c1.Equals(c2) {
t.Fatal("cids mismatch")
}
if c1.Prefix() != c2.Prefix() {
t.Fatal("prefixes mismatch")
}
}
func TestNewPrefixV0(t *testing.T) {
data := []byte("this is some test content")
// Construct c1
prefix := NewPrefixV0(mh.SHA2_256)
c1, err := prefix.Sum(data)
if err != nil {
t.Fatal(err)
}
if c1.Prefix() != prefix {
t.Fatal("prefix not preserved")
}
// Construct c2
hash, err := mh.Sum(data, mh.SHA2_256, -1)
if err != nil {
t.Fatal(err)
}
c2 := NewCidV0(hash)
if !c1.Equals(c2) {
t.Fatal("cids mismatch")
}
if c1.Prefix() != c2.Prefix() {
t.Fatal("prefixes mismatch")
}
}
func TestPrefixRoundtrip(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
c := NewCidV1(CBOR, hash)
c := NewCidV1(DagCBOR, hash)
pref := c.Prefix()
@@ -117,6 +278,15 @@ func TestPrefixRoundtrip(t *testing.T) {
}
}
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++ {
@@ -125,3 +295,136 @@ func TestFuzzCid(t *testing.T) {
_, _ = 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")
}
}
func TestJsonRoundTrip(t *testing.T) {
exp, err := Decode("zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG")
if err != nil {
t.Fatal(err)
}
// Verify it works for a *Cid.
enc, err := json.Marshal(exp)
if err != nil {
t.Fatal(err)
}
var actual Cid
err = json.Unmarshal(enc, &actual)
if !exp.Equals(&actual) {
t.Fatal("cids not equal for *Cid")
}
// Verify it works for a Cid.
enc, err = json.Marshal(*exp)
if err != nil {
t.Fatal(err)
}
var actual2 Cid
err = json.Unmarshal(enc, &actual2)
if !exp.Equals(&actual2) {
t.Fatal("cids not equal for Cid")
}
}
func BenchmarkStringV1(b *testing.B) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
cid := NewCidV1(Raw, hash)
b.ResetTimer()
count := 0
for i := 0; i < b.N; i++ {
count += len(cid.String())
}
if count != 49*b.N {
b.FailNow()
}
}

3
codecov.yml Normal file
View File

@@ -0,0 +1,3 @@
coverage:
range: "50...100"
comment: off

28
deprecated.go Normal file
View File

@@ -0,0 +1,28 @@
package cid
import (
mh "github.com/multiformats/go-multihash"
)
// NewPrefixV0 returns a CIDv0 prefix with the specified multihash type.
// DEPRECATED: Use V0Builder
func NewPrefixV0(mhType uint64) Prefix {
return Prefix{
MhType: mhType,
MhLength: mh.DefaultLengths[mhType],
Version: 0,
Codec: DagProtobuf,
}
}
// NewPrefixV1 returns a CIDv1 prefix with the specified codec and multihash
// type.
// DEPRECATED: Use V1Builder
func NewPrefixV1(codecType uint64, mhType uint64) Prefix {
return Prefix{
MhType: mhType,
MhLength: mh.DefaultLengths[mhType],
Version: 1,
Codec: codecType,
}
}

151
format.go Normal file
View File

@@ -0,0 +1,151 @@
package cid
import (
"bytes"
"fmt"
mb "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
// FormatRef is a string documenting the format string for the Format function
const FormatRef = `
%% literal %
%b multibase name
%B multibase code
%v version string
%V version number
%c codec name
%C codec code
%h multihash name
%H multihash code
%L hash digest length
%m multihash encoded in base %b (with multibase prefix)
%M multihash encoded in base %b without multibase prefix
%d hash digest encoded in base %b (with multibase prefix)
%D hash digest encoded in base %b without multibase prefix
%s cid string encoded in base %b (1)
%S cid string encoded in base %b without multibase prefix
%P cid prefix: %v-%c-%h-%L
(1) For CID version 0 the multibase must be base58btc and no prefix is
used. For Cid version 1 the multibase prefix is included.
`
// Format formats a cid according to the format specificer as
// documented in the FormatRef constant
func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) {
p := cid.Prefix()
var out bytes.Buffer
var err error
encoder, err := mb.NewEncoder(base)
if err != nil {
return "", err
}
for i := 0; i < len(fmtStr); i++ {
if fmtStr[i] != '%' {
out.WriteByte(fmtStr[i])
continue
}
i++
if i >= len(fmtStr) {
return "", FormatStringError{"premature end of format string", ""}
}
switch fmtStr[i] {
case '%':
out.WriteByte('%')
case 'b': // base name
out.WriteString(baseToString(base))
case 'B': // base code
out.WriteByte(byte(base))
case 'v': // version string
fmt.Fprintf(&out, "cidv%d", p.Version)
case 'V': // version num
fmt.Fprintf(&out, "%d", p.Version)
case 'c': // codec name
out.WriteString(codecToString(p.Codec))
case 'C': // codec code
fmt.Fprintf(&out, "%d", p.Codec)
case 'h': // hash fun name
out.WriteString(hashToString(p.MhType))
case 'H': // hash fun code
fmt.Fprintf(&out, "%d", p.MhType)
case 'L': // hash length
fmt.Fprintf(&out, "%d", p.MhLength)
case 'm', 'M': // multihash encoded in base %b
out.WriteString(encode(encoder, cid.Hash(), fmtStr[i] == 'M'))
case 'd', 'D': // hash digest encoded in base %b
dec, err := mh.Decode(cid.Hash())
if err != nil {
return "", err
}
out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D'))
case 's': // cid string encoded in base %b
str, err := cid.StringOfBase(base)
if err != nil {
return "", err
}
out.WriteString(str)
case 'S': // cid string without base prefix
out.WriteString(encode(encoder, cid.Bytes(), true))
case 'P': // prefix
fmt.Fprintf(&out, "cidv%d-%s-%s-%d",
p.Version,
codecToString(p.Codec),
hashToString(p.MhType),
p.MhLength,
)
default:
return "", FormatStringError{"unrecognized specifier in format string", fmtStr[i-1 : i+1]}
}
}
return out.String(), err
}
// FormatStringError is the error return from Format when the format
// string is ill formed
type FormatStringError struct {
Message string
Specifier string
}
func (e FormatStringError) Error() string {
if e.Specifier == "" {
return e.Message
} else {
return fmt.Sprintf("%s: %s", e.Message, e.Specifier)
}
}
func baseToString(base mb.Encoding) string {
baseStr, ok := mb.EncodingToStr[base]
if !ok {
return fmt.Sprintf("base?%c", base)
}
return baseStr
}
func codecToString(num uint64) string {
name, ok := CodecToStr[num]
if !ok {
return fmt.Sprintf("codec?%d", num)
}
return name
}
func hashToString(num uint64) string {
name, ok := mh.Codes[num]
if !ok {
return fmt.Sprintf("hash?%d", num)
}
return name
}
func encode(base mb.Encoder, data []byte, strip bool) string {
str := base.Encode(data)
if strip {
return str[1:]
}
return str
}

73
format_test.go Normal file
View File

@@ -0,0 +1,73 @@
package cid
import (
"fmt"
"testing"
mb "github.com/multiformats/go-multibase"
)
func TestFmt(t *testing.T) {
cids := map[string]string{
"cidv0": "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn",
"cidv1": "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD",
}
tests := []struct {
cidId string
newBase mb.Encoding
fmtStr string
result string
}{
{"cidv0", -1, "%P", "cidv0-protobuf-sha2-256-32"},
{"cidv0", -1, "%b-%v-%c-%h-%L", "base58btc-cidv0-protobuf-sha2-256-32"},
{"cidv0", -1, "%s", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
{"cidv0", -1, "%S", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
{"cidv0", -1, "ver#%V/#%C/#%H/%L", "ver#0/#112/#18/32"},
{"cidv0", -1, "%m", "zQmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
{"cidv0", -1, "%M", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"},
{"cidv0", -1, "%d", "z72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"},
{"cidv0", -1, "%D", "72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"},
{"cidv0", 'B', "%S", "CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"},
{"cidv0", 'B', "%B%S", "BCIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"},
{"cidv1", -1, "%P", "cidv1-protobuf-sha2-256-32"},
{"cidv1", -1, "%b-%v-%c-%h-%L", "base58btc-cidv1-protobuf-sha2-256-32"},
{"cidv1", -1, "%s", "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"},
{"cidv1", -1, "%S", "dj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"},
{"cidv1", -1, "ver#%V/#%C/#%H/%L", "ver#1/#112/#18/32"},
{"cidv1", -1, "%m", "zQmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"},
{"cidv1", -1, "%M", "QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"},
{"cidv1", -1, "%d", "zAux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"},
{"cidv1", -1, "%D", "Aux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"},
{"cidv1", 'B', "%s", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
{"cidv1", 'B', "%S", "AFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
{"cidv1", 'B', "%B%S", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"},
}
for _, tc := range tests {
name := fmt.Sprintf("%s/%s", tc.cidId, tc.fmtStr)
if tc.newBase != -1 {
name = fmt.Sprintf("%s/%c", name, tc.newBase)
}
cidStr := cids[tc.cidId]
t.Run(name, func(t *testing.T) {
testFmt(t, cidStr, tc.newBase, tc.fmtStr, tc.result)
})
}
}
func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, result string) {
cid, err := Decode(cidStr)
if err != nil {
t.Fatal(err)
}
base := newBase
if newBase == -1 {
base, _ = ExtractEncoding(cidStr)
}
str, err := Format(fmtStr, base, cid)
if err != nil {
t.Fatal(err)
}
if str != result {
t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str))
}
}

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": [
{
"author": "whyrusleeping",
"hash": "QmYDds3421prZgqKbLpEK7T9Aa2eVdQ7o3YarX1LVLdP2J",
"hash": "QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8",
"name": "go-multihash",
"version": "1.0.0"
"version": "1.0.8"
},
{
"author": "whyrusleeping",
"hash": "QmUq3H9YpcPphbRj6ct6rBgBE377A8wANP8zPMRqe1WYbf",
"hash": "QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR",
"name": "go-multibase",
"version": "0.2.1"
"version": "0.2.7"
}
],
"gxVersion": "0.8.0",
"language": "go",
"license": "",
"license": "MIT",
"name": "go-cid",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "0.7.3"
"version": "0.7.25"
}

51
set.go
View File

@@ -1,39 +1,53 @@
package cid
import (
"context"
)
// 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 {
set map[string]struct{}
}
// NewSet initializes and returns a new Set.
func NewSet() *Set {
return &Set{set: make(map[string]struct{})}
}
// Add puts a Cid in the Set.
func (s *Set) Add(c *Cid) {
s.set[string(c.Bytes())] = struct{}{}
}
// Has returns if the Set contains a given Cid.
func (s *Set) Has(c *Cid) bool {
_, ok := s.set[string(c.Bytes())]
return ok
}
// Remove deletes a Cid from the Set.
func (s *Set) Remove(c *Cid) {
delete(s.set, string(c.Bytes()))
}
// Len returns how many elements the Set has.
func (s *Set) Len() int {
return len(s.set)
}
// Keys returns the Cids in the set.
func (s *Set) Keys() []*Cid {
out := make([]*Cid, 0, len(s.set))
for k, _ := range s.set {
for k := range s.set {
c, _ := Cast([]byte(k))
out = append(out, c)
}
return out
}
// Visit adds a Cid to the set only if it is
// not in it already.
func (s *Set) Visit(c *Cid) bool {
if !s.Has(c) {
s.Add(c)
@@ -43,8 +57,10 @@ func (s *Set) Visit(c *Cid) bool {
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 {
for cs := range s.set {
c, _ := Cast([]byte(cs))
err := f(c)
if err != nil {
@@ -53,3 +69,34 @@ func (s *Set) ForEach(f func(c *Cid) error) error {
}
return nil
}
// StreamingSet is an extension of Set which allows to implement back-pressure
// for the Visit function
type StreamingSet struct {
Set *Set
New chan *Cid
}
// NewStreamingSet initializes and returns new Set.
func NewStreamingSet() *StreamingSet {
return &StreamingSet{
Set: NewSet(),
New: make(chan *Cid),
}
}
// Visitor creates new visitor which adds a Cids to the set and emits them to
// the set.New channel
func (s *StreamingSet) Visitor(ctx context.Context) func(c *Cid) bool {
return func(c *Cid) bool {
if s.Set.Visit(c) {
select {
case s.New <- c:
case <-ctx.Done():
}
return true
}
return false
}
}

92
set_test.go Normal file
View File

@@ -0,0 +1,92 @@
package cid
import (
"crypto/rand"
"errors"
"testing"
mh "github.com/multiformats/go-multihash"
)
func makeRandomCid(t *testing.T) *Cid {
p := make([]byte, 256)
_, err := rand.Read(p)
if err != nil {
t.Fatal(err)
}
h, err := mh.Sum(p, mh.SHA3, 4)
if err != nil {
t.Fatal(err)
}
cid := &Cid{
codec: 7,
version: 1,
hash: h,
}
return cid
}
func TestSet(t *testing.T) {
cid := makeRandomCid(t)
cid2 := makeRandomCid(t)
s := NewSet()
s.Add(cid)
if !s.Has(cid) {
t.Error("should have the CID")
}
if s.Len() != 1 {
t.Error("should report 1 element")
}
keys := s.Keys()
if len(keys) != 1 || !keys[0].Equals(cid) {
t.Error("key should correspond to Cid")
}
if s.Visit(cid) {
t.Error("visit should return false")
}
foreach := []*Cid{}
foreachF := func(c *Cid) error {
foreach = append(foreach, c)
return nil
}
if err := s.ForEach(foreachF); err != nil {
t.Error(err)
}
if len(foreach) != 1 {
t.Error("ForEach should have visited 1 element")
}
foreachErr := func(c *Cid) error {
return errors.New("test")
}
if err := s.ForEach(foreachErr); err == nil {
t.Error("Should have returned an error")
}
if !s.Visit(cid2) {
t.Error("should have visited a new Cid")
}
if s.Len() != 2 {
t.Error("len should be 2 now")
}
s.Remove(cid2)
if s.Len() != 1 {
t.Error("len should be 1 now")
}
}