Compare commits

...

202 Commits

Author SHA1 Message Date
Daniel Martí
373e25552c avoid double alloc in NewCidV1
We allocate once via "make([]byte, len)",
and again when that buffer is converted to a string.

Thankfully, since Go 1.10 we have strings.Builder,
designed specifically for this use case.

In a downstream benchmark in go-car,
which needs to reconstruct many CID values,
we see small but nice gains:

    name           old time/op    new time/op    delta
    ReadBlocks-16    1.09ms ± 4%    1.06ms ± 5%   -3.33%  (p=0.007 n=11+11)

    name           old speed      new speed      delta
    ReadBlocks-16   478MB/s ± 4%   494MB/s ± 5%   +3.46%  (p=0.007 n=11+11)

    name           old alloc/op   new alloc/op   delta
    ReadBlocks-16    1.30MB ± 0%    1.25MB ± 0%   -3.86%  (p=0.000 n=12+12)

    name           old allocs/op  new allocs/op  delta
    ReadBlocks-16     9.50k ± 0%     8.45k ± 0%  -11.05%  (p=0.000 n=12+12)
2021-09-07 16:49:03 +02:00
Steven Allen
cf76220258 Merge pull request #131 from ipfs/web3-bot/sync
sync: update CI config files
2021-08-30 08:37:22 -07:00
web3-bot
9e2855d9ff update .github/workflows/go-check.yml 2021-08-17 13:32:06 +00:00
web3-bot
b8eba8ea35 update .github/workflows/go-test.yml 2021-08-17 13:32:06 +00:00
web3-bot
2628583977 update .github/workflows/automerge.yml 2021-08-17 13:32:06 +00:00
web3-bot
6e96c56557 run gofmt -s 2021-08-17 13:32:05 +00:00
web3-bot
44cccd62db bump go.mod to Go 1.16 and run go fix 2021-08-17 13:32:05 +00:00
Daniel Martí
de6c03deae amend the CidFromReader slice extension math
The append+make slice extension idiom works, but note that append uses
the slice's length as its base. We need to append the number of bytes
required for length to reach cidLength, not the capacity.

The added test case panicked before this change, and works now:

	--- FAIL: TestReadCidsFromBuffer (0.00s)
	panic: runtime error: slice bounds out of range [:73] with capacity 64 [recovered]
		panic: runtime error: slice bounds out of range [:73] with capacity 64

	goroutine 37 [running]:
	testing.tRunner.func1.2({0x570d60, 0xc000016438})
		testing/testing.go:1203 +0x24e
	testing.tRunner.func1()
		testing/testing.go:1206 +0x218
	panic({0x570d60, 0xc000016438})
		runtime/panic.go:1038 +0x215
	github.com/ipfs/go-cid.CidFromReader({0x5b0e20, 0xc000010900})
		github.com/ipfs/go-cid/cid.go:803 +0x75f
	github.com/ipfs/go-cid.TestReadCidsFromBuffer(0xc00014ba00)
		github.com/ipfs/go-cid/cid_test.go:710 +0x625
	testing.tRunner(0xc00014ba00, 0x58af38)
		testing/testing.go:1253 +0x102
	created by testing.(*T).Run
		testing/testing.go:1300 +0x35a
	exit status 2
	FAIL	github.com/ipfs/go-cid	0.004s
2021-07-16 10:10:50 +01:00
Daniel Martí
c4c8760a80 implement CidFromReader
And reuse two CidFromBytes tests for it, which includes both CIDv0 and
CIDv1 cases as inputs, as well as some inputs that should error.

Fixes #126.
2021-07-15 01:01:22 +01:00
Steven Allen
8e9280df3d Merge pull request #118 from ipfs/multihash-update
Update go-multihash.  Now uses registry system.
2021-06-25 17:23:48 -07:00
Eric Myhre
b4bcfe45c9 Update go-multihash.
You can't see it from here, but go-mulithash now uses registry system,
so it's reasonably possible to introduce new hashers, and to use
(some parts of!) go-multihash without bringing in lots of transitive
dependencies.

The main package of go-multihash still brings in everything
transitively that it did before, so go-cid's transitives aren't
shrinking, and no code is changing here... but it's closer.
If we did cut over to the new go-mulithash/core, we could make
many transitive dependencies become optional.
2021-06-25 16:05:47 -07:00
Marten Seemann
979bf3fb85 Merge pull request #125 from ipfs/web3-bot/sync
sync: update CI config files
2021-06-01 12:40:31 -07:00
web3-bot
95ec98a9cc update .github/workflows/go-check.yml 2021-06-01 16:53:30 +00:00
web3-bot
1c23d3b694 update .github/workflows/go-test.yml 2021-06-01 16:53:30 +00:00
web3-bot
5b5d1329c5 update .github/workflows/automerge.yml 2021-06-01 16:53:30 +00:00
web3-bot
a42c04af93 sync: update CI config files (#124) 2021-05-12 15:13:58 +00:00
Steven Allen
0582f6b9b2 Merge pull request #122 from iand/chore/repo-health
chore: fixups from running go vet, go fmt and staticcheck
2021-05-10 08:32:22 -07:00
Ian Davis
0717510dfb chore: fixups from running go vet, go fmt and staticcheck 2021-05-07 15:43:00 +01:00
dependabot-preview[bot]
e530276a70 Bump github.com/multiformats/go-varint from 0.0.5 to 0.0.6
Bumps [github.com/multiformats/go-varint](https://github.com/multiformats/go-varint) from 0.0.5 to 0.0.6.
- [Release notes](https://github.com/multiformats/go-varint/releases)
- [Commits](https://github.com/multiformats/go-varint/compare/v0.0.5...v0.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-15 14:27:46 +11:00
dependabot-preview[bot]
45f4147a06 Bump github.com/multiformats/go-multihash from 0.0.13 to 0.0.14
Bumps [github.com/multiformats/go-multihash](https://github.com/multiformats/go-multihash) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/multiformats/go-multihash/releases)
- [Commits](https://github.com/multiformats/go-multihash/compare/v0.0.13...v0.0.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-15 14:12:37 +11:00
Rod Vagg
b34ad3cfa5 s/characters/bytes 2020-10-15 14:05:52 +11:00
gammazero
ec089b8a53 Fix inaccurate comment for uvarint
This fixes the comment to be consistent with the code, and fixes spelling.

Fixes Issue #111
2020-10-15 14:05:52 +11:00
Rod Vagg
efe2d2de45 coverage: more tests for cid 2020-10-15 14:00:36 +11:00
Rod Vagg
46aac88838 coverage: more tests for varint 2020-10-15 14:00:36 +11:00
Rod Vagg
f60e346b24 coverage: more tests for builder 2020-10-15 14:00:36 +11:00
Rod Vagg
8647a1d84b fix: make tests run with Go 1.15
> ./cid_test.go:451:52: conversion from uint64 to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
2020-10-15 14:00:36 +11:00
Alex Good
de49849130 Add the dagjose multiformat 2020-10-15 14:00:36 +11:00
Whyrusleeping
8b9ff3906e Merge pull request #109 from ipfs/feat/optimize-prefix
feat: optimize cid.Prefix
2020-07-23 09:15:54 -07:00
Steven Allen
f7cb4c91eb feat: optimize cid.Prefix
We call this frequently and having to allocate here is really unfortunate.
2020-07-22 19:45:20 -07:00
Peter Rabbitson
85cd30874e Merge pull request #107 from ipfs/chore/base36_support
Chore/base36 support
2020-05-25 18:15:04 +02:00
Peter Rabbitson
12e2623f35 gx-era file no longer relevant 2020-05-25 18:08:48 +02:00
Marcin Rataj
d683e9243c test: lowercase base36
Base36 was introduced mainly for use in DNS, and various user agents
force lowercase, so tests should use that instead.

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
2020-05-25 17:46:16 +02:00
Peter Rabbitson
18af217de6 Add explicit base36 test 2020-05-25 14:06:26 +02:00
Peter Rabbitson
9830f823b5 Base36 support 2020-05-25 13:51:07 +02:00
Peter Rabbitson
d110f73c43 Remove gx remnants 2020-05-25 13:50:43 +02:00
Steven Allen
95a7ed4233 Merge pull request #104 from rvagg/rvagg/filecoin-codecs
feat: add Filecoin multicodecs
2020-05-25 00:46:19 -07:00
Rod Vagg
8dd6fe2778 feat: add Filecoin multicodecs
* fil-commitment-unsealed
* fil-commitment-sealed

Ref: https://github.com/multiformats/multicodec/pull/161
Ref: https://github.com/multiformats/multicodec/pull/172
2020-05-13 11:22:42 +10:00
Hector Sanjuan
a25d68f3e4 Add autocomment configuration 2020-05-04 12:05:06 +02:00
Whyrusleeping
7c82f3b81c Merge pull request #103 from ipfs/fix/write-to-interface
avoid calling the method WriteTo if we don't satisfy its contract
2020-05-01 16:06:55 -07:00
Jeromy
75caa6bdbd avoid calling the method WriteTo if we don't satisfy its contract 2020-05-01 16:04:36 -07:00
Whyrusleeping
c1c89c20c1 Merge pull request #102 from ipfs/feat/add-methods-for-using-less-memory
add a couple useful methods
2020-05-01 15:54:35 -07:00
Eric Myhre
628a0123ed Slightly more documentation comment. 2020-05-02 00:52:07 +02:00
Jeromy
266e76d591 properly satisfy writerTo interface 2020-05-01 15:44:06 -07:00
Jeromy
7d7cb88d78 add a couple useful methods 2020-05-01 15:01:28 -07:00
Steven Allen
723b4ab105 Merge pull request #101 from vmx/patch-1
chore: make comment reflect the code
2020-04-29 09:27:52 -07:00
Volker Mische
9cb3848e51 chore: make comment reflect the code
During the change to default CIDV1 to Base32, the corresponding code
comment wasn't updated.
2020-04-28 16:02:21 +02:00
Hector Sanjuan
f458e04107 Add standard issue template 2020-04-27 11:27:08 +02:00
Steven Allen
72cd3d39d7 Merge pull request #99 from ipfs/fix/minimal-encoding
fix: enforce minimal encoding
2020-02-04 12:12:58 -08:00
Steven Allen
c1b740035a fix: enforce minimal encoding
Note: this removes two errors that don't appear to be used by anyone.
2020-02-03 20:55:45 -08:00
Steven Allen
3da5bbbe45 Merge pull request #92 from b5/chore_remove_gx
docs(readme): remove reference to gx
2019-12-09 06:47:25 -06:00
Steven Allen
9d598bbdbd Merge pull request #98 from ipfs/dependabot/go_modules/github.com/multiformats/go-multihash-0.0.10
Bump github.com/multiformats/go-multihash from 0.0.9 to 0.0.10
2019-12-09 06:24:09 -06:00
dependabot-preview[bot]
b420452400 Bump github.com/multiformats/go-multihash from 0.0.9 to 0.0.10
Bumps [github.com/multiformats/go-multihash](https://github.com/multiformats/go-multihash) from 0.0.9 to 0.0.10.
- [Release notes](https://github.com/multiformats/go-multihash/releases)
- [Commits](https://github.com/multiformats/go-multihash/compare/v0.0.9...v0.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-09 11:21:09 +00:00
Steven Allen
ed7dab1755 Merge pull request #97 from ipfs/fix/parse-panic
fix parsing issues and nits
2019-12-03 09:50:46 -05:00
Steven Allen
5df89959a0 test: test parsing non-sha256 hashes 2019-12-02 20:41:59 -05:00
Steven Allen
60ab0f84f0 chore: avoid magic numbers 2019-12-02 20:35:36 -05:00
Steven Allen
08e15f8a6c chore: avoid re-validated already validated CIDs 2019-12-02 20:35:27 -05:00
Steven Allen
58b483a841 fix: avoid panicing if we try to parse a non-sha multihash as a CIDv0
I'm pretty sure we never call `Parse` multihashes anyways, but we should be careful.
2019-12-02 20:34:30 -05:00
Whyrusleeping
79e75dffeb Merge pull request #95 from ipfs/feat/cid-from-bytes
Add a function to read a cid from an array of bytes
2019-11-12 10:17:18 +09:00
whyrusleeping
d93d4baeab add a simple test for reading cids from a buffer 2019-11-11 16:48:19 -08:00
whyrusleeping
cf8cf8856e Use optimized multihash method for parsing 2019-11-11 16:33:49 -08:00
Steven Allen
aa8072eeb5 Merge pull request #96 from ipfs/fix/verif-v0
Verify that prefix is correct v0 prefix
2019-11-11 11:29:02 -08:00
Jakub Sztandera
51871ccaa9 Disable gx builds
License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
2019-11-11 20:04:40 +01:00
Jakub Sztandera
9238d1a533 Verify that prefix is correct v0 prefix
License: MIT
Signed-off-by: Jakub Sztandera <kubuxu@protocol.ai>
2019-11-11 20:00:31 +01:00
whyrusleeping
16bbe1cf14 Add a command to read a cid from an array of bytes 2019-11-10 22:17:41 -08:00
b5
c91a795815 docs(readme): remove reference to gx 2019-10-24 10:06:43 -04:00
Steven Allen
64acce09e1 Merge pull request #91 from ipfs/dependabot/go_modules/github.com/multiformats/go-multihash-0.0.8
Bump github.com/multiformats/go-multihash from 0.0.1 to 0.0.8
2019-09-24 16:11:26 -07:00
dependabot-preview[bot]
2a2f67d2c4 Bump github.com/multiformats/go-multihash from 0.0.1 to 0.0.8
Bumps [github.com/multiformats/go-multihash](https://github.com/multiformats/go-multihash) from 0.0.1 to 0.0.8.
- [Release notes](https://github.com/multiformats/go-multihash/releases)
- [Commits](https://github.com/multiformats/go-multihash/compare/v0.0.1...v0.0.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-24 23:01:51 +00:00
Steven Allen
86ca2a8300 Merge pull request #90 from ipfs/feat/lead-maintainer
doc: add a lead maintainer
2019-09-18 22:09:20 -07:00
Steven Allen
e0a778be45 doc: add a lead maintainer 2019-09-18 22:08:21 -07:00
Steven Allen
9bb7ea6920 Merge pull request #86 from lidel/feat/libp2p-key
Add support for libp2p-key multicodec to go-cid
2019-05-27 16:46:10 -07:00
Marcin Rataj
3f1777738f Add libp2p-key multicodec
Context: https://github.com/multiformats/multicodec/issues/130

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
2019-05-28 01:40:54 +02:00
Steven Allen
b1cc3e404d Merge pull request #85 from ipfs/feat/cidv1-default-base32
default cidv1 to base32
2019-05-13 10:54:14 -07:00
Steven Allen
b16425b966 make CID in readme base32 2019-05-10 09:41:01 -07:00
Steven Allen
f04f9216e7 default cidv1 to base32 2019-05-06 16:22:17 -07:00
Jakub Sztandera
e7e67e08cf Add gomod and travis 2019-02-28 18:32:58 +01:00
Steven Allen
29a66d1820 gx publish 0.9.3 2019-02-20 20:12:25 -08:00
Steven Allen
08f30d213e Merge pull request #84 from ipfs/fix/83
fix inline CIDs generated by Prefix.Sum
2019-02-20 20:11:24 -08:00
Steven Allen
cf3b4efcaf fix inline CIDs generated by Prefix.Sum 2019-02-20 19:06:04 -08:00
Hector Sanjuan
ca991e8eb6 Merge pull request #82 from ipfs/gx/0.9.2
gx publish 0.9.2
2019-02-20 16:21:43 +00:00
Hector Sanjuan
8d327b2f4b gx publish 0.9.2 2019-02-20 16:21:01 +00:00
Hector Sanjuan
14b828acf5 Merge pull request #81 from ipfs/feat/binary-marshaler
Let Cid implement Binary[Un]Marshaler and Text[Un]Marshaler interfaces.
2019-02-20 16:20:09 +00:00
Hector Sanjuan
00439572fb Let Cid implement Binary[Un]Marshaler and Text[Un]Marshaler interfaces.
This makes Cid implement https://golang.org/pkg/encoding/#BinaryMarshaler
which is used by go-codec to decide if things know how to serialize themselves
(currently we need do manual wrapping for anything containing a CID).

Since I was at it, I did the TextMarshaling one too.
2019-02-19 16:41:30 +00:00
Jakub Sztandera
37bf2f9503 Merge pull request #80 from madper/fix_typo
fix typo in comment
2019-02-15 18:30:48 +01:00
Madper Xie
e6d04f280e fix typo in comment 2019-02-15 20:19:46 +08:00
Steven Allen
033594dcd6 gx publish 0.9.1 2018-11-02 16:51:23 -07:00
Steven Allen
c9e99b39db Merge pull request #78 from samli88/dash-codecs
add codecs for Dash blocks, tx
2018-10-23 05:29:30 +01:00
Samuel Li
3ec3578fe9 add dash to codecs table 2018-10-07 11:44:18 -07:00
Samuel Li
628ab3426c add codecs for Dash blocks, tx 2018-10-07 11:21:41 -07:00
Kevin Atkinson
6e296c5c49 gx publish 0.9.0 2018-09-11 19:18:20 -04:00
Kevin Atkinson
f0033600ca Gx update go-multibase. 2018-09-11 19:17:40 -04:00
Kevin Atkinson
dfc48d3ec4 Make sure we have a SHA2_256, length 32 hash when creating a CidV0. 2018-09-11 19:17:40 -04:00
Kevin Atkinson
46dd393ad1 Handel undefined Cid is JSON representation. 2018-09-07 14:03:03 -04:00
Kevin Atkinson
67a2bcf7e7 Change 'Nil' constant to 'Undef'. 2018-09-05 15:43:18 -04:00
Kevin Atkinson
643f78a8f9 Change 'IsNil' method to 'Defined'. 2018-09-05 03:26:26 -04:00
Kevin Atkinson
7b4617fa6e Eliminate unnecessary copy of Cid now that its an immutable string. 2018-09-01 00:09:38 -04:00
Kevin Atkinson
440a1c1a5a Removed description of layout of CID as it is not correct for CIDv0. 2018-08-31 00:35:55 -04:00
Kevin Atkinson
e0a5698af9 Add IsNil() method. 2018-08-31 00:35:54 -04:00
Kevin Atkinson
667c6a9418 Avoid allocating memory in Type() method. 2018-08-31 00:35:53 -04:00
Kevin Atkinson
426ebe9e55 Simplify assignment in UnmarshalJSON. 2018-08-31 00:35:52 -04:00
Kevin Atkinson
cad52160a4 Ensure we always have a valid Cid by hiding the type in a struct. 2018-08-31 00:35:51 -04:00
Kevin Atkinson
b5a08dcaaa Change EmptyCid to just Nil. 2018-08-31 00:35:51 -04:00
Kevin Atkinson
9831436a6f Change string representation to represent actual binary representation. 2018-08-31 00:35:47 -04:00
Kevin Atkinson
d7974d2277 Export version() method, various other code cleanups. 2018-08-31 00:34:19 -04:00
dignifiedquire
8009448a20 fix KeyString() 2018-08-31 00:34:03 -04:00
dignifiedquire
92496b5494 use string instead of []byte as underlying store 2018-08-31 00:34:02 -04:00
dignifiedquire
e153340e5a feat: use CIDs as their byte representation instead of a struct 2018-08-31 00:33:47 -04:00
Steven Allen
6ddb575a8d Merge pull request #60 from ipfs/kevina/cid-fmtb
Create a new Encode method that is like StringOfBase but never errors
2018-08-30 23:06:50 +00:00
Kevin Atkinson
b3d85b3dee Enhance documentation for Encode method. 2018-08-30 00:38:17 -04:00
Kevin Atkinson
bea727bbd1 Enhance tests. 2018-08-30 00:30:14 -04:00
Kevin Atkinson
9091e50b29 Rename Format method to Encode. 2018-08-30 00:30:14 -04:00
Kevin Atkinson
a0b3b11e63 Create a new Format method that is like StringOfBase but never errors 2018-08-30 00:30:14 -04:00
Steven Allen
1766ab0fcf Merge pull request #72 from ipfs/rsrch-cid-as-struct-wrapped-str
cid implementation variations++
2018-08-30 01:52:53 +00:00
Eric Myhre
924534b811 Inspect memory layout of struct wrapping string.
It's also viable.  Options list expanded.  (And regretting my ordering of it now.  Wish I'd thought of this one and realized it's distinct earlier.)
2018-08-28 01:00:21 +02:00
Eric Myhre
5ddbe21740 Merge pull request #70 from ipfs/rsrch
cid implementation research
2018-08-28 00:34:28 +02:00
Eric Myhre
2cf56e3813 Benchmarks of various Cid types as map keys.
And writeup on same.

tl;dr interfaces are not cheap if you're already at the scale where you
started caring about whether or not you have pointers.
2018-08-24 14:03:54 +02:00
Eric Myhre
5a6d4bdf06 More readme on the state of iface research. 2018-08-24 13:18:50 +02:00
Eric Myhre
fb8ecaccad Enumerate some more options in prose. 2018-08-24 12:37:52 +02:00
Eric Myhre
348b9201a6 Start a readme for this research project.
Right now this is mostly this is to document the behavior of interface-keyed
maps.  I suspect some of those caveats might be non-obvious to a lot of folks.
2018-08-24 12:24:34 +02:00
Eric Myhre
b4ab25ffda Discovered interesting case in map key checking.
Using interfaces as a map's key type can cause some things that were otherwise
compile-time checks to be pushed off into runtime checks instead.
This is a pretty major "caveat emptor" if you use interface-keyed maps.
2018-08-24 12:18:07 +02:00
Eric Myhre
c724ad0d22 cid impl via struct and via string together.
Added back in some of the parser methods.  (These were previously named "Cast"
and I think that's silly and wrong so I fixed it.)

Functions are named overly-literally with their type (e.g. ParseCidString and
ParseCidStruct rather than ParseCid or even just Parse) because for this
research package I don't want to bother with many sub-packages.  (Maybe I'll
regret this, but at the moment it seems simpler to hold back on sub-packages.)

Functions that produce Cids are literal with their return types, as well.
Part of the purpose of this research package is going to be to concretely
benchmark exactly how much performance overhead there is to using interfaces
(which will likely cause a lot of boxing and unboxing in practice) -- since we
want to explore where this boxing happens and how much it costs, it's important
that none of our basic implementation functions do the boxing!

The entire set of codec enums came along in this commit.  Ah well; they would
have eventually anyway, I guess.  But it's interesting to note the only thing
that dragged them along so far is the reference to 'DagProtobuf' when
constructing v0 CIDs; otherwise, this enum is quite unused here.
2018-08-24 12:00:31 +02:00
Eric Myhre
ff25e9673c Open research dir; want to explore cid impl perf.
It's been discussed in several issues and PRs already that we might want to
explore various ways of implementing CIDs for maximum performance and
ease-of-use because they show up extremely often.  Current CIDs are pointers,
which generally speaking means you can't get one without a malloc; and also,
they're not particularly well-suited for use in map keys.

This branch is to attempt to consolidate all the proposals so far -- and do so
in a single branch which can be checked out and contains all the proposals at
once, because this will make it easy to do benchmarks and compare all of the
various ways we could implement this in one place (and also easier for humans
to track what the latest of each proposal is, since they're all in one place).

To start with: a Cid implementation backed by a string; and matching interface.

(I'm also taking this opportunity to be as minimalistic as possible in what
I port over into these experimental new Cid implementations.  This might not
last; but as long as all this work is to be done, it's a more convenient time
than usual to see what can be stripped down and still get work done.)

More to come.
2018-08-24 10:53:52 +02:00
Kevin Atkinson
afcde25c66 gx publish 0.8.0 2018-08-21 15:15:34 -04:00
Kevin Atkinson
fb85ebd768 Merge pull request #69 from ipfs/kevina/extract
Extract non-core functionality from go-cid into go-cidutil
2018-08-21 15:11:14 -04:00
Kevin Atkinson
870aa9e7de Extract non-core functionality from go-cid into go-cidutil. 2018-08-16 21:51:31 -04:00
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
32 changed files with 2804 additions and 267 deletions

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Getting Help on IPFS
url: https://ipfs.io/help
about: All information about how and where to get help on IPFS.
- name: IPFS Official Forum
url: https://discuss.ipfs.io
about: Please post general questions, support requests, and discussions here.

19
.github/ISSUE_TEMPLATE/open_an_issue.md vendored Normal file
View File

@@ -0,0 +1,19 @@
---
name: Open an issue
about: Only for actionable issues relevant to this repository.
title: ''
labels: need/triage
assignees: ''
---
<!--
Hello! To ensure this issue is correctly addressed as soon as possible by the IPFS team, please try to make sure:
- This issue is relevant to this repository's topic or codebase.
- A clear description is provided. It should includes as much relevant information as possible and clear scope for the issue to be actionable.
FOR GENERAL DISCUSSION, HELP OR QUESTIONS, please see the options at https://ipfs.io/help or head directly to https://discuss.ipfs.io.
(you can delete this section after reading)
-->

68
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
# Configuration for welcome - https://github.com/behaviorbot/welcome
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thank you for submitting your first issue to this repository! A maintainer
will be here shortly to triage and review.
In the meantime, please double-check that you have provided all the
necessary information to make this process easy! Any information that can
help save additional round trips is useful! We currently aim to give
initial feedback within **two business days**. If this does not happen, feel
free to leave a comment.
Please keep an eye on how this issue will be labeled, as labels give an
overview of priorities, assignments and additional actions requested by the
maintainers:
- "Priority" labels will show how urgent this is for the team.
- "Status" labels will show if this is ready to be worked on, blocked, or in progress.
- "Need" labels will indicate if additional input or analysis is required.
Finally, remember to use https://discuss.ipfs.io if you just need general
support.
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
Thank you for submitting this PR!
A maintainer will be here shortly to review it.
We are super grateful, but we are also overloaded! Help us by making sure
that:
* The context for this PR is clear, with relevant discussion, decisions
and stakeholders linked/mentioned.
* Your contribution itself is clear (code comments, self-review for the
rest) and in its best form. Follow the [code contribution
guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#code-contribution-guidelines)
if they apply.
Getting other community members to do a review would be great help too on
complex PRs (you can ask in the chats/forums). If you are unsure about
something, just leave us a comment.
Next steps:
* A maintainer will triage and assign priority to this PR, commenting on
any missing things and potentially assigning a reviewer for high
priority items.
* The PR gets reviews, discussed and approvals as needed.
* The PR is merged by maintainers when it has been approved and comments addressed.
We currently aim to provide initial feedback/triaging within **two business
days**. Please keep an eye on any labelling actions, as these will indicate
priorities and status of your contribution.
We are very grateful for your contribution!
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
# Currently disabled
#firstPRMergeComment: ""

53
.github/workflows/automerge.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
# Automatically merge pull requests opened by web3-bot, as soon as (and only if) all tests pass.
# This reduces the friction associated with updating with our workflows.
on: [ pull_request ]
name: Automerge
jobs:
automerge-check:
if: github.event.pull_request.user.login == 'web3-bot'
runs-on: ubuntu-latest
outputs:
status: ${{ steps.should-automerge.outputs.status }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check if we should automerge
id: should-automerge
run: |
for commit in $(git rev-list --first-parent origin/${{ github.event.pull_request.base.ref }}..${{ github.event.pull_request.head.sha }}); do
committer=$(git show --format=$'%ce' -s $commit)
echo "Committer: $committer"
if [[ "$committer" != "web3-bot@users.noreply.github.com" ]]; then
echo "Commit $commit wasn't committed by web3-bot, but by $committer."
echo "::set-output name=status::false"
exit
fi
done
echo "::set-output name=status::true"
automerge:
needs: automerge-check
runs-on: ubuntu-latest
# The check for the user is redundant here, as this job depends on the automerge-check job,
# but it prevents this job from spinning up, just to be skipped shortly after.
if: github.event.pull_request.user.login == 'web3-bot' && needs.automerge-check.outputs.status == 'true'
steps:
- name: Wait on tests
uses: lewagon/wait-on-check-action@bafe56a6863672c681c3cf671f5e10b20abf2eaa # v0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
running-workflow-name: 'automerge' # the name of this job
- name: Merge PR
uses: pascalgn/automerge-action@741c311a47881be9625932b0a0de1b0937aab1ae # v0.13.1
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_LABELS: ""
MERGE_METHOD: "squash"
MERGE_DELETE_BRANCH: true

74
.github/workflows/go-check.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
name: Go Checks
jobs:
unit:
runs-on: ubuntu-latest
name: All
env:
RUNGOGENERATE: false
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-go@v2
with:
go-version: "1.17.x"
- name: Run repo-specific setup
uses: ./.github/actions/go-check-setup
if: hashFiles('./.github/actions/go-check-setup') != ''
- name: Read config
if: hashFiles('./.github/workflows/go-check-config.json') != ''
run: |
if jq -re .gogenerate ./.github/workflows/go-check-config.json; then
echo "RUNGOGENERATE=true" >> $GITHUB_ENV
fi
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@df71e5d0e0ed317ebf43e6e59cf919430fa4b8f2 # 2021.1.1 (v0.2.1)
- name: Check that go.mod is tidy
uses: protocol/multiple-go-modules@v1.2
with:
run: |
go mod tidy
if [[ -n $(git ls-files --other --exclude-standard --directory -- go.sum) ]]; then
echo "go.sum was added by go mod tidy"
exit 1
fi
git diff --exit-code -- go.sum go.mod
- name: gofmt
if: ${{ success() || failure() }} # run this step even if the previous one failed
run: |
out=$(gofmt -s -l .)
if [[ -n "$out" ]]; then
echo $out | awk '{print "::error file=" $0 ",line=0,col=0::File is not gofmt-ed."}'
exit 1
fi
- name: go vet
if: ${{ success() || failure() }} # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2
with:
run: go vet ./...
- name: staticcheck
if: ${{ success() || failure() }} # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2
with:
run: |
set -o pipefail
staticcheck ./... | sed -e 's@\(.*\)\.go@./\1.go@g'
- name: go generate
uses: protocol/multiple-go-modules@v1.2
if: (success() || failure()) && env.RUNGOGENERATE == 'true'
with:
run: |
git clean -fd # make sure there aren't untracked files / directories
go generate ./...
# check if go generate modified or added any files
if ! $(git add . && git diff-index HEAD --exit-code --quiet); then
echo "go generated caused changes to the repository:"
git status --short
exit 1
fi

55
.github/workflows/go-test.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
name: Go Test
jobs:
unit:
strategy:
fail-fast: false
matrix:
os: [ "ubuntu", "windows", "macos" ]
go: [ "1.16.x", "1.17.x" ]
env:
COVERAGES: ""
runs-on: ${{ matrix.os }}-latest
name: ${{ matrix.os}} (go ${{ matrix.go }})
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Go information
run: |
go version
go env
- name: Run repo-specific setup
uses: ./.github/actions/go-test-setup
if: hashFiles('./.github/actions/go-test-setup') != ''
- name: Run tests
uses: protocol/multiple-go-modules@v1.2
with:
run: go test -v -coverprofile module-coverage.txt ./...
- name: Run tests (32 bit)
if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.
uses: protocol/multiple-go-modules@v1.2
env:
GOARCH: 386
with:
run: go test -v ./...
- name: Run tests with race detector
if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow
uses: protocol/multiple-go-modules@v1.2
with:
run: go test -v -race ./...
- name: Collect coverage files
shell: bash
run: echo "COVERAGES=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_ENV
- name: Upload coverage to Codecov
uses: codecov/codecov-action@51d810878be5422784e86451c0e7c14e5860ec47 # v2.0.2
with:
files: '${{ env.COVERAGES }}'
env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }}

View File

@@ -1 +0,0 @@
0.7.8: QmXm6Di2T89xAeprno3x6nD6jRgtMm2wRy25DX4zc4gk8V

View File

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

View File

@@ -1,16 +1,5 @@
all: deps
gx:
go get github.com/whyrusleeping/gx
go get github.com/whyrusleeping/gx-go
covertools:
deps:
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

View File

@@ -4,14 +4,19 @@ 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.
> 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.
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.
## Lead Maintainer
[Eric Myhre](https://github.com/warpfork)
## Table of Contents
@@ -23,29 +28,36 @@ It is used in go-ipfs and related packages to refer to a typed hunk of data.
## Install
`go-cid` is a standard Go module which can be installed with:
```sh
make deps
go get github.com/ipfs/go-cid
```
## Examples
## Usage
To use CIDs, import it in your code like:
```go
import "github.com/ipfs/go-cid"
### Running tests
Run tests with `go test` from the directory root
```sh
go test
```
Then, depending on how you want to use it, something like one of the following examples should work:
### Examples
#### Parsing string input from users
### Parsing string input from users
```go
// Create a cid from a marshaled string
c, err := cid.Decode("zdvgqEMYmNeH5fKciougvQcfzMcNjF3Z1tPouJ8C7pc3pe63k")
c, err := cid.Decode("bafzbeigai3eoy2ccc7ybwjfz5r3rdxqrinwi4rwytly24tdbh6yk7zslrm")
if err != nil {...}
fmt.Println("Got CID: ", c)
```
### Creating a CID from scratch
#### Creating a CID from scratch
```go
// Create a cid manually by specifying the 'prefix' parameters
pref := cid.Prefix{
@@ -62,7 +74,8 @@ if err != nil {...}
fmt.Println("Created CID: ", c)
```
### Check if two CIDs match
#### 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) {
@@ -70,7 +83,8 @@ if c1.Equals(c2) {
}
```
### Check if some data matches a given CID
#### 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:

168
_rsrch/cidiface/README.md Normal file
View File

@@ -0,0 +1,168 @@
What golang Kinds work best to implement CIDs?
==============================================
There are many possible ways to implement CIDs. This package explores them.
### criteria
There's a couple different criteria to consider:
- We want the best performance when operating on the type (getters, mostly);
- We want to minimize the number of memory allocations we need;
- We want types which can be used as map keys, because this is common.
The priority of these criteria is open to argument, but it's probably
mapkeys > minalloc > anythingelse.
(Mapkeys and minalloc are also quite entangled, since if we don't pick a
representation that can work natively as a map key, we'll end up needing
a `KeyRepr()` method which gives us something that does work as a map key,
an that will almost certainly involve a malloc itself.)
### options
There are quite a few different ways to go:
- Option A: CIDs as a struct; multihash as bytes.
- Option B: CIDs as a string.
- Option C: CIDs as an interface with multiple implementors.
- Option D: CIDs as a struct; multihash also as a struct or string.
- Option E: CIDs as a struct; content as strings plus offsets.
- Option F: CIDs as a struct wrapping only a string.
The current approach on the master branch is Option A.
Option D is distinctive from Option A because multihash as bytes transitively
causes the CID struct to be non-comparible and thus not suitable for map keys
as per https://golang.org/ref/spec#KeyType . (It's also a bit more work to
pursue Option D because it's just a bigger splash radius of change; but also,
something we might also want to do soon, because we *do* also have these same
map-key-usability concerns with multihash alone.)
Option E is distinctive from Option D because Option E would always maintain
the binary format of the cid internally, and so could yield it again without
malloc, while still potentially having faster access to components than
Option B since it wouldn't need to re-parse varints to access later fields.
Option F is actually a varation of Option B; it's distinctive from the other
struct options because it is proposing *literally* `struct{ x string }` as
the type, with no additional fields for components nor offsets.
Option C is the avoid-choices choice, but note that interfaces are not free;
since "minimize mallocs" is one of our major goals, we cannot use interfaces
whimsically.
Note there is no proposal for migrating to `type Cid []bytes`, because that
is generally considered to be strictly inferior to `type Cid string`.
Discoveries
-----------
### using interfaces as map keys forgoes a lot of safety checks
Using interfaces as map keys pushes a bunch of type checking to runtime.
E.g., it's totally valid at compile time to push a type which is non-comparable
into a map key; it will panic at *runtime* instead of failing at compile-time.
There's also no way to define equality checks between implementors of the
interface: golang will always use its innate concept of comparison for the
concrete types. This means its effectively *never safe* to use two different
concrete implementations of an interface in the same map; you may add elements
which are semantically "equal" in your mind, and end up very confused later
when both impls of the same "equal" object have been stored.
### sentinel values are possible in any impl, but some are clearer than others
When using `*Cid`, the nil value is a clear sentinel for 'invalid';
when using `type Cid string`, the zero value is a clear sentinel;
when using `type Cid struct` per Option A or D... the only valid check is
for a nil multihash field, since version=0 and codec=0 are both valid values.
When using `type Cid struct{string}` per Option F, zero is a clear sentinel.
### usability as a map key is important
We already covered this in the criteria section, but for clarity:
- Option A: ❌
- Option B: ✔
- Option C: ~ (caveats, and depends on concrete impl)
- Option D: ✔
- Option E: ✔
- Option F: ✔
### living without offsets requires parsing
Since CID (and multihash!) are defined using varints, they require parsing;
we can't just jump into the string at a known offset in order to yield e.g.
the multicodec number.
In order to get to the 'meat' of the CID (the multihash content), we first
must parse:
- the CID version varint;
- the multicodec varint;
- the multihash type enum varint;
- and the multihash length varint.
Since there are many applications where we want to jump straight to the
multihash content (for example, when doing CAS sharding -- see the
[disclaimer](https://github.com/multiformats/multihash#disclaimers) about
bias in leading bytes), this overhead may be interesting.
How much this overhead is significant is hard to say from microbenchmarking;
it depends largely on usage patterns. If these traversals are a significant
timesink, it would be an argument for Option D/E.
If these traversals are *not* a significant timesink, we might be wiser
to keep to Option B/F, because keeping a struct full of offsets will add several
words of memory usage per CID, and we keep a *lot* of CIDs.
### interfaces cause boxing which is a significant performance cost
See `BenchmarkCidMap_CidStr` and friends.
Long story short: using interfaces *anywhere* will cause the compiler to
implicitly generate boxing and unboxing code (e.g. `runtime.convT2E`);
this is both another function call, and more concerningly, results in
large numbers of unbatchable memory allocations.
Numbers without context are dangerous, but if you need one: 33%.
It's a big deal.
This means attempts to "use interfaces, but switch to concrete impls when
performance is important" are a red herring: it doesn't work that way.
This is not a general inditement against using interfaces -- but
if a situation is at the scale where it's become important to mind whether
or not pointers are a performance impact, then that situation also
is one where you have to think twice before using interfaces.
### struct wrappers can be used in place of typedefs with zero overhead
See `TestSizeOf`.
Using the `unsafe.Sizeof` feature to inspect what the Go runtime thinks,
we can see that `type Foo string` and `type Foo struct{x string}` consume
precisely the same amount of memory.
This is interesting because it means we can choose between either
type definition with no significant overhead anywhere we use it:
thus, we can choose freely between Option B and Option F based on which
we feel is more pleasant to work with.
Option F (a struct wrapper) means we can prevent casting into our Cid type.
Option B (typedef string) can be declared a `const`.
Are there any other concerns that would separate the two choices?
### one way or another: let's get rid of that star
We should switch completely to handling `Cid` and remove `*Cid` completely.
Regardless of whether we do this by migrating to interface, or string
implementations, or simply structs with no pointers... once we get there,
refactoring to any of the *others* can become a no-op from the perspective
of any downstream code that uses CIDs.
(This means all access via functions, never references to fields -- even if
we were to use a struct implementation. *Pretend* there's a interface,
in other words.)
There are probably `gofix` incantations which can help us with this migration.

48
_rsrch/cidiface/cid.go Normal file
View File

@@ -0,0 +1,48 @@
package cid
import (
mh "github.com/multiformats/go-multihash"
)
// Cid represents a self-describing content adressed identifier.
//
// A CID is composed of:
//
// - a Version of the CID itself,
// - a Multicodec (indicates the encoding of the referenced content),
// - and a Multihash (which identifies the referenced content).
//
// (Note that the Multihash further contains its own version and hash type
// indicators.)
type Cid interface {
// n.b. 'yields' means "without copy", 'produces' means a malloc.
Version() uint64 // Yields the version prefix as a uint.
Multicodec() uint64 // Yields the multicodec as a uint.
Multihash() mh.Multihash // Yields the multihash segment.
String() string // Produces the CID formatted as b58 string.
Bytes() []byte // Produces the CID formatted as raw binary.
Prefix() Prefix // Produces a tuple of non-content metadata.
// some change notes:
// - `KeyString() CidString` is gone because we're natively a map key now, you're welcome.
// - `StringOfBase(mbase.Encoding) (string, error)` is skipped, maybe it can come back but maybe it should be a formatter's job.
// - `Equals(o Cid) bool` is gone because it's now `==`, you're welcome.
// TODO: make a multi-return method for {v,mc,mh} decomposition. CidStr will be able to implement this more efficiently than if one makes a series of the individual getter calls.
}
// 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 uint64
MhLength int
}

View File

@@ -0,0 +1,71 @@
package cid
import (
"testing"
)
// BenchmarkCidMap_CidStr estimates how fast it is to insert primitives into a map
// keyed by CidStr (concretely).
//
// We do 100 insertions per benchmark run to make sure the map initialization
// doesn't dominate the results.
//
// Sample results on linux amd64 go1.11beta:
//
// BenchmarkCidMap_CidStr-8 100000 16317 ns/op
// BenchmarkCidMap_CidIface-8 100000 20516 ns/op
//
// With benchmem on:
//
// 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_StrPlusHax-8 200000 10451 ns/op 7589 B/op 202 allocs/op
//
// 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
// the implied `runtime.convT2E` calls cause another malloc each.
//
// There are also significant allocations in both cases because
// A) we cannot create a multihash without allocations since they are []byte;
// B) the map has to be grown several times;
// C) something I haven't quite put my finger on yet.
// Ideally we'd drive those down further as well.
//
// Pre-allocating the map reduces allocs by a very small percentage by *count*,
// but reduces the time taken by 66% overall (presumably because when a map
// 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
// whether or not interfaces are a good idea; just for contextualizing.
//
func BenchmarkCidMap_CidStr(b *testing.B) {
for i := 0; i < b.N; i++ {
mp := map[CidStr]int{}
for x := 0; x < 100; x++ {
mp[NewCidStr(0, uint64(x), []byte{})] = x
}
}
}
// BenchmarkCidMap_CidIface is in the family of BenchmarkCidMap_CidStr:
// it is identical except the map key type is declared as an interface
// (which forces all insertions to be boxed, changing performance).
func BenchmarkCidMap_CidIface(b *testing.B) {
for i := 0; i < b.N; i++ {
mp := map[Cid]int{}
for x := 0; x < 100; x++ {
mp[NewCidStr(0, uint64(x), []byte{})] = x
}
}
}
// BenchmarkCidMap_CidStrAvoidMapGrowth is in the family of BenchmarkCidMap_CidStr:
// it is identical except the map is created with a size hint that removes
// some allocations (5, in practice, apparently).
func BenchmarkCidMap_CidStrAvoidMapGrowth(b *testing.B) {
for i := 0; i < b.N; i++ {
mp := make(map[CidStr]int, 100)
for x := 0; x < 100; x++ {
mp[NewCidStr(0, uint64(x), []byte{})] = x
}
}
}

View File

@@ -0,0 +1,161 @@
package cid
import (
"encoding/binary"
"fmt"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
//=================
// def & accessors
//=================
var _ Cid = CidStr("")
var _ map[CidStr]struct{} = nil
// CidStr is a representation of a Cid as a string type containing binary.
//
// Using golang's string type is preferable over byte slices even for binary
// data because golang strings are immutable, usable as map keys,
// trivially comparable with built-in equals operators, etc.
//
// Please do not cast strings or bytes into the CidStr type directly;
// use a parse method which validates the data and yields a CidStr.
type CidStr string
// EmptyCidStr is a constant for a zero/uninitialized/sentinelvalue cid;
// it is declared mainly for readability in checks for sentinel values.
const EmptyCidStr = CidStr("")
func (c CidStr) Version() uint64 {
bytes := []byte(c)
v, _ := binary.Uvarint(bytes)
return v
}
func (c CidStr) Multicodec() uint64 {
bytes := []byte(c)
_, n := binary.Uvarint(bytes) // skip version length
codec, _ := binary.Uvarint(bytes[n:])
return codec
}
func (c CidStr) Multihash() mh.Multihash {
bytes := []byte(c)
_, n1 := binary.Uvarint(bytes) // skip version length
_, n2 := binary.Uvarint(bytes[n1:]) // skip codec length
return mh.Multihash(bytes[n1+n2:]) // return slice of remainder
}
// String returns the default string representation of a Cid.
// Currently, Base58 is used as the encoding for the multibase string.
func (c CidStr) String() string {
switch c.Version() {
case 0:
return c.Multihash().B58String()
case 1:
mbstr, err := mbase.Encode(mbase.Base58BTC, []byte(c))
if err != nil {
panic("should not error with hardcoded mbase: " + err.Error())
}
return mbstr
default:
panic("not possible to reach this point")
}
}
// Bytes produces a raw binary format of the CID.
//
// (For CidStr, this method is only distinct from casting because of
// compatibility with v0 CIDs.)
func (c CidStr) Bytes() []byte {
switch c.Version() {
case 0:
return c.Multihash()
case 1:
return []byte(c)
default:
panic("not possible to reach this point")
}
}
// Prefix builds and returns a Prefix out of a Cid.
func (c CidStr) Prefix() Prefix {
dec, _ := mh.Decode(c.Multihash()) // assuming we got a valid multiaddr, this will not error
return Prefix{
MhType: dec.Code,
MhLength: dec.Length,
Version: c.Version(),
Codec: c.Multicodec(),
}
}
//==================================
// parsers & validators & factories
//==================================
func NewCidStr(version uint64, codecType uint64, mhash mh.Multihash) CidStr {
hashlen := len(mhash)
// two 8 bytes (max) numbers plus hash
buf := make([]byte, 2*binary.MaxVarintLen64+hashlen)
n := binary.PutUvarint(buf, version)
n += binary.PutUvarint(buf[n:], codecType)
cn := copy(buf[n:], mhash)
if cn != hashlen {
panic("copy hash length is inconsistent")
}
return CidStr(buf[:n+hashlen])
}
// CidStrParse takes a binary byte slice, parses it, and returns either
// a valid CidStr, or the zero CidStr and an error.
//
// For CidV1, the data buffer is in the form:
//
// <version><codec-type><multihash>
//
// CidV0 are also supported. In particular, data buffers starting
// with length 34 bytes, which starts with bytes [18,32...] are considered
// binary multihashes.
//
// The multicodec bytes are not parsed to verify they're a valid varint;
// no further reification is performed.
//
// Multibase encoding should already have been unwrapped before parsing;
// if you have a multibase-enveloped string, use CidStrDecode instead.
//
// CidStrParse is the inverse of Cid.Bytes().
func CidStrParse(data []byte) (CidStr, error) {
if len(data) == 34 && data[0] == 18 && data[1] == 32 {
h, err := mh.Cast(data)
if err != nil {
return EmptyCidStr, err
}
return NewCidStr(0, DagProtobuf, h), nil
}
vers, n := binary.Uvarint(data)
if err := uvError(n); err != nil {
return EmptyCidStr, err
}
if vers != 0 && vers != 1 {
return EmptyCidStr, fmt.Errorf("invalid cid version number: %d", vers)
}
_, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return EmptyCidStr, err
}
rest := data[n+cn:]
h, err := mh.Cast(rest)
if err != nil {
return EmptyCidStr, err
}
// REVIEW: if the data is longer than the mh.len expects, we silently ignore it? should we?
return CidStr(data[0 : n+cn+len(h)]), nil
}

View File

@@ -0,0 +1,164 @@
package cid
import (
"encoding/binary"
"fmt"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
//=================
// def & accessors
//=================
var _ Cid = CidStruct{}
//var _ map[CidStruct]struct{} = nil // Will not compile! See struct def docs.
//var _ map[Cid]struct{} = map[Cid]struct{}{CidStruct{}: struct{}{}} // Legal to compile...
// but you'll get panics: "runtime error: hash of unhashable type cid.CidStruct"
// CidStruct represents a CID in a struct format.
//
// This format complies with the exact same Cid interface as the CidStr
// implementation, but completely pre-parses the Cid metadata.
// CidStruct is a tad quicker in case of repeatedly accessed fields,
// but requires more reshuffling to parse and to serialize.
// CidStruct is not usable as a map key, because it contains a Multihash
// reference, which is a slice, and thus not "comparable" as a primitive.
//
// Beware of zero-valued CidStruct: it is difficult to distinguish an
// incorrectly-initialized "invalid" CidStruct from one representing a v0 cid.
type CidStruct struct {
version uint64
codec uint64
hash mh.Multihash
}
// EmptyCidStruct is a constant for a zero/uninitialized/sentinelvalue cid;
// it is declared mainly for readability in checks for sentinel values.
//
// Note: it's not actually a const; the compiler does not allow const structs.
var EmptyCidStruct = CidStruct{}
func (c CidStruct) Version() uint64 {
return c.version
}
func (c CidStruct) Multicodec() uint64 {
return c.codec
}
func (c CidStruct) Multihash() mh.Multihash {
return c.hash
}
// String returns the default string representation of a Cid.
// Currently, Base58 is used as the encoding for the multibase string.
func (c CidStruct) String() string {
switch c.Version() {
case 0:
return c.Multihash().B58String()
case 1:
mbstr, err := mbase.Encode(mbase.Base58BTC, c.Bytes())
if err != nil {
panic("should not error with hardcoded mbase: " + err.Error())
}
return mbstr
default:
panic("not possible to reach this point")
}
}
// Bytes produces a raw binary format of the CID.
func (c CidStruct) Bytes() []byte {
switch c.version {
case 0:
return []byte(c.hash)
case 1:
// two 8 bytes (max) numbers plus hash
buf := make([]byte, 2*binary.MaxVarintLen64+len(c.hash))
n := binary.PutUvarint(buf, c.version)
n += binary.PutUvarint(buf[n:], c.codec)
cn := copy(buf[n:], c.hash)
if cn != len(c.hash) {
panic("copy hash length is inconsistent")
}
return buf[:n+len(c.hash)]
default:
panic("not possible to reach this point")
}
}
// Prefix builds and returns a Prefix out of a Cid.
func (c CidStruct) Prefix() Prefix {
dec, _ := mh.Decode(c.hash) // assuming we got a valid multiaddr, this will not error
return Prefix{
MhType: dec.Code,
MhLength: dec.Length,
Version: c.version,
Codec: c.codec,
}
}
//==================================
// parsers & validators & factories
//==================================
// CidStructParse takes a binary byte slice, parses it, and returns either
// a valid CidStruct, or the zero CidStruct and an error.
//
// For CidV1, the data buffer is in the form:
//
// <version><codec-type><multihash>
//
// CidV0 are also supported. In particular, data buffers starting
// with length 34 bytes, which starts with bytes [18,32...] are considered
// binary multihashes.
//
// The multicodec bytes are not parsed to verify they're a valid varint;
// no further reification is performed.
//
// Multibase encoding should already have been unwrapped before parsing;
// if you have a multibase-enveloped string, use CidStructDecode instead.
//
// CidStructParse is the inverse of Cid.Bytes().
func CidStructParse(data []byte) (CidStruct, error) {
if len(data) == 34 && data[0] == 18 && data[1] == 32 {
h, err := mh.Cast(data)
if err != nil {
return EmptyCidStruct, err
}
return CidStruct{
codec: DagProtobuf,
version: 0,
hash: h,
}, nil
}
vers, n := binary.Uvarint(data)
if err := uvError(n); err != nil {
return EmptyCidStruct, err
}
if vers != 0 && vers != 1 {
return EmptyCidStruct, fmt.Errorf("invalid cid version number: %d", vers)
}
codec, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return EmptyCidStruct, err
}
rest := data[n+cn:]
h, err := mh.Cast(rest)
if err != nil {
return EmptyCidStruct, err
}
return CidStruct{
version: vers,
codec: codec,
hash: h,
}, nil
}

79
_rsrch/cidiface/enums.go Normal file
View File

@@ -0,0 +1,79 @@
package 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
Libp2pKey = 0x72
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,
"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,
}
// CodecToStr maps the numeric codec to its name
var CodecToStr = 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",
}

24
_rsrch/cidiface/errors.go Normal file
View File

@@ -0,0 +1,24 @@
package cid
import (
"errors"
)
var (
// ErrVarintBuffSmall means that a buffer passed to the cid parser was not
// long enough, or did not contain an invalid cid
ErrVarintBuffSmall = errors.New("reading varint: buffer too small")
// ErrVarintTooBig means that the varint in the given cid was above the
// limit of 2^64
ErrVarintTooBig = errors.New("reading varint: varint bigger than 64bits" +
" and not supported")
// ErrCidTooShort means that the cid passed to decode was not long
// enough to be a valid Cid
ErrCidTooShort = errors.New("cid too short")
// ErrInvalidEncoding means that selected encoding is not supported
// by this Cid version
ErrInvalidEncoding = errors.New("invalid base encoding")
)

12
_rsrch/cidiface/misc.go Normal file
View File

@@ -0,0 +1,12 @@
package cid
func uvError(read int) error {
switch {
case read == 0:
return ErrVarintBuffSmall
case read < 0:
return ErrVarintTooBig
default:
return nil
}
}

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 Undef, err
}
return Cid{string(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 Undef, 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
}

112
builder_test.go Normal file
View File

@@ -0,0 +1,112 @@
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("Prefix-NoChange", func(t *testing.T) {
p := Prefix{Version: 0, Codec: DagProtobuf, MhType: mh.SHA2_256, MhLength: mh.DefaultLengths[mh.SHA2_256]}
if p.GetCodec() != DagProtobuf {
t.Fatal("original builder not using Protobuf codec")
}
pn := p.WithCodec(DagProtobuf)
if pn != p {
t.Fatal("should have returned same builder")
}
})
t.Run("V0Builder", func(t *testing.T) {
testCodecChange(t, V0Builder{})
})
t.Run("V0Builder-NoChange", func(t *testing.T) {
b := V0Builder{}
if b.GetCodec() != DagProtobuf {
t.Fatal("original builder not using Protobuf codec")
}
bn := b.WithCodec(DagProtobuf)
if bn != b {
t.Fatal("should have returned same builder")
}
})
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")
}
}

799
cid.go
View File

@@ -1,55 +1,225 @@
// 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"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
varint "github.com/multiformats/go-varint"
)
// UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>"
var (
// 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")
)
// 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
Libp2pKey = 0x72
EthereumBlock = 0x90
EthereumTx = 0x91
BitcoinBlock = 0xb0
BitcoinTx = 0xb1
ZcashBlock = 0xc0
ZcashTx = 0xc1
GitRaw = 0x78
DagJOSE = 0x85
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
DashBlock = 0xf0
DashTx = 0xf1
FilCommitmentUnsealed = 0xf101
FilCommitmentSealed = 0xf102
)
func NewCidV0(h mh.Multihash) *Cid {
return &Cid{
version: 0,
codec: DagProtobuf,
hash: h,
// 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
// error on failure.
func tryNewCidV0(mhash mh.Multihash) (Cid, error) {
// Need to make sure hash is valid for CidV0 otherwise we will
// incorrectly detect it as CidV1 in the Version() method
dec, err := mh.Decode(mhash)
if err != nil {
return Undef, err
}
}
func NewCidV1(c uint64, h mh.Multihash) *Cid {
return &Cid{
version: 1,
codec: c,
hash: h,
if dec.Code != mh.SHA2_256 || dec.Length != 32 {
return Undef, fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length)
}
return Cid{string(mhash)}, nil
}
type Cid struct {
version uint64
codec uint64
hash mh.Multihash
// 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.
//
// Panics if the multihash isn't sha2-256.
func NewCidV0(mhash mh.Multihash) Cid {
c, err := tryNewCidV0(mhash)
if err != nil {
panic(err)
}
return c
}
func Parse(v interface{}) (*Cid, error) {
// NewCidV1 returns a new Cid using the given multicodec-packed
// content type.
//
// Panics if the multihash is invalid.
func NewCidV1(codecType uint64, mhash mh.Multihash) Cid {
hashlen := len(mhash)
// Two 8 bytes (max) numbers plus hash.
// We use strings.Builder to only allocate once.
var b strings.Builder
b.Grow(1 + varint.UvarintSize(codecType) + hashlen)
b.WriteByte(1)
var buf [binary.MaxVarintLen64]byte
n := varint.PutUvarint(buf[:], codecType)
b.Write(buf[:n])
cn, _ := b.Write(mhash)
if cn != hashlen {
panic("copy hash length is inconsistent")
}
return Cid{b.String()}
}
var (
_ encoding.BinaryMarshaler = Cid{}
_ encoding.BinaryUnmarshaler = (*Cid)(nil)
_ encoding.TextMarshaler = Cid{}
_ encoding.TextUnmarshaler = (*Cid)(nil)
)
// Cid represents a self-describing content addressed
// identifier. It is formed by a Version, a Codec (which indicates
// a multicodec-packed content type) and a Multihash.
type Cid struct{ str string }
// Undef can be used to represent a nil or undefined Cid, using Cid{}
// directly is also acceptable.
var Undef = Cid{}
// Defined returns true if a Cid is defined
// Calling any other methods on an undefined Cid will result in
// undefined behavior.
func (c Cid) Defined() bool {
return c.str != ""
}
// 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/") {
@@ -59,104 +229,143 @@ func Parse(v interface{}) (*Cid, error) {
case []byte:
return Cast(v2)
case mh.Multihash:
return NewCidV0(v2), nil
case *Cid:
return tryNewCidV0(v2)
case Cid:
return v2, nil
default:
return nil, fmt.Errorf("can't parse %+v as Cid", v2)
return Undef, fmt.Errorf("can't parse %+v as Cid", v2)
}
}
func Decode(v string) (*Cid, error) {
// Decode parses a Cid-encoded string and returns a Cid object.
// For CidV1, a Cid-encoded string is primarily a multibase string:
//
// <multibase-type-code><base-encoded-string>
//
// The base-encoded string represents a:
//
// <version><codec-type><multihash>
//
// Decode will also detect and parse CidV0 strings. Strings
// starting with "Qm" are considered CidV0 and treated directly
// as B58-encoded multihashes.
func Decode(v string) (Cid, error) {
if len(v) < 2 {
return nil, fmt.Errorf("cid too short")
return Undef, ErrCidTooShort
}
if len(v) == 46 && v[:2] == "Qm" {
hash, err := mh.FromB58String(v)
if err != nil {
return nil, err
return Undef, err
}
return NewCidV0(hash), nil
return tryNewCidV0(hash)
}
_, data, err := mbase.Decode(v)
if err != nil {
return nil, err
return Undef, err
}
return Cast(data)
}
var (
ErrVarintBuffSmall = errors.New("reading varint: buffer to small")
ErrVarintTooBig = errors.New("reading varint: varint bigger than 64bits" +
" and not supported")
)
func uvError(read int) error {
switch {
case read == 0:
return ErrVarintBuffSmall
case read < 0:
return ErrVarintTooBig
default:
return nil
}
}
func Cast(data []byte) (*Cid, error) {
if len(data) == 34 && data[0] == 18 && data[1] == 32 {
h, err := mh.Cast(data)
if err != nil {
return nil, err
}
return &Cid{
codec: DagProtobuf,
version: 0,
hash: h,
}, nil
// 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
}
vers, n := binary.Uvarint(data)
if err := uvError(n); err != nil {
return nil, err
if len(v) == 46 && v[:2] == "Qm" {
return mbase.Base58BTC, nil
}
if vers != 0 && vers != 1 {
return nil, fmt.Errorf("invalid cid version number: %d", vers)
}
encoding := mbase.Encoding(v[0])
codec, cn := binary.Uvarint(data[n:])
if err := uvError(cn); err != nil {
return nil, err
}
rest := data[n+cn:]
h, err := mh.Cast(rest)
// check encoding is valid
_, err := mbase.NewEncoder(encoding)
if err != nil {
return nil, err
return -1, err
}
return &Cid{
version: vers,
codec: codec,
hash: h,
}, nil
return encoding, nil
}
func (c *Cid) Type() uint64 {
return c.codec
// 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) {
nr, c, err := CidFromBytes(data)
if err != nil {
return Undef, err
}
if nr != len(data) {
return Undef, fmt.Errorf("trailing bytes in data buffer passed to cid Cast")
}
return c, nil
}
func (c *Cid) String() string {
switch c.version {
// UnmarshalBinary is equivalent to Cast(). It implements the
// encoding.BinaryUnmarshaler interface.
func (c *Cid) UnmarshalBinary(data []byte) error {
casted, err := Cast(data)
if err != nil {
return err
}
c.str = casted.str
return nil
}
// UnmarshalText is equivalent to Decode(). It implements the
// encoding.TextUnmarshaler interface.
func (c *Cid) UnmarshalText(text []byte) error {
decodedCid, err := Decode(string(text))
if err != nil {
return err
}
c.str = decodedCid.str
return nil
}
// Version returns the Cid version.
func (c Cid) Version() uint64 {
if len(c.str) == 34 && c.str[0] == 18 && c.str[1] == 32 {
return 0
}
return 1
}
// Type returns the multicodec-packed content type of a Cid.
func (c Cid) Type() uint64 {
if c.Version() == 0 {
return DagProtobuf
}
_, n, _ := uvarint(c.str)
codec, _, _ := uvarint(c.str[n:])
return codec
}
// String returns the default string representation of a
// Cid. Currently, Base32 is used for CIDV1 as the encoding for the
// multibase string, Base58 is used for CIDV0.
func (c Cid) String() string {
switch c.Version() {
case 0:
return c.hash.B58String()
return c.Hash().B58String()
case 1:
mbstr, err := mbase.Encode(mbase.Base58BTC, c.bytesV1())
mbstr, err := mbase.Encode(mbase.Base32, c.Bytes())
if err != nil {
panic("should not error with hardcoded mbase: " + err.Error())
}
@@ -167,95 +376,218 @@ func (c *Cid) String() string {
}
}
func (c *Cid) Hash() mh.Multihash {
return c.hash
}
func (c *Cid) Bytes() []byte {
switch c.version {
// 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:
return c.bytesV0()
if base != mbase.Base58BTC {
return "", ErrInvalidEncoding
}
return c.Hash().B58String(), nil
case 1:
return c.bytesV1()
return mbase.Encode(base, c.Bytes())
default:
panic("not possible to reach this point")
}
}
func (c *Cid) bytesV0() []byte {
return []byte(c.hash)
// Encode return the string representation of a Cid in a given base
// when applicable. Version 0 Cid's are always in Base58 as they do
// not take a multibase prefix.
func (c Cid) Encode(base mbase.Encoder) string {
switch c.Version() {
case 0:
return c.Hash().B58String()
case 1:
return base.Encode(c.Bytes())
default:
panic("not possible to reach this point")
}
}
func (c *Cid) bytesV1() []byte {
// two 8 bytes (max) numbers plus hash
buf := make([]byte, 2*binary.MaxVarintLen64+len(c.hash))
n := binary.PutUvarint(buf, c.version)
n += binary.PutUvarint(buf[n:], c.codec)
cn := copy(buf[n:], c.hash)
if cn != len(c.hash) {
panic("copy hash length is inconsistent")
// Hash returns the multihash contained by a Cid.
func (c Cid) Hash() mh.Multihash {
bytes := c.Bytes()
if c.Version() == 0 {
return mh.Multihash(bytes)
}
return buf[:n+len(c.hash)]
// skip version length
_, n1, _ := varint.FromUvarint(bytes)
// skip codec length
_, n2, _ := varint.FromUvarint(bytes[n1:])
return mh.Multihash(bytes[n1+n2:])
}
func (c *Cid) Equals(o *Cid) bool {
return c.codec == o.codec &&
c.version == o.version &&
bytes.Equal(c.hash, o.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 {
return []byte(c.str)
}
// ByteLen returns the length of the CID in bytes.
// It's equivalent to `len(c.Bytes())`, but works without an allocation,
// and should therefore be preferred.
//
// (See also the WriteTo method for other important operations that work without allocation.)
func (c Cid) ByteLen() int {
return len(c.str)
}
// WriteBytes writes the CID bytes to the given writer.
// This method works without incurring any allocation.
//
// (See also the ByteLen method for other important operations that work without allocation.)
func (c Cid) WriteBytes(w io.Writer) (int, error) {
n, err := io.WriteString(w, c.str)
if err != nil {
return n, err
}
if n != len(c.str) {
return n, fmt.Errorf("failed to write entire cid string")
}
return n, nil
}
// MarshalBinary is equivalent to Bytes(). It implements the
// encoding.BinaryMarshaler interface.
func (c Cid) MarshalBinary() ([]byte, error) {
return c.Bytes(), nil
}
// MarshalText is equivalent to String(). It implements the
// encoding.TextMarshaler interface.
func (c Cid) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
// 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 == o
}
// 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:"/"`
}{}
objptr := &obj
err := json.Unmarshal(b, &objptr)
if err != nil {
return err
}
if objptr == nil {
*c = Cid{}
return nil
}
if obj.CidTarget == "" {
return fmt.Errorf("cid was incorrectly formatted")
}
out, err := Decode(obj.CidTarget)
if err != nil {
return err
}
c.version = out.version
c.hash = out.hash
c.codec = out.codec
*c = out
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) {
if !c.Defined() {
return []byte("null"), nil
}
return []byte(fmt.Sprintf("{\"/\":\"%s\"}", c.String())), nil
}
func (c *Cid) KeyString() string {
return string(c.Bytes())
// KeyString returns the binary representation of the Cid as a string
func (c Cid) KeyString() string {
return c.str
}
func (c *Cid) Loggable() map[string]interface{} {
// 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,
}
}
func (c *Cid) Prefix() Prefix {
dec, _ := mh.Decode(c.hash) // assuming we got a valid multiaddr, this will not error
// Prefix builds and returns a Prefix out of a Cid.
func (c Cid) Prefix() Prefix {
if c.Version() == 0 {
return Prefix{
MhType: mh.SHA2_256,
MhLength: 32,
Version: 0,
Codec: DagProtobuf,
}
}
offset := 0
version, n, _ := uvarint(c.str[offset:])
offset += n
codec, n, _ := uvarint(c.str[offset:])
offset += n
mhtype, n, _ := uvarint(c.str[offset:])
offset += n
mhlen, _, _ := uvarint(c.str[offset:])
return Prefix{
MhType: dec.Code,
MhLength: dec.Length,
Version: c.version,
Codec: c.codec,
MhType: mhtype,
MhLength: int(mhlen),
Version: version,
Codec: codec,
}
}
// 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
}
func (p Prefix) Sum(data []byte) (*Cid, error) {
hash, err := mh.Sum(data, p.MhType, p.MhLength)
// 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) {
length := p.MhLength
if p.MhType == mh.IDENTITY {
length = -1
}
if p.Version == 0 && (p.MhType != mh.SHA2_256 ||
(p.MhLength != 32 && p.MhLength != -1)) {
return Undef, fmt.Errorf("invalid v0 prefix")
}
hash, err := mh.Sum(data, p.MhType, length)
if err != nil {
return nil, err
return Undef, err
}
switch p.Version {
@@ -264,37 +596,50 @@ func (p Prefix) Sum(data []byte) (*Cid, error) {
case 1:
return NewCidV1(p.Codec, hash), nil
default:
return nil, fmt.Errorf("invalid cid version")
return Undef, fmt.Errorf("invalid cid version")
}
}
// Bytes returns a byte representation of a Prefix. It looks like:
//
// <version><codec><mh-type><mh-length>
func (p Prefix) Bytes() []byte {
buf := make([]byte, 4*binary.MaxVarintLen64)
n := binary.PutUvarint(buf, p.Version)
n += binary.PutUvarint(buf[n:], p.Codec)
n += binary.PutUvarint(buf[n:], uint64(p.MhType))
n += binary.PutUvarint(buf[n:], uint64(p.MhLength))
return buf[:n]
size := varint.UvarintSize(p.Version)
size += varint.UvarintSize(p.Codec)
size += varint.UvarintSize(p.MhType)
size += varint.UvarintSize(uint64(p.MhLength))
buf := make([]byte, size)
n := varint.PutUvarint(buf, p.Version)
n += varint.PutUvarint(buf[n:], p.Codec)
n += varint.PutUvarint(buf[n:], p.MhType)
n += varint.PutUvarint(buf[n:], uint64(p.MhLength))
if n != size {
panic("size mismatch")
}
return buf
}
// PrefixFromBytes parses a Prefix-byte representation onto a
// Prefix.
func PrefixFromBytes(buf []byte) (Prefix, error) {
r := bytes.NewReader(buf)
vers, err := binary.ReadUvarint(r)
vers, err := varint.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
codec, err := binary.ReadUvarint(r)
codec, err := varint.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
mhtype, err := binary.ReadUvarint(r)
mhtype, err := varint.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
mhlen, err := binary.ReadUvarint(r)
mhlen, err := varint.ReadUvarint(r)
if err != nil {
return Prefix{}, err
}
@@ -302,7 +647,181 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
return Prefix{
Version: vers,
Codec: codec,
MhType: int(mhtype),
MhType: mhtype,
MhLength: int(mhlen),
}, nil
}
func CidFromBytes(data []byte) (int, Cid, error) {
if len(data) > 2 && data[0] == mh.SHA2_256 && data[1] == 32 {
if len(data) < 34 {
return 0, Undef, fmt.Errorf("not enough bytes for cid v0")
}
h, err := mh.Cast(data[:34])
if err != nil {
return 0, Undef, err
}
return 34, Cid{string(h)}, nil
}
vers, n, err := varint.FromUvarint(data)
if err != nil {
return 0, Undef, err
}
if vers != 1 {
return 0, Undef, fmt.Errorf("expected 1 as the cid version number, got: %d", vers)
}
_, cn, err := varint.FromUvarint(data[n:])
if err != nil {
return 0, Undef, err
}
mhnr, _, err := mh.MHFromBytes(data[n+cn:])
if err != nil {
return 0, Undef, err
}
l := n + cn + mhnr
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.
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 {
return len(br.dst), Undef, 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, err
}
br.dst = br.dst[:34]
h, err := mh.Cast(br.dst)
if err != nil {
return len(br.dst), Undef, err
}
return len(br.dst), Cid{string(h)}, nil
}
if vers != 1 {
return len(br.dst), Undef, 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, 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, err
}
// Multihash digest length.
mhl, err := varint.ReadUvarint(br)
if err != nil {
return len(br.dst), Undef, 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, 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, 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, err
}
return len(br.dst), Cid{string(br.dst)}, nil
}

View File

@@ -1,10 +1,10 @@
//go:build gofuzz
// +build gofuzz
package cid
func Fuzz(data []byte) int {
cid, err := Cast(data)
if err != nil {
return 0
}
@@ -23,7 +23,7 @@ func Fuzz(data []byte) int {
if err != nil {
panic(err.Error())
}
cid2 := &Cid{}
cid2 := Cid{}
err = cid2.UnmarshalJSON(json)
if err != nil {
panic(err.Error())

View File

@@ -2,39 +2,119 @@ package cid
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"reflect"
"strings"
"testing"
"testing/iotest"
mbase "github.com/multiformats/go-multibase"
mh "github.com/multiformats/go-multihash"
)
func assertEqual(t *testing.T, a, b *Cid) {
if a.codec != b.codec {
// 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) {
if a.Type() != b.Type() {
t.Fatal("mismatch on type")
}
if a.version != b.version {
if a.Version() != b.Version() {
t.Fatal("mismatch on version")
}
if !bytes.Equal(a.hash, b.hash) {
if !bytes.Equal(a.Hash(), b.Hash()) {
t.Fatal("multihash mismatch")
}
}
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) {
// Test creating CIDs both manually and with Prefix.
// Tests: https://github.com/ipfs/go-cid/issues/83
for _, hashfun := range []uint64{
mh.IDENTITY, 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)
c3, err := c1.Prefix().Sum([]byte("foobar"))
if err != nil {
t.Fatal(err)
}
if !c2.Equals(c3) {
t.Fatal("expected CIDs to be equal")
}
}
}
func TestBasicMarshaling(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,
}
cid := NewCidV1(7, h)
data := cid.Bytes()
@@ -54,6 +134,144 @@ 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 := NewCidV1(7, 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)
encoder, err := mbase.NewEncoder(b)
if err != nil {
t.Fatal(err)
}
s2 := cid.Encode(encoder)
if s != s2 {
t.Fatalf("%q != %q", s, s2)
}
ee, err := ExtractEncoding(s)
if err != nil {
t.Fatal(err)
}
if ee != b {
t.Fatalf("could not properly determine base (got %v)", ee)
}
}
ee, err := ExtractEncoding("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n")
if err != nil {
t.Fatal(err)
}
if ee != mbase.Base58BTC {
t.Fatalf("expected Base58BTC from Qm string (got %v)", ee)
}
ee, err = ExtractEncoding("1")
if err == nil {
t.Fatal("expected too-short error from ExtractEncoding")
}
if ee != -1 {
t.Fatal("expected -1 from too-short ExtractEncoding")
}
}
func TestBinaryMarshaling(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
c := NewCidV1(DagCBOR, hash)
var c2 Cid
var c3 Cid
data, err := c.MarshalBinary()
if err != nil {
t.Fatal(err)
}
if err = c2.UnmarshalBinary(data); err != nil {
t.Fatal(err)
}
if !c.Equals(c2) {
t.Errorf("cids should be the same: %s %s", c, c2)
}
var buf bytes.Buffer
wrote, err := c.WriteBytes(&buf)
if err != nil {
t.Fatal(err)
}
if wrote != 36 {
t.Fatalf("expected 36 bytes written (got %d)", wrote)
}
if err = c3.UnmarshalBinary(data); err != nil {
t.Fatal(err)
}
if !c.Equals(c3) {
t.Errorf("cids should be the same: %s %s", c, c3)
}
}
func TestTextMarshaling(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
c := NewCidV1(DagCBOR, hash)
var c2 Cid
data, err := c.MarshalText()
if err != nil {
t.Fatal(err)
}
if err = c2.UnmarshalText(data); err != nil {
t.Fatal(err)
}
if !c.Equals(c2) {
t.Errorf("cids should be the same: %s %s", c, c2)
}
if c.KeyString() != string(c.Bytes()) {
t.Errorf("got unexpected KeyString() result")
}
}
func TestEmptyString(t *testing.T) {
_, err := Decode("")
if err == nil {
@@ -69,17 +287,43 @@ func TestV0Handling(t *testing.T) {
t.Fatal(err)
}
if cid.version != 0 {
if cid.Version() != 0 {
t.Fatal("should have gotten version 0 cid")
}
if cid.hash.B58String() != old {
t.Fatal("marshaling roundtrip failed")
if cid.Hash().B58String() != old {
t.Fatalf("marshaling roundtrip failed: %s != %s", cid.Hash().B58String(), old)
}
if cid.String() != old {
t.Fatal("marshaling roundtrip failed")
}
byteLen := cid.ByteLen()
if byteLen != 34 {
t.Fatalf("expected V0 ByteLen to be 34 (got %d)", byteLen)
}
new, err := cid.StringOfBase(mbase.Base58BTC)
if err != nil {
t.Fatal(err)
}
if new != old {
t.Fatal("StringOfBase roundtrip failed")
}
encoder, err := mbase.NewEncoder(mbase.Base58BTC)
if err != nil {
t.Fatal(err)
}
if cid.Encode(encoder) != old {
t.Fatal("Encode roundtrip failed")
}
_, err = cid.StringOfBase(mbase.Base32)
if err != ErrInvalidEncoding {
t.Fatalf("expected ErrInvalidEncoding for V0 StringOfBase(Base32) (got %v)", err)
}
}
func TestV0ErrorCases(t *testing.T) {
@@ -90,6 +334,105 @@ 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 TestInvalidV0Prefix(t *testing.T) {
tests := []Prefix{
{
MhType: mh.SHA2_256,
MhLength: 31,
},
{
MhType: mh.SHA2_256,
MhLength: 33,
},
{
MhType: mh.SHA2_256,
MhLength: -2,
},
{
MhType: mh.SHA2_512,
MhLength: 32,
},
{
MhType: mh.SHA2_512,
MhLength: -1,
},
}
for i, p := range tests {
t.Log(i)
_, err := p.Sum([]byte("testdata"))
if err == nil {
t.Fatalf("should error (index %d)", i)
}
}
}
func TestBadPrefix(t *testing.T) {
p := Prefix{Version: 3, Codec: DagProtobuf, MhType: mh.SHA2_256, MhLength: 3}
_, err := p.Sum([]byte{0x00, 0x01, 0x03})
if err == nil {
t.Fatalf("expected error on v3 prefix Sum")
}
}
func TestPrefixRoundtrip(t *testing.T) {
data := []byte("this is some test content")
hash, _ := mh.Sum(data, mh.SHA2_256, -1)
@@ -119,12 +462,29 @@ func TestPrefixRoundtrip(t *testing.T) {
}
}
func TestBadPrefixFromBytes(t *testing.T) {
_, err := PrefixFromBytes([]byte{0x80})
if err == nil {
t.Fatal("expected error for bad byte 0")
}
_, err = PrefixFromBytes([]byte{0x01, 0x80})
if err == nil {
t.Fatal("expected error for bad byte 1")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x80})
if err == nil {
t.Fatal("expected error for bad byte 2")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x01, 0x80})
if err == nil {
t.Fatal("expected error for bad byte 3")
}
}
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 := NewCidV1(1<<63, hash)
_ = c.Bytes()
}
@@ -153,13 +513,13 @@ func TestParse(t *testing.T) {
}
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},
{NewCidV0(h), theHash},
{NewCidV0(h).Bytes(), theHash},
{h, theHash},
{theHash, theHash},
{"/ipfs/" + theHash, theHash},
{"https://ipfs.io/ipfs/" + theHash, theHash},
{"http://localhost:8080/ipfs/" + theHash, theHash},
}
assert := func(arg interface{}, expected string) error {
@@ -167,8 +527,8 @@ func TestParse(t *testing.T) {
if err != nil {
return err
}
if cid.version != 0 {
return fmt.Errorf("expected version 0, got %s", string(cid.version))
if cid.Version() != 0 {
return fmt.Errorf("expected version 0, got %d", cid.Version())
}
actual := cid.Hash().B58String()
if actual != expected {
@@ -182,8 +542,7 @@ func TestParse(t *testing.T) {
}
for _, args := range assertions {
err := assert(args[0], args[1].(string))
if err != nil {
if err := assert(args[0], args[1].(string)); err != nil {
t.Fatal(err)
}
}
@@ -196,7 +555,261 @@ func TestHexDecode(t *testing.T) {
t.Fatal(err)
}
if c.String() != "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" {
if c.String() != "bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm" {
t.Fatal("hash value failed to round trip decoding from hex")
}
}
func ExampleDecode() {
encoded := "bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm"
c, err := Decode(encoded)
if err != nil {
fmt.Printf("Error: %s", err)
return
}
fmt.Println(c)
// Output: bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm
}
func TestFromJson(t *testing.T) {
cval := "bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm"
jsoncid := []byte(`{"/":"` + cval + `"}`)
var c Cid
if err := json.Unmarshal(jsoncid, &c); err != nil {
t.Fatal(err)
}
if c.String() != cval {
t.Fatal("json parsing failed")
}
}
func TestJsonRoundTrip(t *testing.T) {
expectedJSON := `{"/":"bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm"}`
exp, err := Decode("bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm")
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
if err = json.Unmarshal(enc, &actual); err != nil {
t.Fatal(err)
}
if !exp.Equals(actual) {
t.Fatal("cids not equal for *Cid")
}
if string(enc) != expectedJSON {
t.Fatalf("did not get expected JSON form (got %q)", string(enc))
}
// Verify it works for a Cid.
enc, err = json.Marshal(exp)
if err != nil {
t.Fatal(err)
}
var actual2 Cid
if err = json.Unmarshal(enc, &actual2); err != nil {
t.Fatal(err)
}
if !exp.Equals(actual2) {
t.Fatal("cids not equal for Cid")
}
if err = actual2.UnmarshalJSON([]byte("1")); err == nil {
t.Fatal("expected error for too-short JSON")
}
if err = actual2.UnmarshalJSON([]byte(`{"nope":"nope"}`)); err == nil {
t.Fatal("expected error for bad CID JSON")
}
if err = actual2.UnmarshalJSON([]byte(`bad "" json!`)); err == nil {
t.Fatal("expected error for bad JSON")
}
var actual3 Cid
enc, err = actual3.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(enc) != "null" {
t.Fatalf("expected 'null' string for undefined CID (got %q)", string(enc))
}
}
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.ReportAllocs()
b.ResetTimer()
count := 0
for i := 0; i < b.N; i++ {
count += len(cid.String())
}
if count != 49*b.N {
b.FailNow()
}
}
func TestReadCidsFromBuffer(t *testing.T) {
cidstr := []string{
"bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm",
"k2cwueckqkibutvhkr4p2ln2pjcaxaakpd9db0e7j7ax1lxhhxy3ekpv",
"Qmf5Qzp6nGBku7CEn2UQx4mgN8TW69YUok36DrGa6NN893",
"zb2rhZi1JR4eNc2jBGaRYJKYM8JEB4ovenym8L1CmFsRAytkz",
"bafkqarjpmzuwyzltorxxezjpkvcfgqkfjfbfcvslivje2vchkzdu6rckjjcfgtkolaze6mssjqzeyn2ekrcfatkjku2vowseky3fswkfkm2deqkrju3e2",
}
var cids []Cid
var buf []byte
for _, cs := range cidstr {
c, err := Decode(cs)
if err != nil {
t.Fatal(err)
}
cids = append(cids, c)
buf = append(buf, c.Bytes()...)
}
var cur int
for _, expc := range cids {
n, c, err := CidFromBytes(buf[cur:])
if err != nil {
t.Fatal(err)
}
if c != expc {
t.Fatal("cids mismatched")
}
cur += n
}
if cur != len(buf) {
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) {
for _, name := range []string{
"FromBytes",
"FromReader",
} {
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")
}
})
}
}
func TestBadParse(t *testing.T) {
hash, err := mh.Sum([]byte("foobar"), mh.SHA3_256, -1)
if err != nil {
t.Fatal(err)
}
_, err = Parse(hash)
if err == nil {
t.Fatal("expected to fail to parse an invalid CIDv1 CID")
}
}
func TestLoggable(t *testing.T) {
c, err := Decode("bafkreie5qrjvaw64n4tjm6hbnm7fnqvcssfed4whsjqxzslbd3jwhsk3mm")
if err != nil {
t.Fatal(err)
}
actual := c.Loggable()
expected := make(map[string]interface{})
expected["cid"] = c
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("did not get expected loggable form (got %v)", actual)
}
}

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,
}
}

10
go.mod Normal file
View File

@@ -0,0 +1,10 @@
module github.com/ipfs/go-cid
require (
github.com/multiformats/go-multibase v0.0.3
github.com/multiformats/go-multihash v0.0.15
github.com/multiformats/go-varint v0.0.6
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
)
go 1.16

35
go.sum Normal file
View File

@@ -0,0 +1,35 @@
github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
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/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/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/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
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-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM=
github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg=
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -1,30 +0,0 @@
{
"author": "whyrusleeping",
"bugs": {
"url": "https://github.com/ipfs/go-cid"
},
"gx": {
"dvcsimport": "github.com/ipfs/go-cid"
},
"gxDependencies": [
{
"author": "whyrusleeping",
"hash": "QmYDds3421prZgqKbLpEK7T9Aa2eVdQ7o3YarX1LVLdP2J",
"name": "go-multihash",
"version": "1.0.0"
},
{
"author": "whyrusleeping",
"hash": "QmXzWFN4iLdX1Vq8Sc13mET7aXsHkTyJoMbaJJD3NGRhiJ",
"name": "go-multibase",
"version": "0.2.1"
}
],
"gxVersion": "0.8.0",
"language": "go",
"license": "MIT",
"name": "go-cid",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "0.7.8"
}

44
set.go
View File

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

88
set_test.go Normal file
View File

@@ -0,0 +1,88 @@
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 := NewCidV1(7, 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")
}
}

37
varint.go Normal file
View File

@@ -0,0 +1,37 @@
package cid
import (
"github.com/multiformats/go-varint"
)
// Version of varint function that works with a string rather than
// []byte to avoid unnecessary allocation
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license as given at https://golang.org/LICENSE
// uvarint decodes a uint64 from buf and returns that value and the
// number of bytes read (> 0). If an error occurred, then 0 is
// returned for both the value and the number of bytes read, and an
// error is returned.
func uvarint(buf string) (uint64, int, error) {
var x uint64
var s uint
// we have a binary string so we can't use a range loop
for i := 0; i < len(buf); i++ {
b := buf[i]
if b < 0x80 {
if i > 9 || i == 9 && b > 1 {
return 0, 0, varint.ErrOverflow
}
if b == 0 && i > 0 {
return 0, 0, varint.ErrNotMinimal
}
return x | uint64(b)<<s, i + 1, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return 0, 0, varint.ErrUnderflow
}

56
varint_test.go Normal file
View File

@@ -0,0 +1,56 @@
package cid
import (
"testing"
"github.com/multiformats/go-varint"
)
func TestUvarintRoundTrip(t *testing.T) {
testCases := []uint64{0, 1, 2, 127, 128, 129, 255, 256, 257, 1<<63 - 1}
for _, tc := range testCases {
t.Log("testing", tc)
buf := make([]byte, 16)
varint.PutUvarint(buf, tc)
v, l1, err := uvarint(string(buf))
if err != nil {
t.Fatalf("%v: %s", buf, err)
}
_, l2, err := varint.FromUvarint(buf)
if err != nil {
t.Fatal(err)
}
if tc != v {
t.Errorf("roundtrip failed expected %d but got %d", tc, v)
}
if l1 != l2 {
t.Errorf("length incorrect expected %d but got %d", l2, l1)
}
}
}
func TestUvarintEdges(t *testing.T) {
tests := []struct {
name string
input []byte
want error
}{
{"ErrNotMinimal", []byte{0x01 | 0x80, 0}, varint.ErrNotMinimal},
{"ErrOverflow", []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}, varint.ErrOverflow},
{"ErrUnderflow", []byte{0x80}, varint.ErrUnderflow},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
v, l1, err := uvarint(string(test.input))
if err != test.want {
t.Fatalf("error case (%v) should return varint.%s (got: %v)", test.input, test.name, err)
}
if v != 0 {
t.Fatalf("error case (%v) should return 0 value (got %d)", test.input, v)
}
if l1 != 0 {
t.Fatalf("error case (%v) should return 0 length (got %d)", test.input, l1)
}
})
}
}