From 799731b9e5c0f9cfe9b8204c3ff888efaf1bebce Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Fri, 27 Jul 2018 18:02:02 -0400 Subject: [PATCH 1/8] gx update go-multibase to 0.2.7 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4dcadb0..e6ea612 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ }, { "author": "whyrusleeping", - "hash": "QmexBtiTTEwwn42Yi6ouKt6VqzpA6wjJgiW1oh9VfaRrup", + "hash": "QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR", "name": "go-multibase", - "version": "0.2.6" + "version": "0.2.7" } ], "gxVersion": "0.8.0", From 019d945bf58f83284e00c6fccaf2c7a0fe8f5b88 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Thu, 26 Jul 2018 17:56:48 -0400 Subject: [PATCH 2/8] Use lookup table in go-multibase now that it is supported. --- cid-fmt/main.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 7dacb8a..6ec6501 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -228,13 +228,11 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { } func baseToString(base mb.Encoding) string { - // FIXME: Use lookup tables when they are added to go-multibase - switch base { - case mb.Base58BTC: - return "base58btc" - default: + baseStr, ok := mb.EncodingToStr[base] + if !ok { return fmt.Sprintf("base?%c", base) } + return baseStr } func codecToString(num uint64) string { From 038b7f7cc9161aa5fc3176093a6b84e0b8450f63 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Tue, 31 Jul 2018 17:07:49 -0400 Subject: [PATCH 3/8] Use new Encoder in multibase package. This allows multibase codes to be specified by the name rather than just the prefix char. --- cid-fmt/main.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 6ec6501..67c5571 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -55,11 +55,12 @@ outer: if len(args) < 2 { usage() } - if len(args[1]) != 1 { - fmt.Fprintf(os.Stderr, "Error: Invalid multibase code: %s\n", args[1]) + encoder, err := mb.EncoderByName(args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) os.Exit(2) } - newBase = mb.Encoding(args[1][0]) + newBase = encoder.Encoding() args = args[2:] case "-v": if len(args) < 2 { @@ -162,6 +163,10 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { p := cid.Prefix() out := new(bytes.Buffer) var err error + encoder, err := mb.NewEncoder(base) + if err != nil { + return ERR_STR, err + } for i := 0; i < len(fmtStr); i++ { if fmtStr[i] != '%' { out.WriteByte(fmtStr[i]) @@ -193,7 +198,7 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { case 'L': // hash length fmt.Fprintf(out, "%d", p.MhLength) case 'm', 'M': // multihash encoded in base %b - out.WriteString(encode(base, cid.Hash(), fmtStr[i] == 'M')) + out.WriteString(encode(encoder, cid.Hash(), fmtStr[i] == 'M')) case 'd', 'D': // hash digest encoded in base %b dec, err := mh.Decode(cid.Hash()) if err != nil { @@ -201,7 +206,7 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { errorMsg("%v", err) continue } - out.WriteString(encode(base, dec.Digest, fmtStr[i] == 'D')) + out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D')) case 's': // cid string encoded in base %b str, err := cid.StringOfBase(base) if err != nil { @@ -211,7 +216,7 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { } out.WriteString(str) case 'S': // cid string without base prefix - out.WriteString(encode(base, cid.Bytes(), true)) + out.WriteString(encode(encoder, cid.Bytes(), true)) case 'P': // prefix fmt.Fprintf(out, "cidv%d-%s-%s-%d", p.Version, @@ -251,12 +256,8 @@ func hashToString(num uint64) string { return name } -func encode(base mb.Encoding, data []byte, strip bool) string { - str, err := mb.Encode(base, data) - if err != nil { - errorMsg("%v", err) - return ERR_STR - } +func encode(base mb.Encoder, data []byte, strip bool) string { + str := base.Encode(data) if strip { return str[1:] } From 056eac16aed279f77f531d2c23d2a7e76599ba22 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Tue, 31 Jul 2018 17:12:16 -0400 Subject: [PATCH 4/8] Fix fmtRef string. --- cid-fmt/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 67c5571..bc5cab2 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -34,7 +34,7 @@ const fmtRef = ` %d hash digest encoded in base %b (with multibase prefix) %D hash digest encoded in base %b without multibase prefix %s cid string encoded in base %b (1) - %s cid string encoded in base %b without multibase prefix + %S cid string encoded in base %b without multibase prefix %P cid prefix: %v-%c-%h-%L (1) For CID version 0 the multibase must be base58btc and no prefix is From 36bab4873cbc24a8f2ec73fa126d11c91fe2d31a Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Tue, 31 Jul 2018 22:10:47 -0400 Subject: [PATCH 5/8] Clean up cid-fmt util code. --- cid-fmt/main.go | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/cid-fmt/main.go b/cid-fmt/main.go index bc5cab2..44bfb44 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -114,10 +114,17 @@ outer: } } str, err := fmtCid(fmtStr, base, cid) - if err != nil { + switch err.(type) { + case FormatStringError: fmt.Fprintf(os.Stderr, "Error: %v\n", err) - // An error here means a bad format string, no point in continuing os.Exit(2) + default: + fmt.Fprintf(os.Stdout, "!ERROR!\n") + errorMsg("%s: %v", cidStr, err) + // Don't abort on cid specific errors + continue + case nil: + // no error } fmt.Fprintf(os.Stdout, "%s\n", str) } @@ -159,13 +166,26 @@ func decode(v string) (mb.Encoding, *c.Cid, error) { const ERR_STR = "!ERROR!" +type FormatStringError struct { + Message string + Specifier string +} + +func (e FormatStringError) Error() string { + if e.Specifier == "" { + return e.Message + } else { + return fmt.Sprintf("%s: %s", e.Message, e.Specifier) + } +} + func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { p := cid.Prefix() out := new(bytes.Buffer) var err error encoder, err := mb.NewEncoder(base) if err != nil { - return ERR_STR, err + return "", err } for i := 0; i < len(fmtStr); i++ { if fmtStr[i] != '%' { @@ -174,7 +194,7 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { } i++ if i >= len(fmtStr) { - return "", fmt.Errorf("premature end of format string") + return "", FormatStringError{"premature end of format string", ""} } switch fmtStr[i] { case '%': @@ -202,17 +222,13 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { case 'd', 'D': // hash digest encoded in base %b dec, err := mh.Decode(cid.Hash()) if err != nil { - out.WriteString(ERR_STR) - errorMsg("%v", err) - continue + return "", err } out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D')) case 's': // cid string encoded in base %b str, err := cid.StringOfBase(base) if err != nil { - out.WriteString(ERR_STR) - errorMsg("%v", err) - continue + return "", err } out.WriteString(str) case 'S': // cid string without base prefix @@ -225,7 +241,7 @@ func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { p.MhLength, ) default: - return "", fmt.Errorf("unrecognized specifier in format string: %c", fmtStr[i]) + return "", FormatStringError{"unrecognized specifier in format string", fmtStr[i-1 : i+1]} } } From c4bfcd0671272482dc7ba9b5979331881d5a1313 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Wed, 1 Aug 2018 04:21:32 -0400 Subject: [PATCH 6/8] Extract most of `cid-fmt` logic so it can be used as a library. --- cid-fmt/main.go | 173 +------------------------------------------ cid-fmt/main_test.go | 70 +---------------- cid.go | 21 ++++-- format.go | 151 +++++++++++++++++++++++++++++++++++++ format_test.go | 73 ++++++++++++++++++ 5 files changed, 246 insertions(+), 242 deletions(-) create mode 100644 format.go create mode 100644 format_test.go diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 44bfb44..8d7f53b 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "fmt" "os" "strings" @@ -9,38 +8,14 @@ import ( c "github.com/ipfs/go-cid" mb "github.com/multiformats/go-multibase" - mh "github.com/multiformats/go-multihash" ) func usage() { fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] ...\n\n", os.Args[0]) - fmt.Fprintf(os.Stderr, " is either 'prefix' or a printf style format string:\n%s", fmtRef) + fmt.Fprintf(os.Stderr, " is either 'prefix' or a printf style format string:\n%s", c.FormatRef) os.Exit(2) } -const fmtRef = ` - %% literal % - %b multibase name - %B multibase code - %v version string - %V version number - %c codec name - %C codec code - %h multihash name - %H multihash code - %L hash digest length - %m multihash encoded in base %b (with multibase prefix) - %M multihash encoded in base %b without multibase prefix - %d hash digest encoded in base %b (with multibase prefix) - %D hash digest encoded in base %b without multibase prefix - %s cid string encoded in base %b (1) - %S cid string encoded in base %b without multibase prefix - %P cid prefix: %v-%c-%h-%L - -(1) For CID version 0 the multibase must be base58btc and no prefix is -used. For Cid version 1 the multibase prefix is included. -` - func main() { if len(os.Args) < 2 { usage() @@ -94,7 +69,7 @@ outer: } } for _, cidStr := range args[1:] { - base, cid, err := decode(cidStr) + base, cid, err := c.DecodeV2(cidStr) if err != nil { fmt.Fprintf(os.Stdout, "!INVALID_CID!\n") errorMsg("%s: %v", cidStr, err) @@ -113,9 +88,9 @@ outer: continue } } - str, err := fmtCid(fmtStr, base, cid) + str, err := c.Format(fmtStr, base, cid) switch err.(type) { - case FormatStringError: + case c.FormatStringError: fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(2) default: @@ -140,146 +115,6 @@ func errorMsg(fmtStr string, a ...interface{}) { exitCode = 1 } -func decode(v string) (mb.Encoding, *c.Cid, error) { - if len(v) < 2 { - return 0, nil, c.ErrCidTooShort - } - - if len(v) == 46 && v[:2] == "Qm" { - hash, err := mh.FromB58String(v) - if err != nil { - return 0, nil, err - } - - return mb.Base58BTC, c.NewCidV0(hash), nil - } - - base, data, err := mb.Decode(v) - if err != nil { - return 0, nil, err - } - - cid, err := c.Cast(data) - - return base, cid, err -} - -const ERR_STR = "!ERROR!" - -type FormatStringError struct { - Message string - Specifier string -} - -func (e FormatStringError) Error() string { - if e.Specifier == "" { - return e.Message - } else { - return fmt.Sprintf("%s: %s", e.Message, e.Specifier) - } -} - -func fmtCid(fmtStr string, base mb.Encoding, cid *c.Cid) (string, error) { - p := cid.Prefix() - out := new(bytes.Buffer) - var err error - encoder, err := mb.NewEncoder(base) - if err != nil { - return "", err - } - for i := 0; i < len(fmtStr); i++ { - if fmtStr[i] != '%' { - out.WriteByte(fmtStr[i]) - continue - } - i++ - if i >= len(fmtStr) { - return "", FormatStringError{"premature end of format string", ""} - } - switch fmtStr[i] { - case '%': - out.WriteByte('%') - case 'b': // base name - out.WriteString(baseToString(base)) - case 'B': // base code - out.WriteByte(byte(base)) - case 'v': // version string - fmt.Fprintf(out, "cidv%d", p.Version) - case 'V': // version num - fmt.Fprintf(out, "%d", p.Version) - case 'c': // codec name - out.WriteString(codecToString(p.Codec)) - case 'C': // codec code - fmt.Fprintf(out, "%d", p.Codec) - case 'h': // hash fun name - out.WriteString(hashToString(p.MhType)) - case 'H': // hash fun code - fmt.Fprintf(out, "%d", p.MhType) - case 'L': // hash length - fmt.Fprintf(out, "%d", p.MhLength) - case 'm', 'M': // multihash encoded in base %b - out.WriteString(encode(encoder, cid.Hash(), fmtStr[i] == 'M')) - case 'd', 'D': // hash digest encoded in base %b - dec, err := mh.Decode(cid.Hash()) - if err != nil { - return "", err - } - out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D')) - case 's': // cid string encoded in base %b - str, err := cid.StringOfBase(base) - if err != nil { - return "", err - } - out.WriteString(str) - case 'S': // cid string without base prefix - out.WriteString(encode(encoder, cid.Bytes(), true)) - case 'P': // prefix - fmt.Fprintf(out, "cidv%d-%s-%s-%d", - p.Version, - codecToString(p.Codec), - hashToString(p.MhType), - p.MhLength, - ) - default: - return "", FormatStringError{"unrecognized specifier in format string", fmtStr[i-1 : i+1]} - } - - } - return out.String(), err -} - -func baseToString(base mb.Encoding) string { - baseStr, ok := mb.EncodingToStr[base] - if !ok { - return fmt.Sprintf("base?%c", base) - } - return baseStr -} - -func codecToString(num uint64) string { - name, ok := c.CodecToStr[num] - if !ok { - return fmt.Sprintf("codec?%d", num) - } - return name -} - -func hashToString(num uint64) string { - name, ok := mh.Codes[num] - if !ok { - return fmt.Sprintf("hash?%d", num) - } - return name -} - -func encode(base mb.Encoder, data []byte, strip bool) string { - str := base.Encode(data) - if strip { - return str[1:] - } - return str -} - func toCidV0(cid *c.Cid) (*c.Cid, error) { if cid.Type() != c.DagProtobuf { return nil, fmt.Errorf("can't convert non-protobuf nodes to cidv0") diff --git a/cid-fmt/main_test.go b/cid-fmt/main_test.go index 568b21a..5161258 100644 --- a/cid-fmt/main_test.go +++ b/cid-fmt/main_test.go @@ -4,77 +4,13 @@ import ( "fmt" "testing" - mb "github.com/multiformats/go-multibase" + c "github.com/ipfs/go-cid" ) -func TestFmt(t *testing.T) { - cids := map[string]string{ - "cidv0": "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", - "cidv1": "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD", - } - tests := []struct { - cidId string - newBase mb.Encoding - fmtStr string - result string - }{ - {"cidv0", -1, "%P", "cidv0-protobuf-sha2-256-32"}, - {"cidv0", -1, "%b-%v-%c-%h-%L", "base58btc-cidv0-protobuf-sha2-256-32"}, - {"cidv0", -1, "%s", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, - {"cidv0", -1, "%S", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, - {"cidv0", -1, "ver#%V/#%C/#%H/%L", "ver#0/#112/#18/32"}, - {"cidv0", -1, "%m", "zQmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, - {"cidv0", -1, "%M", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, - {"cidv0", -1, "%d", "z72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"}, - {"cidv0", -1, "%D", "72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"}, - {"cidv0", 'B', "%S", "CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"}, - {"cidv0", 'B', "%B%S", "BCIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"}, - {"cidv1", -1, "%P", "cidv1-protobuf-sha2-256-32"}, - {"cidv1", -1, "%b-%v-%c-%h-%L", "base58btc-cidv1-protobuf-sha2-256-32"}, - {"cidv1", -1, "%s", "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"}, - {"cidv1", -1, "%S", "dj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"}, - {"cidv1", -1, "ver#%V/#%C/#%H/%L", "ver#1/#112/#18/32"}, - {"cidv1", -1, "%m", "zQmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"}, - {"cidv1", -1, "%M", "QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"}, - {"cidv1", -1, "%d", "zAux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"}, - {"cidv1", -1, "%D", "Aux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"}, - {"cidv1", 'B', "%s", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, - {"cidv1", 'B', "%S", "AFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, - {"cidv1", 'B', "%B%S", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, - } - for _, tc := range tests { - name := fmt.Sprintf("%s/%s", tc.cidId, tc.fmtStr) - if tc.newBase != -1 { - name = fmt.Sprintf("%s/%c", name, tc.newBase) - } - cidStr := cids[tc.cidId] - t.Run(name, func(t *testing.T) { - testFmt(t, cidStr, tc.newBase, tc.fmtStr, tc.result) - }) - } -} - -func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, result string) { - base, cid, err := decode(cidStr) - if newBase != -1 { - base = newBase - } - if err != nil { - t.Fatal(err) - } - str, err := fmtCid(fmtStr, base, cid) - if err != nil { - t.Fatal(err) - } - if str != result { - t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str)) - } -} - func TestCidConv(t *testing.T) { cidv0 := "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" cidv1 := "zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi" - _, cid, err := decode(cidv0) + _, cid, err := c.DecodeV2(cidv0) if err != nil { t.Fatal(err) } @@ -98,7 +34,7 @@ func TestCidConv(t *testing.T) { func TestBadCidConv(t *testing.T) { // this cid is a raw leaf and should not be able to convert to cidv0 cidv1 := "zb2rhhzX7uSKrtQ2ZZXFAabKiKFYZrJqKY2KE1cJ8yre2GSWZ" - _, cid, err := decode(cidv1) + _, cid, err := c.DecodeV2(cidv1) if err != nil { t.Fatal(err) } diff --git a/cid.go b/cid.go index 7859f75..8992055 100644 --- a/cid.go +++ b/cid.go @@ -213,25 +213,34 @@ func Parse(v interface{}) (*Cid, error) { // starting with "Qm" are considered CidV0 and treated directly // as B58-encoded multihashes. func Decode(v string) (*Cid, error) { + _, cid, err := DecodeV2(v) + return cid, err +} + +// DecodeV2 is like Decide but also returns the Multibase encoding the +// Cid was encoded in. EXPERIMENTAL and interface may change at any time. +func DecodeV2(v string) (mbase.Encoding, *Cid, error) { if len(v) < 2 { - return nil, ErrCidTooShort + return 0, nil, ErrCidTooShort } if len(v) == 46 && v[:2] == "Qm" { hash, err := mh.FromB58String(v) if err != nil { - return nil, err + return 0, nil, err } - return NewCidV0(hash), nil + return mbase.Base58BTC, NewCidV0(hash), nil } - _, data, err := mbase.Decode(v) + base, data, err := mbase.Decode(v) if err != nil { - return nil, err + return 0, nil, err } - return Cast(data) + cid, err := Cast(data) + + return base, cid, err } func uvError(read int) error { diff --git a/format.go b/format.go new file mode 100644 index 0000000..b447c65 --- /dev/null +++ b/format.go @@ -0,0 +1,151 @@ +package cid + +import ( + "bytes" + "fmt" + + mb "github.com/multiformats/go-multibase" + mh "github.com/multiformats/go-multihash" +) + +// FormatRef is a string documenting the format string for the Format function +const FormatRef = ` + %% literal % + %b multibase name + %B multibase code + %v version string + %V version number + %c codec name + %C codec code + %h multihash name + %H multihash code + %L hash digest length + %m multihash encoded in base %b (with multibase prefix) + %M multihash encoded in base %b without multibase prefix + %d hash digest encoded in base %b (with multibase prefix) + %D hash digest encoded in base %b without multibase prefix + %s cid string encoded in base %b (1) + %S cid string encoded in base %b without multibase prefix + %P cid prefix: %v-%c-%h-%L + +(1) For CID version 0 the multibase must be base58btc and no prefix is +used. For Cid version 1 the multibase prefix is included. +` + +// Format formats a cid according to the format specificer as +// documented in the FormatRef constant +func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) { + p := cid.Prefix() + out := new(bytes.Buffer) + var err error + encoder, err := mb.NewEncoder(base) + if err != nil { + return "", err + } + for i := 0; i < len(fmtStr); i++ { + if fmtStr[i] != '%' { + out.WriteByte(fmtStr[i]) + continue + } + i++ + if i >= len(fmtStr) { + return "", FormatStringError{"premature end of format string", ""} + } + switch fmtStr[i] { + case '%': + out.WriteByte('%') + case 'b': // base name + out.WriteString(baseToString(base)) + case 'B': // base code + out.WriteByte(byte(base)) + case 'v': // version string + fmt.Fprintf(out, "cidv%d", p.Version) + case 'V': // version num + fmt.Fprintf(out, "%d", p.Version) + case 'c': // codec name + out.WriteString(codecToString(p.Codec)) + case 'C': // codec code + fmt.Fprintf(out, "%d", p.Codec) + case 'h': // hash fun name + out.WriteString(hashToString(p.MhType)) + case 'H': // hash fun code + fmt.Fprintf(out, "%d", p.MhType) + case 'L': // hash length + fmt.Fprintf(out, "%d", p.MhLength) + case 'm', 'M': // multihash encoded in base %b + out.WriteString(encode(encoder, cid.Hash(), fmtStr[i] == 'M')) + case 'd', 'D': // hash digest encoded in base %b + dec, err := mh.Decode(cid.Hash()) + if err != nil { + return "", err + } + out.WriteString(encode(encoder, dec.Digest, fmtStr[i] == 'D')) + case 's': // cid string encoded in base %b + str, err := cid.StringOfBase(base) + if err != nil { + return "", err + } + out.WriteString(str) + case 'S': // cid string without base prefix + out.WriteString(encode(encoder, cid.Bytes(), true)) + case 'P': // prefix + fmt.Fprintf(out, "cidv%d-%s-%s-%d", + p.Version, + codecToString(p.Codec), + hashToString(p.MhType), + p.MhLength, + ) + default: + return "", FormatStringError{"unrecognized specifier in format string", fmtStr[i-1 : i+1]} + } + + } + return out.String(), err +} + +// FormatStringError is the error return from Format when the format +// string is ill formed +type FormatStringError struct { + Message string + Specifier string +} + +func (e FormatStringError) Error() string { + if e.Specifier == "" { + return e.Message + } else { + return fmt.Sprintf("%s: %s", e.Message, e.Specifier) + } +} + +func baseToString(base mb.Encoding) string { + baseStr, ok := mb.EncodingToStr[base] + if !ok { + return fmt.Sprintf("base?%c", base) + } + return baseStr +} + +func codecToString(num uint64) string { + name, ok := CodecToStr[num] + if !ok { + return fmt.Sprintf("codec?%d", num) + } + return name +} + +func hashToString(num uint64) string { + name, ok := mh.Codes[num] + if !ok { + return fmt.Sprintf("hash?%d", num) + } + return name +} + +func encode(base mb.Encoder, data []byte, strip bool) string { + str := base.Encode(data) + if strip { + return str[1:] + } + return str +} diff --git a/format_test.go b/format_test.go new file mode 100644 index 0000000..7a92bb8 --- /dev/null +++ b/format_test.go @@ -0,0 +1,73 @@ +package cid + +import ( + "fmt" + "testing" + + mb "github.com/multiformats/go-multibase" +) + +func TestFmt(t *testing.T) { + cids := map[string]string{ + "cidv0": "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", + "cidv1": "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD", + } + tests := []struct { + cidId string + newBase mb.Encoding + fmtStr string + result string + }{ + {"cidv0", -1, "%P", "cidv0-protobuf-sha2-256-32"}, + {"cidv0", -1, "%b-%v-%c-%h-%L", "base58btc-cidv0-protobuf-sha2-256-32"}, + {"cidv0", -1, "%s", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, + {"cidv0", -1, "%S", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, + {"cidv0", -1, "ver#%V/#%C/#%H/%L", "ver#0/#112/#18/32"}, + {"cidv0", -1, "%m", "zQmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, + {"cidv0", -1, "%M", "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"}, + {"cidv0", -1, "%d", "z72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"}, + {"cidv0", -1, "%D", "72gdmFAgRzYHkJzKiL8MgMMRW3BTSCGyDHroPxJbxMJn"}, + {"cidv0", 'B', "%S", "CIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"}, + {"cidv0", 'B', "%B%S", "BCIQFTFEEHEDF6KLBT32BFAGLXEZL4UWFNWM4LFTLMXQBCERZ6CMLX3Y"}, + {"cidv1", -1, "%P", "cidv1-protobuf-sha2-256-32"}, + {"cidv1", -1, "%b-%v-%c-%h-%L", "base58btc-cidv1-protobuf-sha2-256-32"}, + {"cidv1", -1, "%s", "zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"}, + {"cidv1", -1, "%S", "dj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD"}, + {"cidv1", -1, "ver#%V/#%C/#%H/%L", "ver#1/#112/#18/32"}, + {"cidv1", -1, "%m", "zQmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"}, + {"cidv1", -1, "%M", "QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H"}, + {"cidv1", -1, "%d", "zAux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"}, + {"cidv1", -1, "%D", "Aux4gVVsLRMXtsZ9fd3tFEZN4jGYB6kP37fgoZNTc11H"}, + {"cidv1", 'B', "%s", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, + {"cidv1", 'B', "%S", "AFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, + {"cidv1", 'B', "%B%S", "BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA"}, + } + for _, tc := range tests { + name := fmt.Sprintf("%s/%s", tc.cidId, tc.fmtStr) + if tc.newBase != -1 { + name = fmt.Sprintf("%s/%c", name, tc.newBase) + } + cidStr := cids[tc.cidId] + t.Run(name, func(t *testing.T) { + testFmt(t, cidStr, tc.newBase, tc.fmtStr, tc.result) + }) + } +} + +func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, result string) { + base, cid, err := DecodeV2(cidStr) + if newBase != -1 { + base = newBase + } + if err != nil { + t.Fatal(err) + } + str, err := Format(fmtStr, base, cid) + if err != nil { + t.Fatal(err) + } + if str != result { + t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str)) + } +} + From 0f09109d9f226bd0d6061715d81793c16890e42e Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Thu, 9 Aug 2018 02:37:09 -0400 Subject: [PATCH 7/8] Replace DecodeV2 with ExtractEncoding --- cid-fmt/main.go | 7 ++++--- cid-fmt/main_test.go | 4 ++-- cid.go | 41 +++++++++++++++++++++++++++-------------- format_test.go | 10 +++++----- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/cid-fmt/main.go b/cid-fmt/main.go index 8d7f53b..988126a 100644 --- a/cid-fmt/main.go +++ b/cid-fmt/main.go @@ -69,15 +69,16 @@ outer: } } for _, cidStr := range args[1:] { - base, cid, err := c.DecodeV2(cidStr) + cid, err := c.Decode(cidStr) if err != nil { fmt.Fprintf(os.Stdout, "!INVALID_CID!\n") errorMsg("%s: %v", cidStr, err) // Don't abort on a bad cid continue } - if newBase != -1 { - base = newBase + base := newBase + if newBase == -1 { + base, _ = c.ExtractEncoding(cidStr) } if verConv != nil { cid, err = verConv(cid) diff --git a/cid-fmt/main_test.go b/cid-fmt/main_test.go index 5161258..ce66c0a 100644 --- a/cid-fmt/main_test.go +++ b/cid-fmt/main_test.go @@ -10,7 +10,7 @@ import ( func TestCidConv(t *testing.T) { cidv0 := "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" cidv1 := "zdj7WbTaiJT1fgatdet9Ei9iDB5hdCxkbVyhyh8YTUnXMiwYi" - _, cid, err := c.DecodeV2(cidv0) + cid, err := c.Decode(cidv0) if err != nil { t.Fatal(err) } @@ -34,7 +34,7 @@ func TestCidConv(t *testing.T) { func TestBadCidConv(t *testing.T) { // this cid is a raw leaf and should not be able to convert to cidv0 cidv1 := "zb2rhhzX7uSKrtQ2ZZXFAabKiKFYZrJqKY2KE1cJ8yre2GSWZ" - _, cid, err := c.DecodeV2(cidv1) + cid, err := c.Decode(cidv1) if err != nil { t.Fatal(err) } diff --git a/cid.go b/cid.go index 8992055..337e45f 100644 --- a/cid.go +++ b/cid.go @@ -213,34 +213,47 @@ func Parse(v interface{}) (*Cid, error) { // starting with "Qm" are considered CidV0 and treated directly // as B58-encoded multihashes. func Decode(v string) (*Cid, error) { - _, cid, err := DecodeV2(v) - return cid, err -} - -// DecodeV2 is like Decide but also returns the Multibase encoding the -// Cid was encoded in. EXPERIMENTAL and interface may change at any time. -func DecodeV2(v string) (mbase.Encoding, *Cid, error) { if len(v) < 2 { - return 0, nil, ErrCidTooShort + return nil, ErrCidTooShort } if len(v) == 46 && v[:2] == "Qm" { hash, err := mh.FromB58String(v) if err != nil { - return 0, nil, err + return nil, err } - return mbase.Base58BTC, NewCidV0(hash), nil + return NewCidV0(hash), nil } - base, data, err := mbase.Decode(v) + _, data, err := mbase.Decode(v) if err != nil { - return 0, nil, err + return nil, err + } + + return Cast(data) +} + +// Extract the encoding from a Cid. If Decode on the same string did +// not return an error neither will this function. +func ExtractEncoding(v string) (mbase.Encoding, error) { + if len(v) < 2 { + return -1, ErrCidTooShort + } + + if len(v) == 46 && v[:2] == "Qm" { + return mbase.Base58BTC, nil } - cid, err := Cast(data) + encoding := mbase.Encoding(v[0]) + + // check encoding is valid + _, err := mbase.NewEncoder(encoding) + if err != nil { + return -1, err + } - return base, cid, err + return encoding, nil } func uvError(read int) error { diff --git a/format_test.go b/format_test.go index 7a92bb8..019d85c 100644 --- a/format_test.go +++ b/format_test.go @@ -55,13 +55,14 @@ func TestFmt(t *testing.T) { } func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, result string) { - base, cid, err := DecodeV2(cidStr) - if newBase != -1 { - base = newBase - } + cid, err := Decode(cidStr) if err != nil { t.Fatal(err) } + base := newBase + if newBase == -1 { + base, _ = ExtractEncoding(cidStr) + } str, err := Format(fmtStr, base, cid) if err != nil { t.Fatal(err) @@ -70,4 +71,3 @@ func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, re t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str)) } } - From 1c907dba61b84e16c35c00b1afa50dc2a43cd886 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson Date: Fri, 10 Aug 2018 17:27:58 -0400 Subject: [PATCH 8/8] Allocate bytes.Buffer directly on the stack. --- format.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/format.go b/format.go index b447c65..78bff4c 100644 --- a/format.go +++ b/format.go @@ -36,7 +36,7 @@ used. For Cid version 1 the multibase prefix is included. // documented in the FormatRef constant func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) { p := cid.Prefix() - out := new(bytes.Buffer) + var out bytes.Buffer var err error encoder, err := mb.NewEncoder(base) if err != nil { @@ -59,19 +59,19 @@ func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) { case 'B': // base code out.WriteByte(byte(base)) case 'v': // version string - fmt.Fprintf(out, "cidv%d", p.Version) + fmt.Fprintf(&out, "cidv%d", p.Version) case 'V': // version num - fmt.Fprintf(out, "%d", p.Version) + fmt.Fprintf(&out, "%d", p.Version) case 'c': // codec name out.WriteString(codecToString(p.Codec)) case 'C': // codec code - fmt.Fprintf(out, "%d", p.Codec) + fmt.Fprintf(&out, "%d", p.Codec) case 'h': // hash fun name out.WriteString(hashToString(p.MhType)) case 'H': // hash fun code - fmt.Fprintf(out, "%d", p.MhType) + fmt.Fprintf(&out, "%d", p.MhType) case 'L': // hash length - fmt.Fprintf(out, "%d", p.MhLength) + fmt.Fprintf(&out, "%d", p.MhLength) case 'm', 'M': // multihash encoded in base %b out.WriteString(encode(encoder, cid.Hash(), fmtStr[i] == 'M')) case 'd', 'D': // hash digest encoded in base %b @@ -89,7 +89,7 @@ func Format(fmtStr string, base mb.Encoding, cid *Cid) (string, error) { case 'S': // cid string without base prefix out.WriteString(encode(encoder, cid.Bytes(), true)) case 'P': // prefix - fmt.Fprintf(out, "cidv%d-%s-%s-%d", + fmt.Fprintf(&out, "cidv%d-%s-%s-%d", p.Version, codecToString(p.Codec), hashToString(p.MhType),