63 Commits

Author SHA1 Message Date
Marten Seemann
ecc3acc587 checkout recursively 2021-05-21 10:47:20 -07:00
web3-bot
4c4da582c9 add .github/workflows/go-check.yml 2021-05-21 17:37:42 +00:00
web3-bot
7a4928f460 add .github/workflows/go-test.yml 2021-05-21 17:37:42 +00:00
web3-bot
dc8f758855 add .github/workflows/automerge.yml 2021-05-21 17:37:42 +00:00
web3-bot
5dd3f1a01a set Go version to 1.15 and run go mod tidy 2021-05-21 17:37:42 +00:00
web3-bot
2c89197b5a disable Travis 2021-05-21 17:37:41 +00:00
Marten Seemann
f68598dd02 Merge pull request #41 from multiformats/fix-staticcheck
fix staticcheck
2021-05-05 20:16:15 +07:00
Marten Seemann
daffaef0db fix staticcheck 2021-05-05 20:13:06 +07:00
Steven Allen
95cb7074c4 Merge pull request #39 from gammazero/fix-vet-warnings
Fix vet warnings about conversion of int to string
2021-02-26 15:53:14 -08:00
gammazero
2985033078 Fix vet warnings about conversion of int to string
Fixes issue #38
2020-11-19 17:38:08 -08:00
Steven Allen
e2260b5ff3 Merge pull request #36 from multiformats/feat/base36
Base36 implementation
2020-05-22 21:53:56 -07:00
Peter Rabbitson
158c1deff1 Base36 implementation 2020-05-23 04:34:44 +02:00
Steven Allen
ee5c23343b Merge pull request #34 from multiformats/feat/yet-more-tests
Even more tests/benchmarks, less repetition in-code
2020-05-22 19:33:40 -07:00
Peter Rabbitson
aa5d547a81 Even more tests/benchmarks, less repetition in-code 2020-05-23 02:51:29 +02:00
Peter Rabbitson
c03399abc2 Merge pull request #32 from multiformats/feat/MOAR-testz
Beef up tests before adding new codec
2020-05-22 01:56:19 +02:00
Peter Rabbitson
f5fced06c2 Beef up tests before adding new codec 2020-05-22 01:28:10 +02:00
Steven Allen
be9e91119a Merge pull request #31 from multiformats/chore/fix-tests
Remove GX, bump spec submodule, fix tests
2020-05-21 12:14:29 -07:00
Peter Rabbitson
6519131ca4 More GX removal 2020-05-21 20:59:16 +02:00
Peter Rabbitson
ef04c6a3db Remove GX, bump spec submodule, fix tests 2020-05-21 20:57:04 +02:00
Steven Allen
b84fc17b77 Merge pull request #30 from multiformats/feat/remove-base1
feat: remove base1 support
2019-07-26 11:56:38 -07:00
Steven Allen
f279c85720 feat: remove base1 support
see:

* https://github.com/multiformats/multibase/pull/48
* https://github.com/multiformats/multibase/pull/57
2019-07-26 11:56:01 -07:00
Jakub Sztandera
d63641945d Merge pull request #29 from multiformats/feat/gomod
Switch to multiformats/go-base32, introduce gomod
2019-02-27 13:28:37 +01:00
Jakub Sztandera
79803cc6b5 Merge pull request #27 from multiformats/fix/captain
README: remove out-of-date captain
2019-02-27 13:24:41 +01:00
Jakub Sztandera
4819336788 Switch to multiformats/go-base32, introduce gomod 2019-02-26 19:54:23 +01:00
Steven Allen
f25b77813c Merge pull request #28 from gowthamgts/base2
Added base2 implementation as per RFC
2019-02-18 18:49:39 -08:00
Gowtham Gopalakrishnan
be9178df09 changed shifting logic 2019-02-17 12:13:57 +05:30
Gowtham Gopalakrishnan
f7396abfab bitwise ops and left padding added 2019-02-14 19:34:11 +05:30
Gowtham Gopalakrishnan
fca1c65daf go fmt base2.go 2019-02-10 16:49:34 +05:30
Gowtham Gopalakrishnan
0a49bd57bb added base2 implementation as per RFC 2019-02-10 16:41:02 +05:30
Steven Allen
5d43951a20 README: remove out-of-date captain 2018-12-10 13:16:20 -08:00
Steven Allen
4cd2fef284 Merge pull request #24 from multiformats/testing/test-vectors
test against spec
2018-11-27 18:45:57 -08:00
Steven Allen
916e8af3d6 ci: standardize
* Add makfile
* Use standard CI scripts.
2018-11-27 18:44:08 -08:00
Steven Allen
007b57d388 Merge pull request #25 from gowthamgts/master
Typo fix and readibility improvements
2018-11-17 10:16:56 -08:00
Gowtham Gopalakrishnan
3cdc462d3f Typo fix and readibility improvements 2018-11-17 19:59:15 +05:30
Steven Allen
5b7719f2f5 test implementation against test-vectors 2018-11-15 15:25:58 -08:00
Steven Allen
c53ff45d4d gx: ignore tests when publishing as well 2018-11-15 15:25:51 -08:00
Steven Allen
8ab2e3688b add the spec as a submodule 2018-11-15 15:25:49 -08:00
Kevin Atkinson
bb91b53e56 gx publish 0.3.0 2018-08-31 20:32:43 -04:00
Steven Allen
964f55ad40 Merge pull request #23 from multiformats/kevina/encoder
Don't return an error on NewEncoder, panic on invalid encodings instead.
2018-09-01 00:20:04 +00:00
Kevin Atkinson
3b3047873d Use MustNewEncoder instead for version that does not panic. 2018-08-31 20:08:55 -04:00
Kevin Atkinson
2170058ef9 Add CheckEncoding function. 2018-08-31 18:59:07 -04:00
Kevin Atkinson
ac3d23441b Don't return an error on NewEncoder, panic on invalid encodings instead.
Most of the time this method will be used with a constant and the error
will be thrown away anyway.  By not returning an error we can use this
function to initialize global variables.  The function EncoderByName should
be used when working with user provided input and we care about the error.
2018-08-31 15:23:22 -04:00
Kevin Atkinson
ecd5d58562 Gofmt. 2018-08-31 15:01:41 -04:00
Steven Allen
b46f1c99f0 Merge pull request #22 from ianlopshire/master
Improve test coverage
2018-08-23 17:41:56 +00:00
Ian Lopshire
5fb339e88a Improve test coverage 2018-08-23 13:29:45 -04:00
Kevin Atkinson
caebba6233 gx publish 0.2.7 2018-07-27 17:34:30 -04:00
Kevin Atkinson
83915a874d Merge pull request #21 from multiformats/kevina/map
Enhance Multibase
2018-07-27 17:29:59 -04:00
Steven Allen
03643c33f5 ci: bump minimum go version 2018-07-27 17:02:59 -04:00
Kevin Atkinson
2eb83a994b Remove "magical" NewPrefix function, rename Prefix to Encoder. 2018-07-27 17:02:59 -04:00
Kevin Atkinson
5547437445 Enhance constructor for Prefix type. 2018-07-27 16:59:07 -04:00
Kevin Atkinson
a0557075ec Add prefix type that guarantees a valid multibase prefix. 2018-07-27 16:59:07 -04:00
Kevin Atkinson
3ea5c212ef Add maps for converting from the string repr. to the code and back. 2018-07-27 16:59:01 -04:00
Steven Allen
dfd5076869 gx publish 0.2.6 2017-12-18 10:32:30 -08:00
Steven Allen
3f064509b3 Merge pull request #20 from multiformats/feat/faster
Switch to a faster base58 library
2017-12-18 18:19:05 +00:00
Steven Allen
23774f6467 add benchmarks 2017-12-18 10:08:13 -08:00
Steven Allen
f61622a056 significantly faster b58
* 8-10x faster round trip.
* only 3 allocations each way.
2017-12-17 18:15:25 -08:00
Steven Allen
54686aabd4 gx publish 0.2.5 2017-08-28 20:11:12 -07:00
Steven Allen
307fbd560c Merge pull request #19 from multiformats/kevina/case-preserving
Make base16 and base32 encodings case preserving
2017-08-28 19:32:25 -07:00
Kevin Atkinson
36e6b228ae Move base16 and base32 specific bits to their own file. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
e81eb6473b Change length of test string to encode
to distinguish between padded and unpadded base32 encodings.
2017-08-28 21:33:06 -04:00
Kevin Atkinson
e98e07ec91 Make base16 encoding case preserving. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
8d1441db4b Make base32 encodings case preserving. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
5e86107a34 Update base32 dep. and bump go version to 1.8.3 in CI tests. 2017-08-28 21:32:51 -04:00
21 changed files with 815 additions and 121 deletions

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

@@ -0,0 +1,27 @@
# 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 ]
jobs:
automerge:
if: github.event.pull_request.user.login == 'web3-bot'
runs-on: ubuntu-latest
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

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

@@ -0,0 +1,43 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
name: Go checks
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-go@v2
with:
go-version: "1.16.x"
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@be534f007836a777104a15f2456cd1fffd3ddee8 # v2020.2.2
- name: Check that go.mod is tidy
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
run: go vet ./...
- name: staticcheck
if: ${{ success() || failure() }} # run this step even if the previous one failed
run: |
set -o pipefail
staticcheck ./... | sed -e 's@\(.*\)\.go@./\1.go@g'

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

@@ -0,0 +1,40 @@
# File managed by web3-bot. DO NOT EDIT.
# See https://github.com/protocol/.github/ for details.
on: [push, pull_request]
jobs:
unit:
strategy:
fail-fast: false
matrix:
os: [ "ubuntu", "windows", "macos" ]
go: [ "1.15.x", "1.16.x" ]
runs-on: ${{ matrix.os }}-latest
name: Unit tests (${{ 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 tests
run: go test -v -coverprofile coverage.txt ./...
- name: Run tests (32 bit)
if: ${{ matrix.os != 'macos' }} # can't run 32 bit tests on OSX.
env:
GOARCH: 386
run: go test -v ./...
- name: Run tests with race detector
if: ${{ matrix.os == 'ubuntu' }} # speed things up. Windows and OSX VMs are slow
run: go test -v -race ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@967e2b38a85a62bd61be5529ada27ebc109948c2 # v1.4.1
with:
file: coverage.txt
env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }}

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "spec"]
path = spec
url = https://github.com/multiformats/multibase.git

View File

@@ -1 +0,0 @@
0.2.4: Qme4T6BE4sQxg7ZouamF5M7Tx1ZFTqzcns7BkyQPXpoT99

View File

@@ -1,25 +0,0 @@
os:
- linux
language: go
go:
- 1.7
install:
- go get -u github.com/whyrusleeping/gx
- go get -u github.com/whyrusleeping/gx-go
- gx install
script:
- gx-go rewrite
- go test -race -coverprofile=unittest.coverprofile -covermode=atomic .
after_success:
- bash <(curl -s https://codecov.io/bash) -f unittest.coverprofile -F unittest
cache:
directories:
- $GOPATH/src/gx

7
Makefile Normal file
View File

@@ -0,0 +1,7 @@
test: deps
go test -count=1 -race -v ./...
export IPFS_API ?= v04x.ipfs.io
deps:
go get -t ./...

View File

@@ -18,28 +18,6 @@
go get github.com/multiformats/go-multibase
```
Note that `go-multibase` is packaged with Gx, so it is recommended to use Gx to install and use it (see Usage section).
## Usage
This module is packaged with [Gx](https://github.com/whyrusleeping/gx). In order to use it in your own project it is recommended that you:
```sh
go get -u github.com/whyrusleeping/gx
go get -u github.com/whyrusleeping/gx-go
cd <your-project-repository>
gx init
gx import github.com/multiformats/go-multibase
gx install --global
gx-go --rewrite
```
Please check [Gx](https://github.com/whyrusleeping/gx) and [Gx-go](https://github.com/whyrusleeping/gx-go) documentation for more information.
## Maintainers
Captain: [@whyrusleeping](https://github.com/whyrusleeping).
## Contribute
Contributions welcome. Please check out [the issues](https://github.com/multiformats/go-multibase/issues).

21
base16.go Normal file
View File

@@ -0,0 +1,21 @@
package multibase
func hexEncodeToStringUpper(src []byte) string {
dst := make([]byte, len(src)*2)
hexEncodeUpper(dst, src)
return string(dst)
}
var hexTableUppers = [16]byte{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F',
}
func hexEncodeUpper(dst, src []byte) int {
for i, v := range src {
dst[i*2] = hexTableUppers[v>>4]
dst[i*2+1] = hexTableUppers[v&0x0f]
}
return len(src) * 2
}

52
base2.go Normal file
View File

@@ -0,0 +1,52 @@
package multibase
import (
"fmt"
"strconv"
"strings"
)
// binaryEncodeToString takes an array of bytes and returns
// multibase binary representation
func binaryEncodeToString(src []byte) string {
dst := make([]byte, len(src)*8)
encodeBinary(dst, src)
return string(dst)
}
// encodeBinary takes the src and dst bytes and converts each
// byte to their binary rep using power reduction method
func encodeBinary(dst []byte, src []byte) {
for i, b := range src {
for j := 0; j < 8; j++ {
if b&(1<<uint(7-j)) == 0 {
dst[i*8+j] = '0'
} else {
dst[i*8+j] = '1'
}
}
}
}
// decodeBinaryString takes multibase binary representation
// and returns a byte array
func decodeBinaryString(s string) ([]byte, error) {
if len(s)&7 != 0 {
// prepend the padding
s = strings.Repeat("0", 8-len(s)&7) + s
}
data := make([]byte, len(s)>>3)
for i, dstIndex := 0, 0; i < len(s); i = i + 8 {
value, err := strconv.ParseInt(s[i:i+8], 2, 0)
if err != nil {
return nil, fmt.Errorf("error while conversion: %s", err)
}
data[dstIndex] = byte(value)
dstIndex++
}
return data, nil
}

17
base32.go Normal file
View File

@@ -0,0 +1,17 @@
package multibase
import (
b32 "github.com/multiformats/go-base32"
)
var base32StdLowerPad = b32.NewEncodingCI("abcdefghijklmnopqrstuvwxyz234567")
var base32StdLowerNoPad = base32StdLowerPad.WithPadding(b32.NoPadding)
var base32StdUpperPad = b32.NewEncodingCI("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
var base32StdUpperNoPad = base32StdUpperPad.WithPadding(b32.NoPadding)
var base32HexLowerPad = b32.NewEncodingCI("0123456789abcdefghijklmnopqrstuv")
var base32HexLowerNoPad = base32HexLowerPad.WithPadding(b32.NoPadding)
var base32HexUpperPad = b32.NewEncodingCI("0123456789ABCDEFGHIJKLMNOPQRSTUV")
var base32HexUpperNoPad = base32HexUpperPad.WithPadding(b32.NoPadding)

63
encoder.go Normal file
View File

@@ -0,0 +1,63 @@
package multibase
import (
"fmt"
)
// Encoder is a multibase encoding that is verified to be supported and
// supports an Encode method that does not return an error
type Encoder struct {
enc Encoding
}
// NewEncoder create a new Encoder from an Encoding
func NewEncoder(base Encoding) (Encoder, error) {
_, ok := EncodingToStr[base]
if !ok {
return Encoder{-1}, fmt.Errorf("unsupported multibase encoding: %d", base)
}
return Encoder{base}, nil
}
// MustNewEncoder is like NewEncoder but will panic if the encoding is
// invalid.
func MustNewEncoder(base Encoding) Encoder {
_, ok := EncodingToStr[base]
if !ok {
panic("Unsupported multibase encoding")
}
return Encoder{base}
}
// EncoderByName creates an encoder from a string, the string can
// either be the multibase name or single character multibase prefix
func EncoderByName(str string) (Encoder, error) {
var base Encoding
var ok bool
if len(str) == 0 {
return Encoder{-1}, fmt.Errorf("empty multibase encoding")
} else if len(str) == 1 {
base = Encoding(str[0])
_, ok = EncodingToStr[base]
} else {
base, ok = Encodings[str]
}
if !ok {
return Encoder{-1}, fmt.Errorf("unsupported multibase encoding: %s", str)
}
return Encoder{base}, nil
}
func (p Encoder) Encoding() Encoding {
return p.enc
}
// Encode encodes the multibase using the given Encoder.
func (p Encoder) Encode(data []byte) string {
str, err := Encode(p.enc, data)
if err != nil {
// should not happen
panic(err)
}
return str
}

51
encoder_test.go Normal file
View File

@@ -0,0 +1,51 @@
package multibase
import (
"testing"
)
func TestInvalidCode(t *testing.T) {
_, err := NewEncoder('q')
if err == nil {
t.Error("expected failure")
}
}
func TestInvalidName(t *testing.T) {
values := []string{"invalid", "", "q"}
for _, val := range values {
_, err := EncoderByName(val)
if err == nil {
t.Errorf("EncoderByName(%v) expected failure", val)
}
}
}
func TestEncoder(t *testing.T) {
for name, code := range Encodings {
encoder, err := NewEncoder(code)
if err != nil {
t.Fatal(err)
}
// Make sure the MustNewEncoder doesn't panic
MustNewEncoder(code)
str, err := Encode(code, sampleBytes)
if err != nil {
t.Fatal(err)
}
str2 := encoder.Encode(sampleBytes)
if str != str2 {
t.Errorf("encoded string mismatch: %s != %s", str, str2)
}
_, err = EncoderByName(name)
if err != nil {
t.Fatalf("EncoderByName(%s) failed: %v", name, err)
}
// Test that an encoder can be created from the single letter
// prefix
_, err = EncoderByName(str[0:1])
if err != nil {
t.Fatalf("EncoderByName(%s) failed: %v", str[0:1], err)
}
}
}

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module github.com/multiformats/go-multibase
go 1.15
require (
github.com/mr-tron/base58 v1.1.0
github.com/multiformats/go-base32 v0.0.3
github.com/multiformats/go-base36 v0.1.0
)

6
go.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
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=

View File

@@ -14,8 +14,8 @@ func main() {
}
var newBase multibase.Encoding
if baseParm := os.Args[1]; len(baseParm) != 0 {
newBase = multibase.Encoding(baseParm[0])
if baseParam := os.Args[1]; len(baseParam) != 0 {
newBase = multibase.Encoding(baseParam[0])
} else {
fmt.Fprintln(os.Stderr, "<new-base> is empty")
os.Exit(1)

View File

@@ -5,17 +5,18 @@ import (
"encoding/hex"
"fmt"
b58 "github.com/jbenet/go-base58"
b32 "github.com/whyrusleeping/base32"
b58 "github.com/mr-tron/base58/base58"
b32 "github.com/multiformats/go-base32"
b36 "github.com/multiformats/go-base36"
)
// Encoding identifies the type of base-encoding that a multibase is carrying.
type Encoding int
// These are the supported encodings
// These are the encodings specified in the standard, not are all
// supported yet
const (
Identity = 0x00
Base1 = '1'
Base2 = '0'
Base8 = '7'
Base10 = '9'
@@ -29,14 +30,49 @@ const (
Base32hexUpper = 'V'
Base32hexPad = 't'
Base32hexPadUpper = 'T'
Base58Flickr = 'Z'
Base36 = 'k'
Base36Upper = 'K'
Base58BTC = 'z'
Base58Flickr = 'Z'
Base64 = 'm'
Base64url = 'u'
Base64pad = 'M'
Base64urlPad = 'U'
)
// EncodingToStr is a map of the supported encoding, unsupported encoding
// specified in standard are left out
var EncodingToStr = map[Encoding]string{
0x00: "identity",
'0': "base2",
'f': "base16",
'F': "base16upper",
'b': "base32",
'B': "base32upper",
'c': "base32pad",
'C': "base32padupper",
'v': "base32hex",
'V': "base32hexupper",
't': "base32hexpad",
'T': "base32hexpadupper",
'k': "base36",
'K': "base36upper",
'z': "base58btc",
'Z': "base58flickr",
'm': "base64",
'u': "base64url",
'M': "base64pad",
'U': "base64urlpad",
}
var Encodings = map[string]Encoding{}
func init() {
for e, n := range EncodingToStr {
Encodings[n] = e
}
}
// ErrUnsupportedEncoding is returned when the selected encoding is not known or
// implemented.
var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported")
@@ -48,17 +84,33 @@ func Encode(base Encoding, data []byte) (string, error) {
switch base {
case Identity:
// 0x00 inside a string is OK in golang and causes no problems with the length calculation.
return string(Identity) + string(data), nil
case Base16, Base16Upper:
return string(rune(Identity)) + string(data), nil
case Base2:
return string(Base2) + binaryEncodeToString(data), nil
case Base16:
return string(Base16) + hex.EncodeToString(data), nil
case Base32, Base32Upper:
return string(Base32) + b32.RawStdEncoding.EncodeToString(data), nil
case Base32hex, Base32hexUpper:
return string(Base32hex) + b32.RawHexEncoding.EncodeToString(data), nil
case Base32pad, Base32padUpper:
return string(Base32pad) + b32.StdEncoding.EncodeToString(data), nil
case Base32hexPad, Base32hexPadUpper:
return string(Base32hexPad) + b32.HexEncoding.EncodeToString(data), nil
case Base16Upper:
return string(Base16Upper) + hexEncodeToStringUpper(data), nil
case Base32:
return string(Base32) + base32StdLowerNoPad.EncodeToString(data), nil
case Base32Upper:
return string(Base32Upper) + base32StdUpperNoPad.EncodeToString(data), nil
case Base32hex:
return string(Base32hex) + base32HexLowerNoPad.EncodeToString(data), nil
case Base32hexUpper:
return string(Base32hexUpper) + base32HexUpperNoPad.EncodeToString(data), nil
case Base32pad:
return string(Base32pad) + base32StdLowerPad.EncodeToString(data), nil
case Base32padUpper:
return string(Base32padUpper) + base32StdUpperPad.EncodeToString(data), nil
case Base32hexPad:
return string(Base32hexPad) + base32HexLowerPad.EncodeToString(data), nil
case Base32hexPadUpper:
return string(Base32hexPadUpper) + base32HexUpperPad.EncodeToString(data), nil
case Base36:
return string(Base36) + b36.EncodeToStringLc(data), nil
case Base36Upper:
return string(Base36Upper) + b36.EncodeToStringUc(data), nil
case Base58BTC:
return string(Base58BTC) + b58.EncodeAlphabet(data, b58.BTCAlphabet), nil
case Base58Flickr:
@@ -83,28 +135,38 @@ func Decode(data string) (Encoding, []byte, error) {
return 0, nil, fmt.Errorf("cannot decode multibase for zero length string")
}
switch data[0] {
enc := Encoding(data[0])
switch enc {
case Identity:
return Identity, []byte(data[1:]), nil
case Base2:
bytes, err := decodeBinaryString(data[1:])
return enc, bytes, err
case Base16, Base16Upper:
bytes, err := hex.DecodeString(data[1:])
return Base16, bytes, err
return enc, bytes, err
case Base32, Base32Upper:
bytes, err := b32.RawStdEncoding.DecodeString(data[1:])
return Base32, bytes, err
return enc, bytes, err
case Base32hex, Base32hexUpper:
bytes, err := b32.RawHexEncoding.DecodeString(data[1:])
return Base32hex, bytes, err
return enc, bytes, err
case Base32pad, Base32padUpper:
bytes, err := b32.StdEncoding.DecodeString(data[1:])
return Base32pad, bytes, err
return enc, bytes, err
case Base32hexPad, Base32hexPadUpper:
bytes, err := b32.HexEncoding.DecodeString(data[1:])
return Base32hexPad, bytes, err
return enc, bytes, err
case Base36, Base36Upper:
bytes, err := b36.DecodeString(data[1:])
return enc, bytes, err
case Base58BTC:
return Base58BTC, b58.DecodeAlphabet(data[1:], b58.BTCAlphabet), nil
bytes, err := b58.DecodeAlphabet(data[1:], b58.BTCAlphabet)
return Base58BTC, bytes, err
case Base58Flickr:
return Base58Flickr, b58.DecodeAlphabet(data[1:], b58.FlickrAlphabet), nil
bytes, err := b58.DecodeAlphabet(data[1:], b58.FlickrAlphabet)
return Base58Flickr, bytes, err
case Base64pad:
bytes, err := base64.StdEncoding.DecodeString(data[1:])
return Base64pad, bytes, err

View File

@@ -3,16 +3,47 @@ package multibase
import (
"bytes"
"math/rand"
"sort"
"testing"
)
var sampleBytes = []byte("Decentralize everything!!")
func TestMap(t *testing.T) {
for s, e := range Encodings {
s2 := EncodingToStr[e]
if s != s2 {
t.Errorf("round trip failed on encoding map: %s != %s", s, s2)
}
}
for e, s := range EncodingToStr {
e2 := Encodings[s]
if e != e2 {
t.Errorf("round trip failed on encoding map: '%c' != '%c'", e, e2)
}
}
}
var sampleBytes = []byte("Decentralize everything!!!")
var encodedSamples = map[Encoding]string{
Identity: string(0x00) + "Decentralize everything!!",
Base16: "f446563656e7472616c697a652065766572797468696e672121",
Base58BTC: "zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe",
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ==",
Base64urlPad: "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ==",
Identity: string(rune(0x00)) + "Decentralize everything!!!",
Base2: "00100010001100101011000110110010101101110011101000111001001100001011011000110100101111010011001010010000001100101011101100110010101110010011110010111010001101000011010010110111001100111001000010010000100100001",
Base16: "f446563656e7472616c697a652065766572797468696e67212121",
Base16Upper: "F446563656E7472616C697A652065766572797468696E67212121",
Base32: "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee",
Base32Upper: "BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE",
Base32pad: "cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee======",
Base32padUpper: "CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE======",
Base32hex: "v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144",
Base32hexUpper: "V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144",
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
Base36: "km552ng4dabi4neu1oo8l4i5mndwmpc3mkukwtxy9",
Base36Upper: "KM552NG4DABI4NEU1OO8L4I5MNDWMPC3MKUKWTXY9",
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
Base58Flickr: "Z36tpRGiQ9Endr7dHahm9xwQdhmoER4emaRVT",
Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
Base64urlPad: "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
}
func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string) {
@@ -22,7 +53,7 @@ func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string)
return
}
if actual != expected {
t.Errorf("encoding failed for %c (%d), expected: %s, got: %s", encoding, encoding, expected, actual)
t.Errorf("encoding failed for %c (%d / %s), expected: %s, got: %s", encoding, encoding, EncodingToStr[encoding], expected, actual)
}
}
@@ -41,45 +72,227 @@ func testDecode(t *testing.T, expectedEncoding Encoding, expectedBytes []byte, d
}
func TestEncode(t *testing.T) {
for encoding, data := range encodedSamples {
testEncode(t, encoding, sampleBytes, data)
for encoding := range EncodingToStr {
testEncode(t, encoding, sampleBytes, encodedSamples[encoding])
}
}
func TestDecode(t *testing.T) {
for encoding, data := range encodedSamples {
testDecode(t, encoding, sampleBytes, data)
for encoding := range EncodingToStr {
testDecode(t, encoding, sampleBytes, encodedSamples[encoding])
}
}
func TestRoundTrip(t *testing.T) {
buf := make([]byte, 17)
rand.Read(buf)
baseList := []Encoding{Identity, Base16, Base32, Base32hex, Base32pad, Base32hexPad, Base58BTC, Base58Flickr, Base64pad, Base64urlPad}
for _, base := range baseList {
enc, err := Encode(base, buf)
if err != nil {
t.Fatal(err)
for base := range EncodingToStr {
if int(base) == 0 {
// skip identity: any byte goes there
continue
}
e, out, err := Decode(enc)
if err != nil {
t.Fatal(err)
_, _, err := Decode(string(rune(base)) + "\u00A0")
if err == nil {
t.Fatal(EncodingToStr[base] + " decode should fail on low-unicode")
}
if e != base {
t.Fatal("got wrong encoding out")
_, _, err = Decode(string(rune(base)) + "\u1F4A8")
if err == nil {
t.Fatal(EncodingToStr[base] + " decode should fail on emoji")
}
if !bytes.Equal(buf, out) {
t.Fatal("input wasnt the same as output", buf, out)
_, _, err = Decode(string(rune(base)) + "!")
if err == nil {
t.Fatal(EncodingToStr[base] + " decode should fail on punctuation")
}
_, _, err = Decode(string(rune(base)) + "\xA0")
if err == nil {
t.Fatal(EncodingToStr[base] + " decode should fail on high-latin1")
}
}
buf := make([]byte, 137+16) // sufficiently large prime number of bytes + another 16 to test leading 0s
rand.Read(buf[16:])
for base := range EncodingToStr {
// test roundtrip from the full zero-prefixed buffer down to a single byte
for i := 0; i < len(buf); i++ {
// use a copy to verify we are not overwriting the supplied buffer
newBuf := make([]byte, len(buf)-i)
copy(newBuf, buf[i:])
enc, err := Encode(base, newBuf)
if err != nil {
t.Fatal(err)
}
e, out, err := Decode(enc)
if err != nil {
t.Fatal(err)
}
if e != base {
t.Fatal("got wrong encoding out")
}
if !bytes.Equal(newBuf, buf[i:]) {
t.Fatal("the provided buffer was modified", buf[i:], out)
}
if !bytes.Equal(buf[i:], out) {
t.Fatal("input wasnt the same as output", buf[i:], out)
}
// When we have 3 leading zeroes, do a few extra tests
// ( choice of leading zeroes is arbitrary - just cutting down on test permutations )
if i == 13 {
// if this is a case-insensitive codec semi-randomly swap case in enc and try again
name := EncodingToStr[base]
if name[len(name)-5:] == "upper" || Encodings[name+"upper"] > 0 {
caseTamperedEnc := []byte(enc)
for _, j := range []int{3, 5, 8, 13, 21, 23, 29, 47, 52} {
if caseTamperedEnc[j] >= 65 && caseTamperedEnc[j] <= 90 {
caseTamperedEnc[j] += 32
} else if caseTamperedEnc[j] >= 97 && caseTamperedEnc[j] <= 122 {
caseTamperedEnc[j] -= 32
}
}
e, out, err := Decode(string(caseTamperedEnc))
if err != nil {
t.Fatal(err)
}
if e != base {
t.Fatal("got wrong encoding out")
}
if !bytes.Equal(buf[i:], out) {
t.Fatal("input wasn't the same as output", buf[i:], out)
}
}
}
}
}
// Test that nothing overflows
maxValueBuf := make([]byte, 131)
for i := 0; i < len(maxValueBuf); i++ {
maxValueBuf[i] = 0xFF
}
for base := range EncodingToStr {
// test roundtrip from the complete buffer down to a single byte
for i := 0; i < len(maxValueBuf); i++ {
enc, err := Encode(base, maxValueBuf[i:])
if err != nil {
t.Fatal(err)
}
e, out, err := Decode(enc)
if err != nil {
t.Fatal(err)
}
if e != base {
t.Fatal("got wrong encoding out")
}
if !bytes.Equal(maxValueBuf[i:], out) {
t.Fatal("input wasn't the same as output", maxValueBuf[i:], out)
}
}
}
_, _, err := Decode("")
if err == nil {
t.Fatal("shouldnt be able to decode empty string")
t.Fatal("shouldn't be able to decode empty string")
}
}
var benchmarkBuf [36]byte // typical CID size
var benchmarkCodecs []string
func init() {
rand.Read(benchmarkBuf[:])
benchmarkCodecs = make([]string, 0, len(Encodings))
for n := range Encodings {
// // Only bench b36 and b58
// if len(n) < 6 || (n[4:6] != "36" && n[4:6] != "58") {
// continue
// }
benchmarkCodecs = append(benchmarkCodecs, n)
}
sort.Strings(benchmarkCodecs)
}
func BenchmarkRoundTrip(b *testing.B) {
b.ResetTimer()
for _, name := range benchmarkCodecs {
b.Run(name, func(b *testing.B) {
base := Encodings[name]
for i := 0; i < b.N; i++ {
enc, err := Encode(base, benchmarkBuf[:])
if err != nil {
b.Fatal(err)
}
e, out, err := Decode(enc)
if err != nil {
b.Fatal(err)
}
if e != base {
b.Fatal("got wrong encoding out")
}
if !bytes.Equal(benchmarkBuf[:], out) {
b.Fatal("input wasnt the same as output", benchmarkBuf, out)
}
}
})
}
}
func BenchmarkEncode(b *testing.B) {
b.ResetTimer()
for _, name := range benchmarkCodecs {
b.Run(name, func(b *testing.B) {
base := Encodings[name]
for i := 0; i < b.N; i++ {
_, err := Encode(base, benchmarkBuf[:])
if err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkDecode(b *testing.B) {
b.ResetTimer()
for _, name := range benchmarkCodecs {
b.Run(name, func(b *testing.B) {
enc, _ := Encode(Encodings[name], benchmarkBuf[:])
for i := 0; i < b.N; i++ {
_, _, err := Decode(enc)
if err != nil {
b.Fatal(err)
}
}
})
}
}

View File

@@ -3,28 +3,8 @@
"bugs": {
"url": "https://github.com/multiformats/go-multibase"
},
"gx": {
"dvcsimport": "github.com/multiformats/go-multibase"
},
"gxDependencies": [
{
"author": "whyrusleeping",
"hash": "QmZvZSVtvxak4dcTkhsQhqd1SQ6rg5UzaSTu62WfWKjj93",
"name": "base32",
"version": "0.0.1"
},
{
"author": "whyrusleeping",
"hash": "QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf",
"name": "go-base58",
"version": "0.0.0"
}
],
"gxVersion": "0.8.0",
"language": "go",
"license": "",
"name": "go-multibase",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "0.2.4"
"version": "0.3.0"
}

1
spec Submodule

Submodule spec added at 3806057e6f

147
spec_test.go Normal file
View File

@@ -0,0 +1,147 @@
package multibase
import (
"encoding/csv"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"unicode/utf8"
)
func TestSpec(t *testing.T) {
file, err := os.Open("spec/multibase.csv")
if err != nil {
t.Fatal(err)
}
defer file.Close()
reader := csv.NewReader(file)
reader.LazyQuotes = false
reader.FieldsPerRecord = 4
reader.TrimLeadingSpace = true
values, err := reader.ReadAll()
if err != nil {
t.Error(err)
}
expectedEncodings := make(map[Encoding]string, len(values)-1)
for _, v := range values[1:] {
encoding := v[0]
codeStr := v[1]
var code Encoding
if strings.HasPrefix(codeStr, "0x") {
i, err := strconv.ParseUint(codeStr[2:], 16, 64)
if err != nil {
t.Errorf("invalid multibase byte %q", codeStr)
continue
}
code = Encoding(i)
} else {
codeRune, length := utf8.DecodeRuneInString(codeStr)
if code == utf8.RuneError {
t.Errorf("multibase %q wasn't valid utf8", codeStr)
continue
}
if length != len(codeStr) {
t.Errorf("multibase %q wasn't a single character", codeStr)
continue
}
code = Encoding(codeRune)
}
expectedEncodings[code] = encoding
}
for name, enc := range Encodings {
expectedName, ok := expectedEncodings[enc]
if !ok {
t.Errorf("encoding %q (%c) not defined in the spec", name, enc)
continue
}
if expectedName != name {
t.Errorf("encoding %q (%c) has unexpected name %q", expectedName, enc, name)
}
}
}
func TestSpecVectors(t *testing.T) {
files, err := filepath.Glob("spec/tests/test[0-9]*.csv")
if err != nil {
t.Fatal(err)
}
for _, fname := range files {
t.Run(fname, func(t *testing.T) {
file, err := os.Open(fname)
if err != nil {
t.Error(err)
return
}
defer file.Close()
reader := csv.NewReader(file)
reader.LazyQuotes = false
reader.FieldsPerRecord = 2
reader.TrimLeadingSpace = true
values, err := reader.ReadAll()
if err != nil {
t.Error(err)
}
if len(values) == 0 {
t.Error("no test values")
return
}
header := values[0]
var decodeOnly bool
switch header[0] {
case "encoding":
case "non-canonical encoding":
decodeOnly = true
default:
t.Errorf("invalid test spec %q", fname)
return
}
testValue, err := strconv.Unquote("\"" + header[1] + "\"")
if err != nil {
t.Error("failed to unquote testcase:", err)
return
}
for _, testCase := range values[1:] {
encodingName := testCase[0]
expected := testCase[1]
t.Run(encodingName, func(t *testing.T) {
encoder, err := EncoderByName(encodingName)
if err != nil {
t.Skipf("skipping %s: not supported", encodingName)
return
}
if !decodeOnly {
t.Logf("encoding %q with %s", testValue, encodingName)
actual := encoder.Encode([]byte(testValue))
if expected != actual {
t.Errorf("expected %q, got %q", expected, actual)
}
}
t.Logf("decoding %q", expected)
encoding, decoded, err := Decode(expected)
if err != nil {
t.Error("failed to decode:", err)
return
}
expectedEncoding := Encodings[encodingName]
if encoding != expectedEncoding {
t.Errorf("expected encoding to be %c, got %c", expectedEncoding, encoding)
}
if string(decoded) != testValue {
t.Errorf("failed to decode %q to %q, got %q", expected, testValue, string(decoded))
}
})
}
})
}
}