Compare commits

...

14 Commits

Author SHA1 Message Date
Rod Vagg
d46e7f2866 v0.4.1 2023-04-04 14:08:15 +10:00
gammazero
83a0e939a4 Add unit test for unexpected eof 2023-04-04 14:08:15 +10:00
Andrew Gillis
0981f8566c Update cid.go
Co-authored-by: Rod Vagg <rod@vagg.org>
2023-04-04 14:08:15 +10:00
gammazero
166a3a6880 CidFromReader should not wrap valid EOF return.
When reading from an io.Reader that has no data, the io.EOF error should not be wrapped in ErrInvalidCid. This is not an invalid CID, and is not the same as a partial read which is indicated by io.ErrUnexpectedEOF.

This fix is needed because existing code that uses CidFromReader may check for the end of an input stream by `if err == io.EOF` instead of the preferred `if errors.Is(err, io.EOF)`, and that code break at runtime after upgrading to go-cid v0.4.0.
2023-04-04 14:08:15 +10:00
Henrique Dias
8098d66787 chore: version 0.4.0 2023-03-20 09:29:34 +01:00
Henrique Dias
b98e249130 feat: wrap parsing errors into ErrInvalidCid 2023-03-20 09:29:34 +01:00
dependabot[bot]
85c423677c build(deps): bump golang.org/x/crypto
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20210506145944-38f3c27a63bf to 0.1.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 17:42:17 +11:00
dependabot[bot]
197da0b712 build(deps): bump golang.org/x/sys
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20210309074719-68d13333faf2 to 0.1.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 16:12:56 +11:00
Rod Vagg
06fe289a6a fix: use crypto/rand.Read 2023-02-09 15:30:24 +11:00
web3-bot
a1630a3bd9 update .github/workflows/release-check.yml 2023-02-09 15:30:24 +11:00
web3-bot
225f5db116 update .github/workflows/go-check.yml 2023-02-09 15:30:24 +11:00
web3-bot
c2c040dac7 update .github/workflows/go-test.yml 2023-02-09 15:30:24 +11:00
web3-bot
b729e38c6e bump go.mod to Go 1.19 and run go fix 2023-02-09 15:30:24 +11:00
Nikhilesh Susarla
823c6b8a59 Fix README.md example error (#146) 2022-10-19 17:17:54 +11:00
9 changed files with 256 additions and 73 deletions

View File

@@ -8,26 +8,20 @@ jobs:
unit: unit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: All name: All
env:
RUNGOGENERATE: false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- id: config
uses: protocol/.github/.github/actions/read-config@master
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: "1.19.x" go-version: 1.20.x
- name: Run repo-specific setup - name: Run repo-specific setup
uses: ./.github/actions/go-check-setup uses: ./.github/actions/go-check-setup
if: hashFiles('./.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 - name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@376210a89477dedbe6fdc4484b233998650d7b3c # 2022.1.3 (v0.3.3) run: go install honnef.co/go/tools/cmd/staticcheck@4970552d932f48b71485287748246cf3237cebdf # 2023.1 (v0.4.0)
- name: Check that go.mod is tidy - name: Check that go.mod is tidy
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
with: with:
@@ -39,7 +33,7 @@ jobs:
fi fi
git diff --exit-code -- go.sum go.mod git diff --exit-code -- go.sum go.mod
- name: gofmt - name: gofmt
if: ${{ success() || failure() }} # run this step even if the previous one failed if: success() || failure() # run this step even if the previous one failed
run: | run: |
out=$(gofmt -s -l .) out=$(gofmt -s -l .)
if [[ -n "$out" ]]; then if [[ -n "$out" ]]; then
@@ -47,12 +41,12 @@ jobs:
exit 1 exit 1
fi fi
- name: go vet - name: go vet
if: ${{ success() || failure() }} # run this step even if the previous one failed if: success() || failure() # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
with: with:
run: go vet ./... run: go vet ./...
- name: staticcheck - name: staticcheck
if: ${{ success() || failure() }} # run this step even if the previous one failed if: success() || failure() # run this step even if the previous one failed
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
with: with:
run: | run: |
@@ -60,11 +54,11 @@ jobs:
staticcheck ./... | sed -e 's@\(.*\)\.go@./\1.go@g' staticcheck ./... | sed -e 's@\(.*\)\.go@./\1.go@g'
- name: go generate - name: go generate
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
if: (success() || failure()) && env.RUNGOGENERATE == 'true' if: (success() || failure()) && fromJSON(steps.config.outputs.json).gogenerate == true
with: with:
run: | run: |
git clean -fd # make sure there aren't untracked files / directories git clean -fd # make sure there aren't untracked files / directories
go generate ./... go generate -x ./...
# check if go generate modified or added any files # check if go generate modified or added any files
if ! $(git add . && git diff-index HEAD --exit-code --quiet); then if ! $(git add . && git diff-index HEAD --exit-code --quiet); then
echo "go generated caused changes to the repository:" echo "go generated caused changes to the repository:"

View File

@@ -10,15 +10,17 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ "ubuntu", "windows", "macos" ] os: [ "ubuntu", "windows", "macos" ]
go: [ "1.18.x", "1.19.x" ] go: ["1.19.x","1.20.x"]
env: env:
COVERAGES: "" COVERAGES: ""
runs-on: ${{ format('{0}-latest', matrix.os) }} runs-on: ${{ fromJSON(vars[format('UCI_GO_TEST_RUNNER_{0}', matrix.os)] || format('"{0}-latest"', matrix.os)) }}
name: ${{ matrix.os }} (go ${{ matrix.go }}) name: ${{ matrix.os }} (go ${{ matrix.go }})
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- id: config
uses: protocol/.github/.github/actions/read-config@master
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
@@ -27,7 +29,7 @@ jobs:
go version go version
go env go env
- name: Use msys2 on windows - name: Use msys2 on windows
if: ${{ matrix.os == 'windows' }} if: matrix.os == 'windows'
shell: bash shell: bash
# The executable for msys2 is also called bash.cmd # The executable for msys2 is also called bash.cmd
# https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#shells # https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#shells
@@ -38,6 +40,7 @@ jobs:
uses: ./.github/actions/go-test-setup uses: ./.github/actions/go-test-setup
if: hashFiles('./.github/actions/go-test-setup') != '' if: hashFiles('./.github/actions/go-test-setup') != ''
- name: Run tests - name: Run tests
if: contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
with: with:
# Use -coverpkg=./..., so that we include cross-package coverage. # Use -coverpkg=./..., so that we include cross-package coverage.
@@ -45,16 +48,21 @@ jobs:
# this means ./B's coverage will be significantly higher than 0%. # this means ./B's coverage will be significantly higher than 0%.
run: go test -v -shuffle=on -coverprofile=module-coverage.txt -coverpkg=./... ./... run: go test -v -shuffle=on -coverprofile=module-coverage.txt -coverpkg=./... ./...
- name: Run tests (32 bit) - name: Run tests (32 bit)
if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX. # can't run 32 bit tests on OSX.
if: matrix.os != 'macos' &&
fromJSON(steps.config.outputs.json).skip32bit != true &&
contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
env: env:
GOARCH: 386 GOARCH: 386
with: with:
run: | run: |
export "PATH=${{ env.PATH_386 }}:$PATH" export "PATH=$PATH_386:$PATH"
go test -v -shuffle=on ./... go test -v -shuffle=on ./...
- name: Run tests with race detector - name: Run tests with race detector
if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow # speed things up. Windows and OSX VMs are slow
if: matrix.os == 'ubuntu' &&
contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
uses: protocol/multiple-go-modules@v1.2 uses: protocol/multiple-go-modules@v1.2
with: with:
run: go test -v -race ./... run: go test -v -race ./...
@@ -62,7 +70,7 @@ jobs:
shell: bash shell: bash
run: echo "COVERAGES=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_ENV run: echo "COVERAGES=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_ENV
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
with: with:
files: '${{ env.COVERAGES }}' files: '${{ env.COVERAGES }}'
env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }} env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }}

View File

@@ -3,9 +3,11 @@
name: Release Checker name: Release Checker
on: on:
pull_request: pull_request_target:
paths: [ 'version.json' ] paths: [ 'version.json' ]
jobs: jobs:
release-check: release-check:
uses: protocol/.github/.github/workflows/release-check.yml@master uses: protocol/.github/.github/workflows/release-check.yml@master
with:
go-version: 1.20.x

View File

@@ -69,7 +69,7 @@ import (
// Create a cid manually by specifying the 'prefix' parameters // Create a cid manually by specifying the 'prefix' parameters
pref := cid.Prefix{ pref := cid.Prefix{
Version: 1, Version: 1,
Codec: mc.Raw, Codec: uint64(mc.Raw),
MhType: mh.SHA2_256, MhType: mh.SHA2_256,
MhLength: -1, // default length MhLength: -1, // default length
} }

102
cid.go
View File

@@ -37,10 +37,32 @@ import (
// UnsupportedVersionString just holds an error message // UnsupportedVersionString just holds an error message
const UnsupportedVersionString = "<unsupported cid version>" const UnsupportedVersionString = "<unsupported cid version>"
// ErrInvalidCid is an error that indicates that a CID is invalid.
type ErrInvalidCid struct {
Err error
}
func (e ErrInvalidCid) Error() string {
return fmt.Sprintf("invalid cid: %s", e.Err)
}
func (e ErrInvalidCid) Unwrap() error {
return e.Err
}
func (e ErrInvalidCid) Is(err error) bool {
switch err.(type) {
case ErrInvalidCid, *ErrInvalidCid:
return true
default:
return false
}
}
var ( var (
// ErrCidTooShort means that the cid passed to decode was not long // ErrCidTooShort means that the cid passed to decode was not long
// enough to be a valid Cid // enough to be a valid Cid
ErrCidTooShort = errors.New("cid too short") ErrCidTooShort = ErrInvalidCid{errors.New("cid too short")}
// ErrInvalidEncoding means that selected encoding is not supported // ErrInvalidEncoding means that selected encoding is not supported
// by this Cid version // by this Cid version
@@ -90,10 +112,10 @@ func tryNewCidV0(mhash mh.Multihash) (Cid, error) {
// incorrectly detect it as CidV1 in the Version() method // incorrectly detect it as CidV1 in the Version() method
dec, err := mh.Decode(mhash) dec, err := mh.Decode(mhash)
if err != nil { if err != nil {
return Undef, err return Undef, ErrInvalidCid{err}
} }
if dec.Code != mh.SHA2_256 || dec.Length != 32 { if dec.Code != mh.SHA2_256 || dec.Length != 32 {
return Undef, fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length) return Undef, ErrInvalidCid{fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length)}
} }
return Cid{string(mhash)}, nil return Cid{string(mhash)}, nil
} }
@@ -177,7 +199,7 @@ func Parse(v interface{}) (Cid, error) {
case Cid: case Cid:
return v2, nil return v2, nil
default: default:
return Undef, fmt.Errorf("can't parse %+v as Cid", v2) return Undef, ErrInvalidCid{fmt.Errorf("can't parse %+v as Cid", v2)}
} }
} }
@@ -210,7 +232,7 @@ func Decode(v string) (Cid, error) {
if len(v) == 46 && v[:2] == "Qm" { if len(v) == 46 && v[:2] == "Qm" {
hash, err := mh.FromB58String(v) hash, err := mh.FromB58String(v)
if err != nil { if err != nil {
return Undef, err return Undef, ErrInvalidCid{err}
} }
return tryNewCidV0(hash) return tryNewCidV0(hash)
@@ -218,7 +240,7 @@ func Decode(v string) (Cid, error) {
_, data, err := mbase.Decode(v) _, data, err := mbase.Decode(v)
if err != nil { if err != nil {
return Undef, err return Undef, ErrInvalidCid{err}
} }
return Cast(data) return Cast(data)
@@ -240,7 +262,7 @@ func ExtractEncoding(v string) (mbase.Encoding, error) {
// check encoding is valid // check encoding is valid
_, err := mbase.NewEncoder(encoding) _, err := mbase.NewEncoder(encoding)
if err != nil { if err != nil {
return -1, err return -1, ErrInvalidCid{err}
} }
return encoding, nil return encoding, nil
@@ -260,11 +282,11 @@ func ExtractEncoding(v string) (mbase.Encoding, error) {
func Cast(data []byte) (Cid, error) { func Cast(data []byte) (Cid, error) {
nr, c, err := CidFromBytes(data) nr, c, err := CidFromBytes(data)
if err != nil { if err != nil {
return Undef, err return Undef, ErrInvalidCid{err}
} }
if nr != len(data) { if nr != len(data) {
return Undef, fmt.Errorf("trailing bytes in data buffer passed to cid Cast") return Undef, ErrInvalidCid{fmt.Errorf("trailing bytes in data buffer passed to cid Cast")}
} }
return c, nil return c, nil
@@ -434,7 +456,7 @@ func (c Cid) Equals(o Cid) bool {
// UnmarshalJSON parses the JSON representation of a Cid. // UnmarshalJSON parses the JSON representation of a Cid.
func (c *Cid) UnmarshalJSON(b []byte) error { func (c *Cid) UnmarshalJSON(b []byte) error {
if len(b) < 2 { if len(b) < 2 {
return fmt.Errorf("invalid cid json blob") return ErrInvalidCid{fmt.Errorf("invalid cid json blob")}
} }
obj := struct { obj := struct {
CidTarget string `json:"/"` CidTarget string `json:"/"`
@@ -442,7 +464,7 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
objptr := &obj objptr := &obj
err := json.Unmarshal(b, &objptr) err := json.Unmarshal(b, &objptr)
if err != nil { if err != nil {
return err return ErrInvalidCid{err}
} }
if objptr == nil { if objptr == nil {
*c = Cid{} *c = Cid{}
@@ -450,12 +472,12 @@ func (c *Cid) UnmarshalJSON(b []byte) error {
} }
if obj.CidTarget == "" { if obj.CidTarget == "" {
return fmt.Errorf("cid was incorrectly formatted") return ErrInvalidCid{fmt.Errorf("cid was incorrectly formatted")}
} }
out, err := Decode(obj.CidTarget) out, err := Decode(obj.CidTarget)
if err != nil { if err != nil {
return err return ErrInvalidCid{err}
} }
*c = out *c = out
@@ -542,12 +564,12 @@ func (p Prefix) Sum(data []byte) (Cid, error) {
if p.Version == 0 && (p.MhType != mh.SHA2_256 || if p.Version == 0 && (p.MhType != mh.SHA2_256 ||
(p.MhLength != 32 && p.MhLength != -1)) { (p.MhLength != 32 && p.MhLength != -1)) {
return Undef, fmt.Errorf("invalid v0 prefix") return Undef, ErrInvalidCid{fmt.Errorf("invalid v0 prefix")}
} }
hash, err := mh.Sum(data, p.MhType, length) hash, err := mh.Sum(data, p.MhType, length)
if err != nil { if err != nil {
return Undef, err return Undef, ErrInvalidCid{err}
} }
switch p.Version { switch p.Version {
@@ -556,7 +578,7 @@ func (p Prefix) Sum(data []byte) (Cid, error) {
case 1: case 1:
return NewCidV1(p.Codec, hash), nil return NewCidV1(p.Codec, hash), nil
default: default:
return Undef, fmt.Errorf("invalid cid version") return Undef, ErrInvalidCid{fmt.Errorf("invalid cid version")}
} }
} }
@@ -586,22 +608,22 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
r := bytes.NewReader(buf) r := bytes.NewReader(buf)
vers, err := varint.ReadUvarint(r) vers, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, err return Prefix{}, ErrInvalidCid{err}
} }
codec, err := varint.ReadUvarint(r) codec, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, err return Prefix{}, ErrInvalidCid{err}
} }
mhtype, err := varint.ReadUvarint(r) mhtype, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, err return Prefix{}, ErrInvalidCid{err}
} }
mhlen, err := varint.ReadUvarint(r) mhlen, err := varint.ReadUvarint(r)
if err != nil { if err != nil {
return Prefix{}, err return Prefix{}, ErrInvalidCid{err}
} }
return Prefix{ return Prefix{
@@ -615,12 +637,12 @@ func PrefixFromBytes(buf []byte) (Prefix, error) {
func CidFromBytes(data []byte) (int, Cid, error) { func CidFromBytes(data []byte) (int, Cid, error) {
if len(data) > 2 && data[0] == mh.SHA2_256 && data[1] == 32 { if len(data) > 2 && data[0] == mh.SHA2_256 && data[1] == 32 {
if len(data) < 34 { if len(data) < 34 {
return 0, Undef, fmt.Errorf("not enough bytes for cid v0") return 0, Undef, ErrInvalidCid{fmt.Errorf("not enough bytes for cid v0")}
} }
h, err := mh.Cast(data[:34]) h, err := mh.Cast(data[:34])
if err != nil { if err != nil {
return 0, Undef, err return 0, Undef, ErrInvalidCid{err}
} }
return 34, Cid{string(h)}, nil return 34, Cid{string(h)}, nil
@@ -628,21 +650,21 @@ func CidFromBytes(data []byte) (int, Cid, error) {
vers, n, err := varint.FromUvarint(data) vers, n, err := varint.FromUvarint(data)
if err != nil { if err != nil {
return 0, Undef, err return 0, Undef, ErrInvalidCid{err}
} }
if vers != 1 { if vers != 1 {
return 0, Undef, fmt.Errorf("expected 1 as the cid version number, got: %d", vers) return 0, Undef, ErrInvalidCid{fmt.Errorf("expected 1 as the cid version number, got: %d", vers)}
} }
_, cn, err := varint.FromUvarint(data[n:]) _, cn, err := varint.FromUvarint(data[n:])
if err != nil { if err != nil {
return 0, Undef, err return 0, Undef, ErrInvalidCid{err}
} }
mhnr, _, err := mh.MHFromBytes(data[n+cn:]) mhnr, _, err := mh.MHFromBytes(data[n+cn:])
if err != nil { if err != nil {
return 0, Undef, err return 0, Undef, ErrInvalidCid{err}
} }
l := n + cn + mhnr l := n + cn + mhnr
@@ -695,6 +717,9 @@ func (r *bufByteReader) ReadByte() (byte, error) {
// It's recommended to supply a reader that buffers and implements io.ByteReader, // 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. // as CidFromReader has to do many single-byte reads to decode varints.
// If the argument only implements io.Reader, single-byte Read calls are used instead. // If the argument only implements io.Reader, single-byte Read calls are used instead.
//
// If the Reader is found to yield zero bytes, an io.EOF error is returned directly, in all
// other error cases, an ErrInvalidCid, wrapping the original error, is returned.
func CidFromReader(r io.Reader) (int, Cid, error) { func CidFromReader(r io.Reader) (int, Cid, error) {
// 64 bytes is enough for any CIDv0, // 64 bytes is enough for any CIDv0,
// and it's enough for most CIDv1s in practice. // and it's enough for most CIDv1s in practice.
@@ -705,32 +730,37 @@ func CidFromReader(r io.Reader) (int, Cid, error) {
// The varint package wants a io.ByteReader, so we must wrap our io.Reader. // The varint package wants a io.ByteReader, so we must wrap our io.Reader.
vers, err := varint.ReadUvarint(br) vers, err := varint.ReadUvarint(br)
if err != nil { if err != nil {
return len(br.dst), Undef, err if err == io.EOF {
// First-byte read in ReadUvarint errors with io.EOF, so reader has no data.
// Subsequent reads with an EOF will return io.ErrUnexpectedEOF and be wrapped here.
return 0, Undef, err
}
return len(br.dst), Undef, ErrInvalidCid{err}
} }
// If we have a CIDv0, read the rest of the bytes and cast the buffer. // If we have a CIDv0, read the rest of the bytes and cast the buffer.
if vers == mh.SHA2_256 { if vers == mh.SHA2_256 {
if n, err := io.ReadFull(r, br.dst[1:34]); err != nil { if n, err := io.ReadFull(r, br.dst[1:34]); err != nil {
return len(br.dst) + n, Undef, err return len(br.dst) + n, Undef, ErrInvalidCid{err}
} }
br.dst = br.dst[:34] br.dst = br.dst[:34]
h, err := mh.Cast(br.dst) h, err := mh.Cast(br.dst)
if err != nil { if err != nil {
return len(br.dst), Undef, err return len(br.dst), Undef, ErrInvalidCid{err}
} }
return len(br.dst), Cid{string(h)}, nil return len(br.dst), Cid{string(h)}, nil
} }
if vers != 1 { if vers != 1 {
return len(br.dst), Undef, fmt.Errorf("expected 1 as the cid version number, got: %d", vers) return len(br.dst), Undef, ErrInvalidCid{fmt.Errorf("expected 1 as the cid version number, got: %d", vers)}
} }
// CID block encoding multicodec. // CID block encoding multicodec.
_, err = varint.ReadUvarint(br) _, err = varint.ReadUvarint(br)
if err != nil { if err != nil {
return len(br.dst), Undef, err return len(br.dst), Undef, ErrInvalidCid{err}
} }
// We could replace most of the code below with go-multihash's ReadMultihash. // We could replace most of the code below with go-multihash's ReadMultihash.
@@ -741,19 +771,19 @@ func CidFromReader(r io.Reader) (int, Cid, error) {
// Multihash hash function code. // Multihash hash function code.
_, err = varint.ReadUvarint(br) _, err = varint.ReadUvarint(br)
if err != nil { if err != nil {
return len(br.dst), Undef, err return len(br.dst), Undef, ErrInvalidCid{err}
} }
// Multihash digest length. // Multihash digest length.
mhl, err := varint.ReadUvarint(br) mhl, err := varint.ReadUvarint(br)
if err != nil { if err != nil {
return len(br.dst), Undef, err return len(br.dst), Undef, ErrInvalidCid{err}
} }
// Refuse to make large allocations to prevent OOMs due to bugs. // Refuse to make large allocations to prevent OOMs due to bugs.
const maxDigestAlloc = 32 << 20 // 32MiB const maxDigestAlloc = 32 << 20 // 32MiB
if mhl > maxDigestAlloc { if mhl > maxDigestAlloc {
return len(br.dst), Undef, fmt.Errorf("refusing to allocate %d bytes for a digest", mhl) return len(br.dst), Undef, ErrInvalidCid{fmt.Errorf("refusing to allocate %d bytes for a digest", mhl)}
} }
// Fine to convert mhl to int, given maxDigestAlloc. // Fine to convert mhl to int, given maxDigestAlloc.
@@ -772,7 +802,7 @@ func CidFromReader(r io.Reader) (int, Cid, error) {
if n, err := io.ReadFull(r, br.dst[prefixLength:cidLength]); err != nil { if n, err := io.ReadFull(r, br.dst[prefixLength:cidLength]); err != nil {
// We can't use len(br.dst) here, // We can't use len(br.dst) here,
// as we've only read n bytes past prefixLength. // as we've only read n bytes past prefixLength.
return prefixLength + n, Undef, err return prefixLength + n, Undef, ErrInvalidCid{err}
} }
// This simply ensures the multihash is valid. // This simply ensures the multihash is valid.
@@ -780,7 +810,7 @@ func CidFromReader(r io.Reader) (int, Cid, error) {
// for now, it helps ensure consistency with CidFromBytes. // for now, it helps ensure consistency with CidFromBytes.
_, _, err = mh.MHFromBytes(br.dst[mhStart:]) _, _, err = mh.MHFromBytes(br.dst[mhStart:])
if err != nil { if err != nil {
return len(br.dst), Undef, err return len(br.dst), Undef, ErrInvalidCid{err}
} }
return len(br.dst), Cid{string(br.dst)}, nil return len(br.dst), Cid{string(br.dst)}, nil

View File

@@ -2,7 +2,9 @@ package cid
import ( import (
"bytes" "bytes"
crand "crypto/rand"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
@@ -161,6 +163,9 @@ func TestBasesMarshaling(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected too-short error from ExtractEncoding") t.Fatal("expected too-short error from ExtractEncoding")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
if ee != -1 { if ee != -1 {
t.Fatal("expected -1 from too-short ExtractEncoding") t.Fatal("expected -1 from too-short ExtractEncoding")
} }
@@ -226,6 +231,9 @@ func TestEmptyString(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("shouldnt be able to parse an empty cid") t.Fatal("shouldnt be able to parse an empty cid")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestV0Handling(t *testing.T) { func TestV0Handling(t *testing.T) {
@@ -281,6 +289,9 @@ func TestV0ErrorCases(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("should have failed to decode that ref") t.Fatal("should have failed to decode that ref")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestNewPrefixV1(t *testing.T) { func TestNewPrefixV1(t *testing.T) {
@@ -371,6 +382,9 @@ func TestInvalidV0Prefix(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("should error (index %d)", i) t.Fatalf("should error (index %d)", i)
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
} }
@@ -380,6 +394,9 @@ func TestBadPrefix(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("expected error on v3 prefix Sum") t.Fatalf("expected error on v3 prefix Sum")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
func TestPrefixRoundtrip(t *testing.T) { func TestPrefixRoundtrip(t *testing.T) {
@@ -416,18 +433,30 @@ func TestBadPrefixFromBytes(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 0") t.Fatal("expected error for bad byte 0")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 1") t.Fatal("expected error for bad byte 1")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 2") t.Fatal("expected error for bad byte 2")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
_, err = PrefixFromBytes([]byte{0x01, 0x01, 0x01, 0x80}) _, err = PrefixFromBytes([]byte{0x01, 0x01, 0x01, 0x80})
if err == nil { if err == nil {
t.Fatal("expected error for bad byte 3") t.Fatal("expected error for bad byte 3")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
} }
func Test16BytesVarint(t *testing.T) { func Test16BytesVarint(t *testing.T) {
@@ -441,7 +470,7 @@ func TestFuzzCid(t *testing.T) {
buf := make([]byte, 128) buf := make([]byte, 128)
for i := 0; i < 200; i++ { for i := 0; i < 200; i++ {
s := rand.Intn(128) s := rand.Intn(128)
rand.Read(buf[:s]) crand.Read(buf[:s])
_, _ = Cast(buf[:s]) _, _ = Cast(buf[:s])
} }
} }
@@ -454,6 +483,9 @@ func TestParse(t *testing.T) {
if !strings.Contains(err.Error(), "can't parse 123 as Cid") { if !strings.Contains(err.Error(), "can't parse 123 as Cid") {
t.Fatalf("expected int error, got %s", err.Error()) t.Fatalf("expected int error, got %s", err.Error())
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatalf("expected ErrInvalidCid, got %s", err.Error())
}
theHash := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" theHash := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
h, err := mh.FromB58String(theHash) h, err := mh.FromB58String(theHash)
@@ -571,17 +603,29 @@ func TestJsonRoundTrip(t *testing.T) {
t.Fatal("cids not equal for Cid") t.Fatal("cids not equal for Cid")
} }
if err = actual2.UnmarshalJSON([]byte("1")); err == nil { err = actual2.UnmarshalJSON([]byte("1"))
if err == nil {
t.Fatal("expected error for too-short JSON") t.Fatal("expected error for too-short JSON")
} }
if !errors.Is(err, ErrInvalidCid{}) {
if err = actual2.UnmarshalJSON([]byte(`{"nope":"nope"}`)); err == nil { t.Fatal("expected error to be ErrInvalidCid")
t.Fatal("expected error for bad CID JSON")
} }
if err = actual2.UnmarshalJSON([]byte(`bad "" json!`)); err == nil { err = actual2.UnmarshalJSON([]byte(`{"nope":"nope"}`))
if err == nil {
t.Fatal("expected error for bad CID JSON")
}
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
err = actual2.UnmarshalJSON([]byte(`bad "" json!`))
if err == nil {
t.Fatal("expected error for bad JSON") t.Fatal("expected error for bad JSON")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("expected error to be ErrInvalidCid")
}
var actual3 Cid var actual3 Cid
enc, err = actual3.MarshalJSON() enc, err = actual3.MarshalJSON()
@@ -739,6 +783,30 @@ func TestBadCidInput(t *testing.T) {
} }
} }
func TestFromReaderNoData(t *testing.T) {
// Reading no data from io.Reader should return io.EOF, not ErrInvalidCid.
n, cid, err := CidFromReader(bytes.NewReader(nil))
if err != io.EOF {
t.Fatal("Expected io.EOF error")
}
if cid != Undef {
t.Fatal("Expected Undef CID")
}
if n != 0 {
t.Fatal("Expected 0 data")
}
// Read byte indicatiing more data to and check error is ErrInvalidCid.
_, _, err = CidFromReader(bytes.NewReader([]byte{0x80}))
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("Expected ErrInvalidCid error")
}
// Check for expected wrapped error.
if !errors.Is(err, io.ErrUnexpectedEOF) {
t.Fatal("Expected error", io.ErrUnexpectedEOF)
}
}
func TestBadParse(t *testing.T) { func TestBadParse(t *testing.T) {
hash, err := mh.Sum([]byte("foobar"), mh.SHA3_256, -1) hash, err := mh.Sum([]byte("foobar"), mh.SHA3_256, -1)
if err != nil { if err != nil {
@@ -748,6 +816,9 @@ func TestBadParse(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected to fail to parse an invalid CIDv1 CID") t.Fatal("expected to fail to parse an invalid CIDv1 CID")
} }
if !errors.Is(err, ErrInvalidCid{}) {
t.Fatal("error must be ErrInvalidCid")
}
} }
func TestLoggable(t *testing.T) { func TestLoggable(t *testing.T) {
@@ -762,3 +833,80 @@ func TestLoggable(t *testing.T) {
t.Fatalf("did not get expected loggable form (got %v)", actual) t.Fatalf("did not get expected loggable form (got %v)", actual)
} }
} }
func TestErrInvalidCidIs(t *testing.T) {
for i, test := range []struct {
err error
target error
}{
{&ErrInvalidCid{}, ErrInvalidCid{}},
{ErrInvalidCid{}, &ErrInvalidCid{}},
{ErrInvalidCid{}, ErrInvalidCid{}},
{&ErrInvalidCid{}, &ErrInvalidCid{}},
} {
if !errors.Is(test.err, test.target) {
t.Fatalf("expected error to be ErrInvalidCid, case %d", i)
}
}
}
func TestErrInvalidCid(t *testing.T) {
run := func(err error) {
if err == nil {
t.Fatal("expected error")
}
if !strings.HasPrefix(err.Error(), "invalid cid: ") {
t.Fatal(`expected error message to contain "invalid cid: "`)
}
is := errors.Is(err, ErrInvalidCid{})
if !is {
t.Fatal("expected error to be ErrInvalidCid")
}
if !errors.Is(err, &ErrInvalidCid{}) {
t.Fatal("expected error to be &ErrInvalidCid")
}
}
_, err := Decode("")
run(err)
_, err = Decode("not-a-cid")
run(err)
_, err = Decode("bafyInvalid")
run(err)
_, err = Decode("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII")
run(err)
_, err = Cast([]byte("invalid"))
run(err)
_, err = Parse("not-a-cid")
run(err)
_, err = Parse("bafyInvalid")
run(err)
_, err = Parse("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII")
run(err)
_, err = Parse(123)
run(err)
_, _, err = CidFromBytes([]byte("invalid"))
run(err)
_, err = Prefix{}.Sum([]byte("data"))
run(err)
_, err = PrefixFromBytes([]byte{0x80})
run(err)
_, err = ExtractEncoding("invalid ")
run(err)
}

6
go.mod
View File

@@ -13,8 +13,8 @@ require (
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect golang.org/x/sys v0.1.0 // indirect
) )
go 1.18 go 1.19

7
go.sum
View File

@@ -19,12 +19,13 @@ github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= 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-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-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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20191026070338-33540a1f6037/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/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -1,3 +1,3 @@
{ {
"version": "v0.3.2" "version": "v0.4.1"
} }