Even more tests/benchmarks, less repetition in-code
This commit is contained in:
33
multibase.go
33
multibase.go
@@ -37,29 +37,8 @@ const (
|
|||||||
Base64urlPad = 'U'
|
Base64urlPad = 'U'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encodings is a map of the supported encoding, unsupported encoding
|
// EncodingToStr is a map of the supported encoding, unsupported encoding
|
||||||
// specified in standard are left out
|
// specified in standard are left out
|
||||||
var Encodings = map[string]Encoding{
|
|
||||||
"identity": 0x00,
|
|
||||||
"base2": '0',
|
|
||||||
"base16": 'f',
|
|
||||||
"base16upper": 'F',
|
|
||||||
"base32": 'b',
|
|
||||||
"base32upper": 'B',
|
|
||||||
"base32pad": 'c',
|
|
||||||
"base32padupper": 'C',
|
|
||||||
"base32hex": 'v',
|
|
||||||
"base32hexupper": 'V',
|
|
||||||
"base32hexpad": 't',
|
|
||||||
"base32hexpadupper": 'T',
|
|
||||||
"base58flickr": 'Z',
|
|
||||||
"base58btc": 'z',
|
|
||||||
"base64": 'm',
|
|
||||||
"base64url": 'u',
|
|
||||||
"base64pad": 'M',
|
|
||||||
"base64urlpad": 'U',
|
|
||||||
}
|
|
||||||
|
|
||||||
var EncodingToStr = map[Encoding]string{
|
var EncodingToStr = map[Encoding]string{
|
||||||
0x00: "identity",
|
0x00: "identity",
|
||||||
'0': "base2",
|
'0': "base2",
|
||||||
@@ -73,14 +52,22 @@ var EncodingToStr = map[Encoding]string{
|
|||||||
'V': "base32hexupper",
|
'V': "base32hexupper",
|
||||||
't': "base32hexpad",
|
't': "base32hexpad",
|
||||||
'T': "base32hexpadupper",
|
'T': "base32hexpadupper",
|
||||||
'Z': "base58flickr",
|
|
||||||
'z': "base58btc",
|
'z': "base58btc",
|
||||||
|
'Z': "base58flickr",
|
||||||
'm': "base64",
|
'm': "base64",
|
||||||
'u': "base64url",
|
'u': "base64url",
|
||||||
'M': "base64pad",
|
'M': "base64pad",
|
||||||
'U': "base64urlpad",
|
'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
|
// ErrUnsupportedEncoding is returned when the selected encoding is not known or
|
||||||
// implemented.
|
// implemented.
|
||||||
var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported")
|
var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package multibase
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ var encodedSamples = map[Encoding]string{
|
|||||||
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
|
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
|
||||||
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
|
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
|
||||||
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
|
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
|
||||||
|
Base58Flickr: "Z36tpRGiQ9Endr7dHahm9xwQdhmoER4emaRVT",
|
||||||
Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
|
Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
|
||||||
Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
|
Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
|
||||||
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
|
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
|
||||||
@@ -49,7 +51,7 @@ func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if actual != expected {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,25 +70,53 @@ func testDecode(t *testing.T, expectedEncoding Encoding, expectedBytes []byte, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
func TestEncode(t *testing.T) {
|
||||||
for encoding, data := range encodedSamples {
|
for encoding := range EncodingToStr {
|
||||||
testEncode(t, encoding, sampleBytes, data)
|
testEncode(t, encoding, sampleBytes, encodedSamples[encoding])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
for encoding, data := range encodedSamples {
|
for encoding := range EncodingToStr {
|
||||||
testDecode(t, encoding, sampleBytes, data)
|
testDecode(t, encoding, sampleBytes, encodedSamples[encoding])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoundTrip(t *testing.T) {
|
func TestRoundTrip(t *testing.T) {
|
||||||
buf := make([]byte, 37+16) // sufficiently large prime number of bytes (37) + another 16 to test leading 0s
|
|
||||||
|
for base := range EncodingToStr {
|
||||||
|
if int(base) == 0 {
|
||||||
|
// skip identity: any byte goes there
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := Decode(string(base) + "\u00A0")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(EncodingToStr[base] + " decode should fail on low-unicode")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = Decode(string(base) + "\u1F4A8")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(EncodingToStr[base] + " decode should fail on emoji")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = Decode(string(base) + "!")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(EncodingToStr[base] + " decode should fail on punctuation")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = Decode(string(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:])
|
rand.Read(buf[16:])
|
||||||
|
|
||||||
for base := range EncodingToStr {
|
for base := range EncodingToStr {
|
||||||
|
|
||||||
// test roundtrip from the full zero-prefixed buffer down to a single byte
|
// test roundtrip from the full zero-prefixed buffer down to a single byte
|
||||||
for i := 0; i <= len(buf)-1; i++ {
|
for i := 0; i < len(buf); i++ {
|
||||||
|
|
||||||
// use a copy to verify we are not overwriting the supplied buffer
|
// use a copy to verify we are not overwriting the supplied buffer
|
||||||
newBuf := make([]byte, len(buf)-i)
|
newBuf := make([]byte, len(buf)-i)
|
||||||
@@ -114,14 +144,17 @@ func TestRoundTrip(t *testing.T) {
|
|||||||
t.Fatal("input wasnt the same as output", buf[i:], out)
|
t.Fatal("input wasnt the same as output", buf[i:], out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we have 3 leading zeroes, and this is a case-insensitive codec
|
// When we have 3 leading zeroes, do a few extra tests
|
||||||
// semi-randomly swap case in enc and try again
|
// ( choice of leading zeroes is arbitrary - just cutting down on test permutations )
|
||||||
|
|
||||||
if i == 13 {
|
if i == 13 {
|
||||||
|
|
||||||
|
// if this is a case-insensitive codec semi-randomly swap case in enc and try again
|
||||||
name := EncodingToStr[base]
|
name := EncodingToStr[base]
|
||||||
if name[len(name)-5:] == "upper" || Encodings[name+"upper"] > 0 {
|
if name[len(name)-5:] == "upper" || Encodings[name+"upper"] > 0 {
|
||||||
caseTamperedEnc := []byte(enc)
|
caseTamperedEnc := []byte(enc)
|
||||||
|
|
||||||
for _, j := range []int{3, 5, 8, 13, 21, 23, 29} {
|
for _, j := range []int{3, 5, 8, 13, 21, 23, 29, 47, 52} {
|
||||||
if caseTamperedEnc[j] >= 65 && caseTamperedEnc[j] <= 90 {
|
if caseTamperedEnc[j] >= 65 && caseTamperedEnc[j] <= 90 {
|
||||||
caseTamperedEnc[j] += 32
|
caseTamperedEnc[j] += 32
|
||||||
} else if caseTamperedEnc[j] >= 97 && caseTamperedEnc[j] <= 122 {
|
} else if caseTamperedEnc[j] >= 97 && caseTamperedEnc[j] <= 122 {
|
||||||
@@ -138,49 +171,77 @@ func TestRoundTrip(t *testing.T) {
|
|||||||
t.Fatal("got wrong encoding out")
|
t.Fatal("got wrong encoding out")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(buf[i:], out) {
|
if !bytes.Equal(buf[i:], out) {
|
||||||
t.Fatal("input wasnt the same as output", 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("")
|
_, _, err := Decode("")
|
||||||
if err == nil {
|
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) {
|
func BenchmarkRoundTrip(b *testing.B) {
|
||||||
buf := make([]byte, 32)
|
|
||||||
rand.Read(buf)
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
bases := map[string]Encoding{
|
for _, name := range benchmarkCodecs {
|
||||||
"Identity": Identity,
|
|
||||||
"Base2": Base2,
|
|
||||||
"Base16": Base16,
|
|
||||||
"Base16Upper": Base16Upper,
|
|
||||||
"Base32": Base32,
|
|
||||||
"Base32Upper": Base32Upper,
|
|
||||||
"Base32pad": Base32pad,
|
|
||||||
"Base32padUpper": Base32padUpper,
|
|
||||||
"Base32hex": Base32hex,
|
|
||||||
"Base32hexUpper": Base32hexUpper,
|
|
||||||
"Base32hexPad": Base32hexPad,
|
|
||||||
"Base32hexPadUpper": Base32hexPadUpper,
|
|
||||||
"Base58Flickr": Base58Flickr,
|
|
||||||
"Base58BTC": Base58BTC,
|
|
||||||
"Base64": Base64,
|
|
||||||
"Base64url": Base64url,
|
|
||||||
"Base64pad": Base64pad,
|
|
||||||
"Base64urlPad": Base64urlPad,
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, base := range bases {
|
|
||||||
b.Run(name, func(b *testing.B) {
|
b.Run(name, func(b *testing.B) {
|
||||||
|
base := Encodings[name]
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
enc, err := Encode(base, buf)
|
enc, err := Encode(base, benchmarkBuf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -194,8 +255,40 @@ func BenchmarkRoundTrip(b *testing.B) {
|
|||||||
b.Fatal("got wrong encoding out")
|
b.Fatal("got wrong encoding out")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(buf, out) {
|
if !bytes.Equal(benchmarkBuf[:], out) {
|
||||||
b.Fatal("input wasnt the same as output", buf, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user