diff --git a/.gx/lastpubver b/.gx/lastpubver index 17aa22f..f42dec2 100644 --- a/.gx/lastpubver +++ b/.gx/lastpubver @@ -1 +1 @@ -1.0.8: QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8 +1.2.0: QmVxufDv6HhtP927fpS1bJsWLeF2t1iT7p8b315wehX5du diff --git a/io.go b/io.go index 4629016..1b232c8 100644 --- a/io.go +++ b/io.go @@ -12,7 +12,7 @@ import ( type Reader interface { io.Reader - ReadMultihash() (Multihash, error) + ReadMultihash() (MultihashBytes, error) } // Writer is an io.Writer wrapper that exposes a function @@ -20,7 +20,7 @@ type Reader interface { type Writer interface { io.Writer - WriteMultihash(Multihash) error + WriteMultihash(MultihashBytes) error } // NewReader wraps an io.Reader with a multihash.Reader @@ -59,7 +59,7 @@ func (r *mhReader) ReadByte() (byte, error) { return 0, err } -func (r *mhReader) ReadMultihash() (Multihash, error) { +func (r *mhReader) ReadMultihash() (MultihashBytes, error) { code, err := binary.ReadUvarint(r) if err != nil { return nil, err @@ -97,7 +97,7 @@ func (w *mhWriter) Write(buf []byte) (n int, err error) { return w.w.Write(buf) } -func (w *mhWriter) WriteMultihash(m Multihash) error { +func (w *mhWriter) WriteMultihash(m MultihashBytes) error { _, err := w.w.Write([]byte(m)) return err } diff --git a/multihash.go b/multihash.go index 92b4fda..354ad6f 100644 --- a/multihash.go +++ b/multihash.go @@ -10,6 +10,8 @@ import ( "fmt" "math" + strbinary "github.com/multiformats/go-multihash/strbinary" + b58 "github.com/mr-tron/base58/base58" ) @@ -144,17 +146,8 @@ var DefaultLengths = map[uint64]int{ SHAKE_256: 64, } -func uvarint(buf []byte) (uint64, []byte, error) { - n, c := binary.Uvarint(buf) - - if c == 0 { - return n, buf, ErrVarintBufferShort - } else if c < 0 { - return n, buf[-c:], ErrVarintTooLong - } else { - return n, buf[c:], nil - } -} +// Multihash is the cannoical representation of a Multihash +type Multihash struct{ str string } // DecodedMultihash represents a parsed multihash and allows // easy access to the different parts of a multihash. @@ -168,38 +161,119 @@ type DecodedMultihash struct { // Multihash is byte slice with the following form: // . // See the spec for more information. -type Multihash []byte +type MultihashBytes []byte + +// Builder contains a Sum method to create multihashes +type Builder struct { + Type uint64 + Length int +} + +// FromBinary creates a new multihash from the binary representation. The +// string is assumed to be a valid multihash. It will panic if it is +// unable to parse the string. +func FromBinary(v string) Multihash { + // Sanity check that the string can be parsed. + if len(v) < 2 { + panic(ErrTooShort) + } + i := strbinary.UvarintLen(v) + digestLen, l := strbinary.Uvarint(v[i:]) + i += l + if len(v[i:]) != int(digestLen) { + panic(ErrInvalidMultihash) + } + return Multihash{v} +} + +// IsNil checkes in the multihash is the zero value +func (m Multihash) IsNil() bool { + return m.str == "" +} + +// String returns the hex string for debugging +func (m Multihash) String() string { + return hex.EncodeToString([]byte(m.str)) +} + +// B58String returns the B58-encoded representation of a multihash. +func (m Multihash) B58String() string { + return b58.Encode([]byte(m.str)) +} + +// Parts returns the components of the Multihash (Code, Name, Digest) +func (m Multihash) Parts() (uint64, int, string) { + // Note: no need to check for errors as the New method guarantees + // the string can be parsed + i := 0 + codec, l := strbinary.Uvarint(m.str) + i += l + digestLen, l := strbinary.Uvarint(m.str[i:]) + i += l + return codec, int(digestLen), m.str[i:] +} + +func (b Builder) Sum(data []byte) (Multihash, error) { + len := b.Length + if len <= 0 { + len = -1 + } + hash, err := Sum(data, b.Type, len) + if err != nil { + return Multihash{}, err + } + return Multihash{string(hash)}, nil +} + +// AsBytes converts the multihash to the MultihashBytes type +func (m Multihash) AsBytes() MultihashBytes { + return MultihashBytes(m.str) +} + +// Bytes returns the binary representation +func (m Multihash) Bytes() []byte { + return []byte(m.str) +} + +// Binary returns the binary representation as a string +func (m Multihash) Binary() string { + return m.str +} + +func (m MultihashBytes) Cast() Multihash { + return FromBinary(string(m)) +} // HexString returns the hex-encoded representation of a multihash. -func (m *Multihash) HexString() string { - return hex.EncodeToString([]byte(*m)) +func (m MultihashBytes) HexString() string { + return hex.EncodeToString([]byte(m)) } // String is an alias to HexString(). -func (m *Multihash) String() string { +func (m MultihashBytes) String() string { return m.HexString() } // FromHexString parses a hex-encoded multihash. -func FromHexString(s string) (Multihash, error) { +func FromHexString(s string) (MultihashBytes, error) { b, err := hex.DecodeString(s) if err != nil { - return Multihash{}, err + return MultihashBytes{}, err } return Cast(b) } // B58String returns the B58-encoded representation of a multihash. -func (m Multihash) B58String() string { +func (m MultihashBytes) B58String() string { return b58.Encode([]byte(m)) } // FromB58String parses a B58-encoded multihash. -func FromB58String(s string) (m Multihash, err error) { +func FromB58String(s string) (m MultihashBytes, err error) { b, err := b58.Decode(s) if err != nil { - return Multihash{}, ErrInvalidMultihash + return MultihashBytes{}, ErrInvalidMultihash } return Cast(b) @@ -207,17 +281,17 @@ func FromB58String(s string) (m Multihash, err error) { // Cast casts a buffer onto a multihash, and returns an error // if it does not work. -func Cast(buf []byte) (Multihash, error) { +func Cast(buf []byte) (MultihashBytes, error) { dm, err := Decode(buf) if err != nil { - return Multihash{}, err + return MultihashBytes{}, err } if !ValidCode(dm.Code) { - return Multihash{}, ErrUnknownCode + return MultihashBytes{}, ErrUnknownCode } - return Multihash(buf), nil + return MultihashBytes(buf), nil } // Decode parses multihash bytes into a DecodedMultihash. @@ -298,3 +372,15 @@ func ValidCode(code uint64) bool { func AppCode(code uint64) bool { return code >= 0 && code < 0x10 } + +func uvarint(buf []byte) (uint64, []byte, error) { + n, c := binary.Uvarint(buf) + + if c == 0 { + return n, buf, ErrVarintBufferShort + } else if c < 0 { + return n, buf[-c:], ErrVarintTooLong + } else { + return n, buf[c:], nil + } +} diff --git a/multihash/main.go b/multihash/main.go index 3f874c3..2dd363c 100644 --- a/multihash/main.go +++ b/multihash/main.go @@ -20,7 +20,7 @@ Options: // flags var opts *mhopts.Options var checkRaw string -var checkMh mh.Multihash +var checkMh mh.MultihashBytes var inputFilename string var quiet bool var help bool diff --git a/multihash_test.go b/multihash_test.go index 9305e1a..a643547 100644 --- a/multihash_test.go +++ b/multihash_test.go @@ -50,7 +50,7 @@ var testCases = []TestCase{ TestCase{"4bca2b137edc580fe50a88983ef860ebaca36c857b1f492839d6d7392452a63c82cbebc68e3b70a2a1480b4bb5d437a7cba6ecf9d89f9ff3ccd14cd6146ea7e7", 0x14, "sha3-512"}, } -func (tc TestCase) Multihash() (Multihash, error) { +func (tc TestCase) Multihash() (MultihashBytes, error) { ob, err := hex.DecodeString(tc.hex) if err != nil { return nil, err diff --git a/opts/coding.go b/opts/coding.go index 0696102..78e8c8c 100644 --- a/opts/coding.go +++ b/opts/coding.go @@ -9,7 +9,7 @@ import ( mh "github.com/multiformats/go-multihash" ) -func Decode(encoding, digest string) (mh.Multihash, error) { +func Decode(encoding, digest string) (mh.MultihashBytes, error) { switch encoding { case "raw": return mh.Cast([]byte(digest)) @@ -24,7 +24,7 @@ func Decode(encoding, digest string) (mh.Multihash, error) { } } -func Encode(encoding string, hash mh.Multihash) (string, error) { +func Encode(encoding string, hash mh.MultihashBytes) (string, error) { switch encoding { case "raw": return string(hash), nil diff --git a/opts/opts.go b/opts/opts.go index c905c9b..1258834 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -107,7 +107,7 @@ func strIn(a string, set []string) bool { // Check reads all the data in r, calculates its multihash, // and checks it matches h1 -func (o *Options) Check(r io.Reader, h1 mh.Multihash) error { +func (o *Options) Check(r io.Reader, h1 mh.MultihashBytes) error { h2, err := o.Multihash(r) if err != nil { return err @@ -121,7 +121,7 @@ func (o *Options) Check(r io.Reader, h1 mh.Multihash) error { } // Multihash reads all the data in r and calculates its multihash. -func (o *Options) Multihash(r io.Reader) (mh.Multihash, error) { +func (o *Options) Multihash(r io.Reader) (mh.MultihashBytes, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err diff --git a/package.json b/package.json index 279ecd4..175835f 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,6 @@ "license": "MIT", "name": "go-multihash", "releaseCmd": "git commit -a -m \"gx release $VERSION\"", - "version": "1.0.8" + "version": "1.2.0" } diff --git a/strbinary/uvarint.go b/strbinary/uvarint.go new file mode 100644 index 0000000..647d9dc --- /dev/null +++ b/strbinary/uvarint.go @@ -0,0 +1,42 @@ +package strbinary + +// Version of varint function that work 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 characters read (> 0). If an error occurred, the value is 0 +// and the number of bytes n is <= 0 meaning: +// +// n == 0: buf too small +// n < 0: value larger than 64 bits (overflow) +// and -n is the number of bytes read +// +func Uvarint(buf string) (uint64, int) { + var x uint64 + var s uint + for i, b := range buf { + if b < 0x80 { + if i > 9 || i == 9 && b > 1 { + return 0, -(i + 1) // overflow + } + return x | uint64(b)<