Compare commits

..

1 Commits

Author SHA1 Message Date
hannahhoward
8f7b37bab5 feat(cid): add FromDigest for previously computed hash 2021-02-25 17:38:21 -08:00
26 changed files with 330 additions and 755 deletions

7
.github/Repo.toml vendored
View File

@@ -1,7 +0,0 @@
[scopes]
ci = [".github/workflows"]
config = [".github", "codecov.yml", "go.mod", "go.sum", "Makefile", "version.json"]
docs = ["README.md", "LICENSE"]
tests = ["*_test.go", "fuzz-data"]
research = ["_rsrch"]
core = ["cid.go", "builder.go", "set.go", "varint.go", "cid_fuzz.go", "deprecated.go"]

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
time: "11:00"
open-pull-requests-limit: 10

View File

@@ -1,14 +0,0 @@
name: Close Generated PRs
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1

View File

@@ -1,18 +0,0 @@
name: Go Checks
on:
pull_request:
push:
branches: ["master"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:
go-check:
uses: ipdxco/unified-github-workflows/.github/workflows/go-check.yml@v1.0

View File

@@ -1,20 +0,0 @@
name: Go Test
on:
pull_request:
push:
branches: ["master"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:
go-test:
uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,19 +0,0 @@
name: Release Checker
on:
pull_request_target:
paths: [ 'version.json' ]
types: [ opened, synchronize, reopened, labeled, unlabeled ]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release-check:
uses: ipdxco/unified-github-workflows/.github/workflows/release-check.yml@v1.0

View File

@@ -1,17 +0,0 @@
name: Releaser
on:
push:
paths: [ 'version.json' ]
workflow_dispatch:
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.sha }}
cancel-in-progress: true
jobs:
releaser:
uses: ipdxco/unified-github-workflows/.github/workflows/releaser.yml@v1.0

View File

@@ -1,14 +0,0 @@
name: Close Stale Issues
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1

View File

@@ -1,18 +0,0 @@
name: Tag Push Checker
on:
push:
tags:
- v*
permissions:
contents: read
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
releaser:
uses: ipdxco/unified-github-workflows/.github/workflows/tagpush.yml@v1.0

30
.travis.yml Normal file
View File

@@ -0,0 +1,30 @@
os:
- linux
language: go
go:
- 1.11.x
env:
global:
- GOTFLAGS="-race"
matrix:
- BUILD_DEPTYPE=gomod
# disable travis install
install:
- true
script:
- bash <(curl -s https://raw.githubusercontent.com/ipfs/ci-helpers/master/travis-ci/run-standard-tests.sh)
cache:
directories:
- $GOPATH/pkg/mod
- $HOME/.cache/go-build
notifications:
email: false

View File

@@ -5,7 +5,7 @@ go-cid
[![](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) [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![GoDoc](https://godoc.org/code.sonr.org/go/cid?status.svg)](https://godoc.org/code.sonr.org/go/cid) [![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)
@@ -31,7 +31,7 @@ It is used in `go-ipfs` and related packages to refer to a typed hunk of data.
`go-cid` is a standard Go module which can be installed with: `go-cid` is a standard Go module which can be installed with:
```sh ```sh
go get code.sonr.org/go/cid go get github.com/ipfs/go-cid
``` ```
## Usage ## Usage
@@ -59,17 +59,10 @@ fmt.Println("Got CID: ", c)
#### Creating a CID from scratch #### Creating a CID from scratch
```go ```go
import (
cid "code.sonr.org/go/cid"
mc "github.com/multiformats/go-multicodec"
mh "github.com/multiformats/go-multihash"
)
// Create a cid manually by specifying the 'prefix' parameters // Create a cid manually by specifying the 'prefix' parameters
pref := cid.Prefix{ pref := cid.Prefix{
Version: 1, Version: 1,
Codec: uint64(mc.Raw), Codec: cid.Raw,
MhType: mh.SHA2_256, MhType: mh.SHA2_256,
MhLength: -1, // default length MhLength: -1, // default length
} }

View File

@@ -39,8 +39,7 @@ type Cid interface {
// and the Multihash length. It does not contains // and the Multihash length. It does not contains
// any actual content information. // any actual content information.
// NOTE: The use -1 in MhLength to mean default length is deprecated, // NOTE: The use -1 in MhLength to mean default length is deprecated,
// // use the V0Builder or V1Builder structures instead
// use the V0Builder or V1Builder structures instead
type Prefix struct { type Prefix struct {
Version uint64 Version uint64
Codec uint64 Codec uint64

View File

@@ -12,14 +12,14 @@ import (
// //
// Sample results on linux amd64 go1.11beta: // Sample results on linux amd64 go1.11beta:
// //
// BenchmarkCidMap_CidStr-8 100000 16317 ns/op // BenchmarkCidMap_CidStr-8 100000 16317 ns/op
// BenchmarkCidMap_CidIface-8 100000 20516 ns/op // BenchmarkCidMap_CidIface-8 100000 20516 ns/op
// //
// With benchmem on: // With benchmem on:
// //
// BenchmarkCidMap_CidStr-8 100000 15579 ns/op 11223 B/op 207 allocs/op // BenchmarkCidMap_CidStr-8 100000 15579 ns/op 11223 B/op 207 allocs/op
// BenchmarkCidMap_CidIface-8 100000 19500 ns/op 12824 B/op 307 allocs/op // BenchmarkCidMap_CidIface-8 100000 19500 ns/op 12824 B/op 307 allocs/op
// BenchmarkCidMap_StrPlusHax-8 200000 10451 ns/op 7589 B/op 202 allocs/op // BenchmarkCidMap_StrPlusHax-8 200000 10451 ns/op 7589 B/op 202 allocs/op
// //
// We can see here that the impact of interface boxing is significant: // We can see here that the impact of interface boxing is significant:
// it increases the time taken to do the inserts to 133%, largely because // it increases the time taken to do the inserts to 133%, largely because
@@ -36,6 +36,7 @@ import (
// re-arranges itself, it involves more or less an O(n) copy of the content // re-arranges itself, it involves more or less an O(n) copy of the content
// in addition to the alloc itself). This isn't topical to the question of // in addition to the alloc itself). This isn't topical to the question of
// whether or not interfaces are a good idea; just for contextualizing. // whether or not interfaces are a good idea; just for contextualizing.
//
func BenchmarkCidMap_CidStr(b *testing.B) { func BenchmarkCidMap_CidStr(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
mp := map[CidStr]int{} mp := map[CidStr]int{}

View File

@@ -114,7 +114,7 @@ func NewCidStr(version uint64, codecType uint64, mhash mh.Multihash) CidStr {
// //
// For CidV1, the data buffer is in the form: // For CidV1, the data buffer is in the form:
// //
// <version><codec-type><multihash> // <version><codec-type><multihash>
// //
// CidV0 are also supported. In particular, data buffers starting // CidV0 are also supported. In particular, data buffers starting
// with length 34 bytes, which starts with bytes [18,32...] are considered // with length 34 bytes, which starts with bytes [18,32...] are considered

View File

@@ -110,7 +110,7 @@ func (c CidStruct) Prefix() Prefix {
// //
// For CidV1, the data buffer is in the form: // For CidV1, the data buffer is in the form:
// //
// <version><codec-type><multihash> // <version><codec-type><multihash>
// //
// CidV0 are also supported. In particular, data buffers starting // CidV0 are also supported. In particular, data buffers starting
// with length 34 bytes, which starts with bytes [18,32...] are considered // with length 34 bytes, which starts with bytes [18,32...] are considered

View File

@@ -1,61 +0,0 @@
package cid_test
import (
"math/rand"
"testing"
"code.sonr.org/go/cid"
"github.com/multiformats/go-multihash"
)
// BenchmarkIdentityCheck benchmarks two ways of checking whether a CIDv1 has multihash.IDENTITY
// code:
// 1. Cid.Prefix(), and
// 2. decoding the Cid.Hash().
//
// This benchmark illustrates that using Cid.Prefix is more efficient than multihash.Decode.
// Users wishing to perform such a check should use Cid.Prefix.
//
// Consider that `Cid.Prefix` is already efficient enough and introducing a dedicated API for
// performing this check will likely result in small gains.
func BenchmarkIdentityCheck(b *testing.B) {
rng := rand.New(rand.NewSource(1413))
data := make([]byte, rng.Intn(100)+1024)
if _, err := rng.Read(data); err != nil {
b.Fatal(err)
}
mh, err := multihash.Sum(data, multihash.IDENTITY, -1)
if err != nil {
b.Fatal(err)
}
cv1 := cid.NewCidV1(cid.Raw, mh)
b.SetBytes(int64(cv1.ByteLen()))
b.ReportAllocs()
b.ResetTimer()
b.Run("Prefix", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if cv1.Prefix().MhType != multihash.IDENTITY {
b.Fatal("expected IDENTITY CID")
}
}
})
})
b.Run("MultihashDecode", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
dmh, err := multihash.Decode(cv1.Hash())
if err != nil {
b.Fatal(err)
}
if dmh.Code != multihash.IDENTITY {
b.Fatal("expected IDENTITY CID")
}
}
})
})
}

372
cid.go
View File

@@ -10,7 +10,7 @@
// //
// A CIDv1 has four parts: // A CIDv1 has four parts:
// //
// <cidv1> ::= <multibase-prefix><cid-version><multicodec-packed-content-type><multihash-content-address> // <cidv1> ::= <multibase-prefix><cid-version><multicodec-packed-content-type><multihash-content-address>
// //
// As shown above, the CID implementation relies heavily on Multiformats, // As shown above, the CID implementation relies heavily on Multiformats,
// particularly Multibase // particularly Multibase
@@ -22,7 +22,6 @@ package cid
import ( import (
"bytes" "bytes"
"encoding" "encoding"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -37,53 +36,29 @@ import (
// UnsupportedVersionString just holds an error message // UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>" const UnsupportedVersionString = "<unsupported cid version>"
// ErrInvalidCid is an error that indicates that a CID is invalid.
type ErrInvalidCid struct {
Err error
}
func (e ErrInvalidCid) Error() string {
return fmt.Sprintf("invalid cid: %s", e.Err)
}
func (e ErrInvalidCid) Unwrap() error {
return e.Err
}
func (e ErrInvalidCid) Is(err error) bool {
switch err.(type) {
case ErrInvalidCid, *ErrInvalidCid:
return true
default:
return false
}
}
var ( var (
// ErrCidTooShort means that the cid passed to decode was not long // ErrCidTooShort means that the cid passed to decode was not long
// enough to be a valid Cid // enough to be a valid Cid
ErrCidTooShort = ErrInvalidCid{errors.New("cid too short")} ErrCidTooShort = errors.New("cid too short")
// ErrInvalidEncoding means that selected encoding is not supported // ErrInvalidEncoding means that selected encoding is not supported
// by this Cid version // by this Cid version
ErrInvalidEncoding = errors.New("invalid base encoding") ErrInvalidEncoding = errors.New("invalid base encoding")
) )
// Consts below are DEPRECATED and left only for legacy reasons: // These are multicodec-packed content types. The should match
// <https://code.sonr.org/go/cid/pull/137> // the codes described in the authoritative document:
// Modern code should use consts from go-multicodec instead: // https://github.com/multiformats/multicodec/blob/master/table.csv
// <https://github.com/multiformats/go-multicodec>
const ( const (
// common ones Raw = 0x55
Raw = 0x55
DagProtobuf = 0x70 // https://ipld.io/docs/codecs/known/dag-pb/
DagCBOR = 0x71 // https://ipld.io/docs/codecs/known/dag-cbor/
DagJSON = 0x0129 // https://ipld.io/docs/codecs/known/dag-json/
Libp2pKey = 0x72 // https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids
// other DagProtobuf = 0x70
GitRaw = 0x78 DagCBOR = 0x71
DagJOSE = 0x85 // https://ipld.io/specs/codecs/dag-jose/spec/ Libp2pKey = 0x72
GitRaw = 0x78
DagJOSE = 0x85
EthBlock = 0x90 EthBlock = 0x90
EthBlockList = 0x91 EthBlockList = 0x91
EthTxTrie = 0x92 EthTxTrie = 0x92
@@ -105,6 +80,64 @@ const (
FilCommitmentSealed = 0xf102 FilCommitmentSealed = 0xf102
) )
// Codecs maps the name of a codec to its type
var Codecs = map[string]uint64{
"v0": DagProtobuf,
"raw": Raw,
"protobuf": DagProtobuf,
"cbor": DagCBOR,
"libp2p-key": Libp2pKey,
"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,
"dash-block": DashBlock,
"dash-tx": DashTx,
"fil-commitment-unsealed": FilCommitmentUnsealed,
"fil-commitment-sealed": FilCommitmentSealed,
"dag-jose": DagJOSE,
}
// 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",
DashBlock: "dash-block",
DashTx: "dash-tx",
FilCommitmentUnsealed: "fil-commitment-unsealed",
FilCommitmentSealed: "fil-commitment-sealed",
DagJOSE: "dag-jose",
}
// tryNewCidV0 tries to convert a multihash into a CIDv0 CID and returns an // tryNewCidV0 tries to convert a multihash into a CIDv0 CID and returns an
// error on failure. // error on failure.
func tryNewCidV0(mhash mh.Multihash) (Cid, error) { func tryNewCidV0(mhash mh.Multihash) (Cid, error) {
@@ -112,10 +145,10 @@ func tryNewCidV0(mhash mh.Multihash) (Cid, error) {
// incorrectly detect it as CidV1 in the Version() method // incorrectly detect it as CidV1 in the Version() method
dec, err := mh.Decode(mhash) dec, err := mh.Decode(mhash)
if err != nil { if err != nil {
return Undef, ErrInvalidCid{err} return Undef, err
} }
if dec.Code != mh.SHA2_256 || dec.Length != 32 { if dec.Code != mh.SHA2_256 || dec.Length != 32 {
return Undef, ErrInvalidCid{fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length)} return Undef, fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length)
} }
return Cid{string(mhash)}, nil return Cid{string(mhash)}, nil
} }
@@ -140,32 +173,22 @@ func NewCidV0(mhash mh.Multihash) Cid {
// Panics if the multihash is invalid. // Panics if the multihash is invalid.
func NewCidV1(codecType uint64, mhash mh.Multihash) Cid { func NewCidV1(codecType uint64, mhash mh.Multihash) Cid {
hashlen := len(mhash) hashlen := len(mhash)
// two 8 bytes (max) numbers plus hash
// Two 8 bytes (max) numbers plus hash. buf := make([]byte, 1+varint.UvarintSize(codecType)+hashlen)
// We use strings.Builder to only allocate once. n := varint.PutUvarint(buf, 1)
var b strings.Builder n += varint.PutUvarint(buf[n:], codecType)
b.Grow(1 + varint.UvarintSize(codecType) + hashlen) cn := copy(buf[n:], mhash)
b.WriteByte(1)
var buf [binary.MaxVarintLen64]byte
n := varint.PutUvarint(buf[:], codecType)
b.Write(buf[:n])
cn, _ := b.Write(mhash)
if cn != hashlen { if cn != hashlen {
panic("copy hash length is inconsistent") panic("copy hash length is inconsistent")
} }
return Cid{b.String()} return Cid{string(buf[:n+hashlen])}
} }
var ( var _ encoding.BinaryMarshaler = Cid{}
_ encoding.BinaryMarshaler = Cid{} var _ encoding.BinaryUnmarshaler = (*Cid)(nil)
_ encoding.BinaryUnmarshaler = (*Cid)(nil) var _ encoding.TextMarshaler = Cid{}
_ encoding.TextMarshaler = Cid{} var _ encoding.TextUnmarshaler = (*Cid)(nil)
_ encoding.TextUnmarshaler = (*Cid)(nil)
)
// Cid represents a self-describing content addressed // Cid represents a self-describing content addressed
// identifier. It is formed by a Version, a Codec (which indicates // identifier. It is formed by a Version, a Codec (which indicates
@@ -199,23 +222,14 @@ func Parse(v interface{}) (Cid, error) {
case Cid: case Cid:
return v2, nil return v2, nil
default: default:
return Undef, ErrInvalidCid{fmt.Errorf("can't parse %+v as Cid", v2)} return Undef, fmt.Errorf("can't parse %+v as Cid", v2)
} }
} }
// MustParse calls Parse but will panic on error.
func MustParse(v interface{}) Cid {
c, err := Parse(v)
if err != nil {
panic(err)
}
return c
}
// Decode parses a Cid-encoded string and returns a Cid object. // Decode parses a Cid-encoded string and returns a Cid object.
// For CidV1, a Cid-encoded string is primarily a multibase string: // For CidV1, a Cid-encoded string is primarily a multibase string:
// //
// <multibase-type-code><base-encoded-string> // <multibase-type-code><base-encoded-string>
// //
// The base-encoded string represents a: // The base-encoded string represents a:
// //
@@ -232,7 +246,7 @@ func Decode(v string) (Cid, error) {
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 {
return Undef, ErrInvalidCid{err} return Undef, err
} }
return tryNewCidV0(hash) return tryNewCidV0(hash)
@@ -240,7 +254,7 @@ func Decode(v string) (Cid, error) {
_, data, err := mbase.Decode(v) _, data, err := mbase.Decode(v)
if err != nil { if err != nil {
return Undef, ErrInvalidCid{err} return Undef, err
} }
return Cast(data) return Cast(data)
@@ -262,7 +276,7 @@ func ExtractEncoding(v string) (mbase.Encoding, error) {
// check encoding is valid // check encoding is valid
_, err := mbase.NewEncoder(encoding) _, err := mbase.NewEncoder(encoding)
if err != nil { if err != nil {
return -1, ErrInvalidCid{err} return -1, err
} }
return encoding, nil return encoding, nil
@@ -271,7 +285,7 @@ func ExtractEncoding(v string) (mbase.Encoding, error) {
// Cast takes a Cid data slice, parses it and returns a Cid. // Cast takes a Cid data slice, parses it and returns a Cid.
// For CidV1, the data buffer is in the form: // For CidV1, the data buffer is in the form:
// //
// <version><codec-type><multihash> // <version><codec-type><multihash>
// //
// CidV0 are also supported. In particular, data buffers starting // CidV0 are also supported. In particular, data buffers starting
// with length 34 bytes, which starts with bytes [18,32...] are considered // with length 34 bytes, which starts with bytes [18,32...] are considered
@@ -282,11 +296,11 @@ func ExtractEncoding(v string) (mbase.Encoding, error) {
func Cast(data []byte) (Cid, error) { func Cast(data []byte) (Cid, error) {
nr, c, err := CidFromBytes(data) nr, c, err := CidFromBytes(data)
if err != nil { if err != nil {
return Undef, ErrInvalidCid{err} return Undef, err
} }
if nr != len(data) { if nr != len(data) {
return Undef, ErrInvalidCid{fmt.Errorf("trailing bytes in data buffer passed to cid Cast")} return Undef, fmt.Errorf("trailing bytes in data buffer passed to cid Cast")
} }
return c, nil return c, nil
@@ -400,13 +414,7 @@ func (c Cid) Hash() mh.Multihash {
// Bytes returns the byte representation of a Cid. // Bytes returns the byte representation of a Cid.
// The output of bytes can be parsed back into a Cid // The output of bytes can be parsed back into a Cid
// with Cast(). // with Cast().
//
// If c.Defined() == false, it return a nil slice and may not
// be parsable with Cast().
func (c Cid) Bytes() []byte { func (c Cid) Bytes() []byte {
if !c.Defined() {
return nil
}
return []byte(c.str) return []byte(c.str)
} }
@@ -456,7 +464,7 @@ func (c Cid) Equals(o Cid) bool {
// UnmarshalJSON parses the JSON representation of a Cid. // 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 ErrInvalidCid{fmt.Errorf("invalid cid json blob")} return fmt.Errorf("invalid cid json blob")
} }
obj := struct { obj := struct {
CidTarget string `json:"/"` CidTarget string `json:"/"`
@@ -464,7 +472,7 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
objptr := &obj objptr := &obj
err := json.Unmarshal(b, &objptr) err := json.Unmarshal(b, &objptr)
if err != nil { if err != nil {
return ErrInvalidCid{err} return err
} }
if objptr == nil { if objptr == nil {
*c = Cid{} *c = Cid{}
@@ -472,12 +480,12 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
} }
if obj.CidTarget == "" { if obj.CidTarget == "" {
return ErrInvalidCid{fmt.Errorf("cid was incorrectly formatted")} return fmt.Errorf("cid was incorrectly formatted")
} }
out, err := Decode(obj.CidTarget) out, err := Decode(obj.CidTarget)
if err != nil { if err != nil {
return ErrInvalidCid{err} return err
} }
*c = out *c = out
@@ -487,7 +495,7 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
// MarshalJSON procudes a JSON representation of a Cid, which looks as follows: // MarshalJSON procudes a JSON representation of a Cid, which looks as follows:
// //
// { "/": "<cid-string>" } // { "/": "<cid-string>" }
// //
// Note that this formatting comes from the IPLD specification // Note that this formatting comes from the IPLD specification
// (https://github.com/ipld/specs/tree/master/ipld) // (https://github.com/ipld/specs/tree/master/ipld)
@@ -544,8 +552,7 @@ func (c Cid) Prefix() Prefix {
// and the Multihash length. It does not contains // and the Multihash length. It does not contains
// any actual content information. // any actual content information.
// NOTE: The use -1 in MhLength to mean default length is deprecated, // NOTE: The use -1 in MhLength to mean default length is deprecated,
// // use the V0Builder or V1Builder structures instead
// use the V0Builder or V1Builder structures instead
type Prefix struct { type Prefix struct {
Version uint64 Version uint64
Codec uint64 Codec uint64
@@ -557,19 +564,26 @@ type Prefix struct {
// and return a newly constructed Cid with the resulting multihash. // 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) {
length := p.MhLength length := p.MhLength
if p.MhType == mh.IDENTITY { if p.MhType == mh.ID {
length = -1 length = -1
} }
if p.Version == 0 && (p.MhType != mh.SHA2_256 || if p.Version == 0 && (p.MhType != mh.SHA2_256 ||
(p.MhLength != 32 && p.MhLength != -1)) { (p.MhLength != 32 && p.MhLength != -1)) {
return Undef, ErrInvalidCid{fmt.Errorf("invalid v0 prefix")} return Undef, fmt.Errorf("invalid v0 prefix")
} }
digest, err := mh.Digest(data, p.MhType, length)
hash, err := mh.Sum(data, p.MhType, length)
if err != nil { if err != nil {
return Undef, ErrInvalidCid{err} return Undef, err
}
return p.FromDigest(digest)
}
func (p Prefix) FromDigest(digest []byte) (Cid, error) {
hash, err := mh.Encode(digest, p.MhType)
if err != nil {
return Undef, err
} }
switch p.Version { switch p.Version {
@@ -578,13 +592,13 @@ func (p Prefix) Sum(data []byte) (Cid, error) {
case 1: case 1:
return NewCidV1(p.Codec, hash), nil return NewCidV1(p.Codec, hash), nil
default: default:
return Undef, ErrInvalidCid{fmt.Errorf("invalid cid version")} return Undef, fmt.Errorf("invalid cid version")
} }
} }
// Bytes returns a byte representation of a Prefix. It looks like: // Bytes returns a byte representation of a Prefix. It looks like:
// //
// <version><codec><mh-type><mh-length> // <version><codec><mh-type><mh-length>
func (p Prefix) Bytes() []byte { func (p Prefix) Bytes() []byte {
size := varint.UvarintSize(p.Version) size := varint.UvarintSize(p.Version)
size += varint.UvarintSize(p.Codec) size += varint.UvarintSize(p.Codec)
@@ -608,22 +622,22 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
r := bytes.NewReader(buf) r := bytes.NewReader(buf)
vers, err := varint.ReadUvarint(r) vers, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, ErrInvalidCid{err} return Prefix{}, err
} }
codec, err := varint.ReadUvarint(r) codec, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, ErrInvalidCid{err} return Prefix{}, err
} }
mhtype, err := varint.ReadUvarint(r) mhtype, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, ErrInvalidCid{err} return Prefix{}, err
} }
mhlen, err := varint.ReadUvarint(r) mhlen, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, ErrInvalidCid{err} return Prefix{}, err
} }
return Prefix{ return Prefix{
@@ -637,12 +651,12 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
func CidFromBytes(data []byte) (int, Cid, error) { func CidFromBytes(data []byte) (int, Cid, error) {
if len(data) > 2 && data[0] == mh.SHA2_256 && data[1] == 32 { if len(data) > 2 && data[0] == mh.SHA2_256 && data[1] == 32 {
if len(data) < 34 { if len(data) < 34 {
return 0, Undef, ErrInvalidCid{fmt.Errorf("not enough bytes for cid v0")} return 0, Undef, fmt.Errorf("not enough bytes for cid v0")
} }
h, err := mh.Cast(data[:34]) h, err := mh.Cast(data[:34])
if err != nil { if err != nil {
return 0, Undef, ErrInvalidCid{err} return 0, Undef, err
} }
return 34, Cid{string(h)}, nil return 34, Cid{string(h)}, nil
@@ -650,168 +664,24 @@ func CidFromBytes(data []byte) (int, Cid, error) {
vers, n, err := varint.FromUvarint(data) vers, n, err := varint.FromUvarint(data)
if err != nil { if err != nil {
return 0, Undef, ErrInvalidCid{err} return 0, Undef, err
} }
if vers != 1 { if vers != 1 {
return 0, Undef, ErrInvalidCid{fmt.Errorf("expected 1 as the cid version number, got: %d", vers)} return 0, Undef, fmt.Errorf("expected 1 as the cid version number, got: %d", vers)
} }
_, cn, err := varint.FromUvarint(data[n:]) _, cn, err := varint.FromUvarint(data[n:])
if err != nil { if err != nil {
return 0, Undef, ErrInvalidCid{err} return 0, Undef, err
} }
mhnr, _, err := mh.MHFromBytes(data[n+cn:]) mhnr, _, err := mh.MHFromBytes(data[n+cn:])
if err != nil { if err != nil {
return 0, Undef, ErrInvalidCid{err} return 0, Undef, err
} }
l := n + cn + mhnr l := n + cn + mhnr
return l, Cid{string(data[0:l])}, nil return l, Cid{string(data[0:l])}, nil
} }
func toBufByteReader(r io.Reader, dst []byte) *bufByteReader {
// If the reader already implements ByteReader, use it directly.
// Otherwise, use a fallback that does 1-byte Reads.
if br, ok := r.(io.ByteReader); ok {
return &bufByteReader{direct: br, dst: dst}
}
return &bufByteReader{fallback: r, dst: dst}
}
type bufByteReader struct {
direct io.ByteReader
fallback io.Reader
dst []byte
}
func (r *bufByteReader) ReadByte() (byte, error) {
// The underlying reader has ReadByte; use it.
if br := r.direct; br != nil {
b, err := br.ReadByte()
if err != nil {
return 0, err
}
r.dst = append(r.dst, b)
return b, nil
}
// Fall back to a one-byte Read.
// TODO: consider reading straight into dst,
// once we have benchmarks and if they prove that to be faster.
var p [1]byte
if _, err := io.ReadFull(r.fallback, p[:]); err != nil {
return 0, err
}
r.dst = append(r.dst, p[0])
return p[0], nil
}
// CidFromReader reads a precise number of bytes for a CID from a given reader.
// It returns the number of bytes read, the CID, and any error encountered.
// The number of bytes read is accurate even if a non-nil error is returned.
//
// It's recommended to supply a reader that buffers and implements io.ByteReader,
// as CidFromReader has to do many single-byte reads to decode varints.
// If the argument only implements io.Reader, single-byte Read calls are used instead.
//
// If the Reader is found to yield zero bytes, an io.EOF error is returned directly, in all
// other error cases, an ErrInvalidCid, wrapping the original error, is returned.
func CidFromReader(r io.Reader) (int, Cid, error) {
// 64 bytes is enough for any CIDv0,
// and it's enough for most CIDv1s in practice.
// If the digest is too long, we'll allocate more.
br := toBufByteReader(r, make([]byte, 0, 64))
// We read the first varint, to tell if this is a CIDv0 or a CIDv1.
// The varint package wants a io.ByteReader, so we must wrap our io.Reader.
vers, err := varint.ReadUvarint(br)
if err != nil {
if err == io.EOF {
// First-byte read in ReadUvarint errors with io.EOF, so reader has no data.
// Subsequent reads with an EOF will return io.ErrUnexpectedEOF and be wrapped here.
return 0, Undef, err
}
return len(br.dst), Undef, ErrInvalidCid{err}
}
// If we have a CIDv0, read the rest of the bytes and cast the buffer.
if vers == mh.SHA2_256 {
if n, err := io.ReadFull(r, br.dst[1:34]); err != nil {
return len(br.dst) + n, Undef, ErrInvalidCid{err}
}
br.dst = br.dst[:34]
h, err := mh.Cast(br.dst)
if err != nil {
return len(br.dst), Undef, ErrInvalidCid{err}
}
return len(br.dst), Cid{string(h)}, nil
}
if vers != 1 {
return len(br.dst), Undef, ErrInvalidCid{fmt.Errorf("expected 1 as the cid version number, got: %d", vers)}
}
// CID block encoding multicodec.
_, err = varint.ReadUvarint(br)
if err != nil {
return len(br.dst), Undef, ErrInvalidCid{err}
}
// We could replace most of the code below with go-multihash's ReadMultihash.
// Note that it would save code, but prevent reusing buffers.
// Plus, we already have a ByteReader now.
mhStart := len(br.dst)
// Multihash hash function code.
_, err = varint.ReadUvarint(br)
if err != nil {
return len(br.dst), Undef, ErrInvalidCid{err}
}
// Multihash digest length.
mhl, err := varint.ReadUvarint(br)
if err != nil {
return len(br.dst), Undef, ErrInvalidCid{err}
}
// Refuse to make large allocations to prevent OOMs due to bugs.
const maxDigestAlloc = 32 << 20 // 32MiB
if mhl > maxDigestAlloc {
return len(br.dst), Undef, ErrInvalidCid{fmt.Errorf("refusing to allocate %d bytes for a digest", mhl)}
}
// Fine to convert mhl to int, given maxDigestAlloc.
prefixLength := len(br.dst)
cidLength := prefixLength + int(mhl)
if cidLength > cap(br.dst) {
// If the multihash digest doesn't fit in our initial 64 bytes,
// efficiently extend the slice via append+make.
br.dst = append(br.dst, make([]byte, cidLength-len(br.dst))...)
} else {
// The multihash digest fits inside our buffer,
// so just extend its capacity.
br.dst = br.dst[:cidLength]
}
if n, err := io.ReadFull(r, br.dst[prefixLength:cidLength]); err != nil {
// We can't use len(br.dst) here,
// as we've only read n bytes past prefixLength.
return prefixLength + n, Undef, ErrInvalidCid{err}
}
// This simply ensures the multihash is valid.
// TODO: consider removing this bit, as it's probably redundant;
// for now, it helps ensure consistency with CidFromBytes.
_, _, err = mh.MHFromBytes(br.dst[mhStart:])
if err != nil {
return len(br.dst), Undef, ErrInvalidCid{err}
}
return len(br.dst), Cid{string(br.dst)}, nil
}

View File

@@ -1,9 +1,10 @@
//go:build gofuzz // +build gofuzz
package cid package cid
func Fuzz(data []byte) int { func Fuzz(data []byte) int {
cid, err := Cast(data) cid, err := Cast(data)
if err != nil { if err != nil {
return 0 return 0
} }

View File

@@ -2,21 +2,48 @@ package cid
import ( import (
"bytes" "bytes"
crand "crypto/rand"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"testing/iotest"
mbase "github.com/multiformats/go-multibase" mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash" 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",
Libp2pKey: "libp2p-key",
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",
DashBlock: "dash-block",
DashTx: "dash-tx",
FilCommitmentUnsealed: "fil-commitment-unsealed",
FilCommitmentSealed: "fil-commitment-sealed",
DagJOSE: "dag-jose",
}
func assertEqual(t *testing.T, a, b Cid) { func assertEqual(t *testing.T, a, b Cid) {
if a.Type() != b.Type() { if a.Type() != b.Type() {
t.Fatal("mismatch on type") t.Fatal("mismatch on type")
@@ -31,11 +58,31 @@ 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 TestPrefixSum(t *testing.T) { func TestPrefixSum(t *testing.T) {
// Test creating CIDs both manually and with Prefix. // Test creating CIDs both manually and with Prefix.
// Tests: https://code.sonr.org/go/cid/issues/83 // Tests: https://github.com/ipfs/go-cid/issues/83
for _, hashfun := range []uint64{ for _, hashfun := range []uint64{
mh.IDENTITY, mh.SHA3, mh.SHA2_256, mh.ID, mh.SHA3, mh.SHA2_256,
} { } {
h1, err := mh.Sum([]byte("TEST"), hashfun, -1) h1, err := mh.Sum([]byte("TEST"), hashfun, -1)
if err != nil { if err != nil {
@@ -58,7 +105,37 @@ func TestPrefixSum(t *testing.T) {
} }
} }
} }
func TestPrefixDigest(t *testing.T) {
// Test creating CIDs both manually and with Prefix.
// Tests: https://github.com/ipfs/go-cid/issues/83
for _, hashfun := range []uint64{
mh.ID, mh.SHA3, mh.SHA2_256,
} {
h1, err := mh.Sum([]byte("TEST"), hashfun, -1)
if err != nil {
t.Fatal(err)
}
c1 := NewCidV1(Raw, h1)
h2, err := mh.Sum([]byte("foobar"), hashfun, -1)
if err != nil {
t.Fatal(err)
}
c2 := NewCidV1(Raw, h2)
digest, err := mh.Digest([]byte("foobar"), hashfun, -1)
if err != nil {
t.Fatal(err)
}
c3, err := c1.Prefix().FromDigest(digest)
if err != nil {
t.Fatal(err)
}
if !c2.Equals(c3) {
t.Fatal("expected CIDs to be equal")
}
}
}
func TestBasicMarshaling(t *testing.T) { func TestBasicMarshaling(t *testing.T) {
h, err := mh.Sum([]byte("TEST"), mh.SHA3, 4) h, err := mh.Sum([]byte("TEST"), mh.SHA3, 4)
if err != nil { if err != nil {
@@ -163,9 +240,6 @@ func TestBasesMarshaling(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected too-short error from ExtractEncoding") t.Fatal("expected too-short error from ExtractEncoding")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
if ee != -1 { if ee != -1 {
t.Fatal("expected -1 from too-short ExtractEncoding") t.Fatal("expected -1 from too-short ExtractEncoding")
} }
@@ -231,9 +305,6 @@ func TestEmptyString(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("shouldnt be able to parse an empty cid") t.Fatal("shouldnt be able to parse an empty cid")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestV0Handling(t *testing.T) { func TestV0Handling(t *testing.T) {
@@ -289,9 +360,6 @@ func TestV0ErrorCases(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("should have failed to decode that ref") t.Fatal("should have failed to decode that ref")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestNewPrefixV1(t *testing.T) { func TestNewPrefixV1(t *testing.T) {
@@ -350,6 +418,7 @@ func TestNewPrefixV0(t *testing.T) {
if c1.Prefix() != c2.Prefix() { if c1.Prefix() != c2.Prefix() {
t.Fatal("prefixes mismatch") t.Fatal("prefixes mismatch")
} }
} }
func TestInvalidV0Prefix(t *testing.T) { func TestInvalidV0Prefix(t *testing.T) {
@@ -382,9 +451,6 @@ func TestInvalidV0Prefix(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("should error (index %d)", i) t.Fatalf("should error (index %d)", i)
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
} }
@@ -394,9 +460,6 @@ func TestBadPrefix(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("expected error on v3 prefix Sum") t.Fatalf("expected error on v3 prefix Sum")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
func TestPrefixRoundtrip(t *testing.T) { func TestPrefixRoundtrip(t *testing.T) {
@@ -433,30 +496,18 @@ func TestBadPrefixFromBytes(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 0") t.Fatal("expected error for bad byte 0")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 1") t.Fatal("expected error for bad byte 1")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 2") t.Fatal("expected error for bad byte 2")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x01, 0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 3") t.Fatal("expected error for bad byte 3")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
func Test16BytesVarint(t *testing.T) { func Test16BytesVarint(t *testing.T) {
@@ -470,7 +521,7 @@ func TestFuzzCid(t *testing.T) {
buf := make([]byte, 128) buf := make([]byte, 128)
for i := 0; i < 200; i++ { for i := 0; i < 200; i++ {
s := rand.Intn(128) s := rand.Intn(128)
crand.Read(buf[:s]) rand.Read(buf[:s])
_, _ = Cast(buf[:s]) _, _ = Cast(buf[:s])
} }
} }
@@ -483,9 +534,6 @@ func TestParse(t *testing.T) {
if !strings.Contains(err.Error(), "can't parse 123 as Cid") { if !strings.Contains(err.Error(), "can't parse 123 as Cid") {
t.Fatalf("expected int error, got %s", err.Error()) t.Fatalf("expected int error, got %s", err.Error())
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatalf("expected ErrInvalidCid, got %s", err.Error())
}
theHash := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" theHash := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
h, err := mh.FromB58String(theHash) h, err := mh.FromB58String(theHash)
@@ -494,13 +542,13 @@ func TestParse(t *testing.T) {
} }
assertions := [][]interface{}{ assertions := [][]interface{}{
{NewCidV0(h), theHash}, []interface{}{NewCidV0(h), theHash},
{NewCidV0(h).Bytes(), theHash}, []interface{}{NewCidV0(h).Bytes(), theHash},
{h, theHash}, []interface{}{h, theHash},
{theHash, theHash}, []interface{}{theHash, theHash},
{"/ipfs/" + theHash, theHash}, []interface{}{"/ipfs/" + theHash, theHash},
{"https://ipfs.io/ipfs/" + theHash, theHash}, []interface{}{"https://ipfs.io/ipfs/" + theHash, theHash},
{"http://localhost:8080/ipfs/" + theHash, theHash}, []interface{}{"http://localhost:8080/ipfs/" + theHash, theHash},
} }
assert := func(arg interface{}, expected string) error { assert := func(arg interface{}, expected string) error {
@@ -603,29 +651,17 @@ func TestJsonRoundTrip(t *testing.T) {
t.Fatal("cids not equal for Cid") t.Fatal("cids not equal for Cid")
} }
err = actual2.UnmarshalJSON([]byte("1")) if err = actual2.UnmarshalJSON([]byte("1")); err == nil {
if err == nil {
t.Fatal("expected error for too-short JSON") t.Fatal("expected error for too-short JSON")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
err = actual2.UnmarshalJSON([]byte(`{"nope":"nope"}`)) if err = actual2.UnmarshalJSON([]byte(`{"nope":"nope"}`)); err == nil {
if err == nil {
t.Fatal("expected error for bad CID JSON") t.Fatal("expected error for bad CID JSON")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
err = actual2.UnmarshalJSON([]byte(`bad "" json!`)) if err = actual2.UnmarshalJSON([]byte(`bad "" json!`)); err == nil {
if err == nil {
t.Fatal("expected error for bad JSON") t.Fatal("expected error for bad JSON")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
var actual3 Cid var actual3 Cid
enc, err = actual3.MarshalJSON() enc, err = actual3.MarshalJSON()
@@ -660,7 +696,6 @@ func TestReadCidsFromBuffer(t *testing.T) {
"k2cwueckqkibutvhkr4p2ln2pjcaxaakpd9db0e7j7ax1lxhhxy3ekpv", "k2cwueckqkibutvhkr4p2ln2pjcaxaakpd9db0e7j7ax1lxhhxy3ekpv",
"Qmf5Qzp6nGBku7CEn2UQx4mgN8TW69YUok36DrGa6NN893", "Qmf5Qzp6nGBku7CEn2UQx4mgN8TW69YUok36DrGa6NN893",
"zb2rhZi1JR4eNc2jBGaRYJKYM8JEB4ovenym8L1CmFsRAytkz", "zb2rhZi1JR4eNc2jBGaRYJKYM8JEB4ovenym8L1CmFsRAytkz",
"bafkqarjpmzuwyzltorxxezjpkvcfgqkfjfbfcvslivje2vchkzdu6rckjjcfgtkolaze6mssjqzeyn2ekrcfatkjku2vowseky3fswkfkm2deqkrju3e2",
} }
var cids []Cid var cids []Cid
@@ -688,122 +723,51 @@ func TestReadCidsFromBuffer(t *testing.T) {
if cur != len(buf) { if cur != len(buf) {
t.Fatal("had trailing bytes") t.Fatal("had trailing bytes")
} }
// The same, but now with CidFromReader.
// In multiple forms, to catch more io interface bugs.
for _, r := range []io.Reader{
// implements io.ByteReader
bytes.NewReader(buf),
// tiny reads, no io.ByteReader
iotest.OneByteReader(bytes.NewReader(buf)),
} {
cur = 0
for _, expc := range cids {
n, c, err := CidFromReader(r)
if err != nil {
t.Fatal(err)
}
if c != expc {
t.Fatal("cids mismatched")
}
cur += n
}
if cur != len(buf) {
t.Fatal("had trailing bytes")
}
}
} }
func TestBadCidInput(t *testing.T) { func TestBadCidFromBytes(t *testing.T) {
for _, name := range []string{ l, c, err := CidFromBytes([]byte{mh.SHA2_256, 32, 0x00})
"FromBytes", if err == nil {
"FromReader", t.Fatal("expected not-enough-bytes for V0 CidFromBytes")
} {
t.Run(name, func(t *testing.T) {
usingReader := name == "FromReader"
fromBytes := CidFromBytes
if usingReader {
fromBytes = func(data []byte) (int, Cid, error) {
return CidFromReader(bytes.NewReader(data))
}
}
l, c, err := fromBytes([]byte{mh.SHA2_256, 32, 0x00})
if err == nil {
t.Fatal("expected not-enough-bytes for V0 CID")
}
if !usingReader && l != 0 {
t.Fatal("expected length==0 from bad CID")
} else if usingReader && l == 0 {
t.Fatal("expected length!=0 from bad CID")
}
if c != Undef {
t.Fatal("expected Undef CID from bad CID")
}
c, err = Decode("bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm")
if err != nil {
t.Fatal(err)
}
byts := make([]byte, c.ByteLen())
copy(byts, c.Bytes())
byts[1] = 0x80 // bad codec varint
byts[2] = 0x00
l, c, err = fromBytes(byts)
if err == nil {
t.Fatal("expected not-enough-bytes for V1 CID")
}
if !usingReader && l != 0 {
t.Fatal("expected length==0 from bad CID")
} else if usingReader && l == 0 {
t.Fatal("expected length!=0 from bad CID")
}
if c != Undef {
t.Fatal("expected Undef CID from bad CID")
}
copy(byts, c.Bytes())
byts[2] = 0x80 // bad multihash varint
byts[3] = 0x00
l, c, err = fromBytes(byts)
if err == nil {
t.Fatal("expected not-enough-bytes for V1 CID")
}
if !usingReader && l != 0 {
t.Fatal("expected length==0 from bad CID")
} else if usingReader && l == 0 {
t.Fatal("expected length!=0 from bad CID")
}
if c != Undef {
t.Fatal("expected Undef CID from bad CidFromBytes")
}
})
} }
} if l != 0 {
t.Fatal("expected length=0 from bad CidFromBytes")
func TestFromReaderNoData(t *testing.T) {
// Reading no data from io.Reader should return io.EOF, not ErrInvalidCid.
n, cid, err := CidFromReader(bytes.NewReader(nil))
if err != io.EOF {
t.Fatal("Expected io.EOF error")
} }
if cid != Undef { if c != Undef {
t.Fatal("Expected Undef CID") t.Fatal("expected Undef CID from bad CidFromBytes")
}
if n != 0 {
t.Fatal("Expected 0 data")
} }
// Read byte indicatiing more data to and check error is ErrInvalidCid. c, err = Decode("bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm")
_, _, err = CidFromReader(bytes.NewReader([]byte{0x80})) if err != nil {
if !errors.Is(err, ErrInvalidCid{}) { t.Fatal(err)
t.Fatal("Expected ErrInvalidCid error")
} }
// Check for expected wrapped error. byts := make([]byte, c.ByteLen())
if !errors.Is(err, io.ErrUnexpectedEOF) { copy(byts, c.Bytes())
t.Fatal("Expected error", io.ErrUnexpectedEOF) byts[1] = 0x80 // bad codec varint
byts[2] = 0x00
l, c, err = CidFromBytes(byts)
if err == nil {
t.Fatal("expected not-enough-bytes for V1 CidFromBytes")
}
if l != 0 {
t.Fatal("expected length=0 from bad CidFromBytes")
}
if c != Undef {
t.Fatal("expected Undef CID from bad CidFromBytes")
}
copy(byts, c.Bytes())
byts[2] = 0x80 // bad multihash varint
byts[3] = 0x00
l, c, err = CidFromBytes(byts)
if err == nil {
t.Fatal("expected not-enough-bytes for V1 CidFromBytes")
}
if l != 0 {
t.Fatal("expected length=0 from bad CidFromBytes")
}
if c != Undef {
t.Fatal("expected Undef CID from bad CidFromBytes")
} }
} }
@@ -816,9 +780,6 @@ func TestBadParse(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected to fail to parse an invalid CIDv1 CID") t.Fatal("expected to fail to parse an invalid CIDv1 CID")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestLoggable(t *testing.T) { func TestLoggable(t *testing.T) {
@@ -833,80 +794,3 @@ func TestLoggable(t *testing.T) {
t.Fatalf("did not get expected loggable form (got %v)", actual) t.Fatalf("did not get expected loggable form (got %v)", actual)
} }
} }
func TestErrInvalidCidIs(t *testing.T) {
for i, test := range []struct {
err error
target error
}{
{&ErrInvalidCid{}, ErrInvalidCid{}},
{ErrInvalidCid{}, &ErrInvalidCid{}},
{ErrInvalidCid{}, ErrInvalidCid{}},
{&ErrInvalidCid{}, &ErrInvalidCid{}},
} {
if !errors.Is(test.err, test.target) {
t.Fatalf("expected error to be ErrInvalidCid, case %d", i)
}
}
}
func TestErrInvalidCid(t *testing.T) {
run := func(err error) {
if err == nil {
t.Fatal("expected error")
}
if !strings.HasPrefix(err.Error(), "invalid cid: ") {
t.Fatal(`expected error message to contain "invalid cid: "`)
}
is := errors.Is(err, ErrInvalidCid{})
if !is {
t.Fatal("expected error to be ErrInvalidCid")
}
if !errors.Is(err, &ErrInvalidCid{}) {
t.Fatal("expected error to be &ErrInvalidCid")
}
}
_, err := Decode("")
run(err)
_, err = Decode("not-a-cid")
run(err)
_, err = Decode("bafyInvalid")
run(err)
_, err = Decode("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII")
run(err)
_, err = Cast([]byte("invalid"))
run(err)
_, err = Parse("not-a-cid")
run(err)
_, err = Parse("bafyInvalid")
run(err)
_, err = Parse("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII")
run(err)
_, err = Parse(123)
run(err)
_, _, err = CidFromBytes([]byte("invalid"))
run(err)
_, err = Prefix{}.Sum([]byte("data"))
run(err)
_, err = PrefixFromBytes([]byte{0x80})
run(err)
_, err = ExtractEncoding("invalid ")
run(err)
}

22
go.mod
View File

@@ -1,21 +1,9 @@
module code.sonr.org/go/cid module github.com/ipfs/go-cid
require ( require (
github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multibase v0.0.3
github.com/multiformats/go-multihash v0.2.3 github.com/multiformats/go-multihash v0.0.15-0.20210226003459-69a4fed1d6f2
github.com/multiformats/go-varint v0.1.0 github.com/multiformats/go-varint v0.0.6
) )
require ( go 1.13
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sys v0.38.0 // indirect
lukechampine.com/blake3 v1.1.6 // indirect
)
go 1.24.0

46
go.sum
View File

@@ -1,25 +1,33 @@
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4=
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/multiformats/go-multihash v0.0.15-0.20210226003459-69a4fed1d6f2 h1:0DZ3ybel2YfQ4L9GrglYgfXxdXVT8CVkJt//crxSQDM=
github.com/multiformats/go-multihash v0.0.15-0.20210226003459-69a4fed1d6f2/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg=
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -1,3 +0,0 @@
{
"version": "v0.6.0"
}