Do not build decode table for base85 until it is actually needed

Also make the tests actual tests
This commit is contained in:
Kovid Goyal
2023-09-04 21:32:25 +05:30
parent 0107d1cb89
commit 541c0cdde4
2 changed files with 75 additions and 66 deletions

View File

@@ -10,10 +10,11 @@ import (
"errors" "errors"
"io" "io"
"strconv" "strconv"
"sync"
) )
// 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~ // 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~
var b2c_table = [85]byte{ var encode = [85]byte{
// 0 1 2 3 4 5 6 7 8 9 A B C D E F // 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, //0 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, //0
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, //1 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, //1
@@ -23,22 +24,35 @@ var b2c_table = [85]byte{
0x60, 0x7B, 0x7C, 0x7D, 0x7E, 0x60, 0x7B, 0x7C, 0x7D, 0x7E,
} }
var encode [85]byte var decoder_array = sync.OnceValue(func() [256]byte {
var decode [256]byte var decode = [256]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
func init() { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
copy(encode[:], b2c_table[:]) 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
for i := 0; i < len(decode); i++ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
decode[i] = 0xFF 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
} }
for i := 0; i < len(encode); i++ { for i := 0; i < len(encode); i++ {
decode[encode[i]] = byte(i) decode[encode[i]] = byte(i)
} }
} return decode
})
// encodeChunk encodes 4 byte-chunk to 5 byte // encodeChunk encodes 4 byte-chunk to 5 byte
// if chunk size is less then 4, then it is padded before convertion. // if chunk size is less then 4, then it is padded before encoding.
// return written bytes or error // return written bytes
func encodeChunk(dst, src []byte) int { func encodeChunk(dst, src []byte) int {
if len(src) == 0 { if len(src) == 0 {
return 0 return 0
@@ -78,7 +92,7 @@ var decode_base = [5]uint32{85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1}
// decodeChunk decodes 5 byte-chunk to 4 byte // decodeChunk decodes 5 byte-chunk to 4 byte
// if chunk size is less then 5, then it is padded before convertion. // if chunk size is less then 5, then it is padded before convertion.
// return written bytes and error input index // return written bytes and error input index
func decodeChunk(dst, src []byte) (int, int) { func decodeChunk(decode *[256]byte, dst, src []byte) (int, int) {
if len(src) == 0 { if len(src) == 0 {
return 0, 0 return 0, 0
} }
@@ -150,16 +164,17 @@ func DecodeString(src string) ([]byte, error) {
func Decode(dst, src []byte) (int, error) { func Decode(dst, src []byte) (int, error) {
f := 0 f := 0
t := 0 t := 0
decode := decoder_array()
for len(src) > 0 { for len(src) > 0 {
if len(src) < 5 { if len(src) < 5 {
w, err := decodeChunk(dst, src) w, err := decodeChunk(&decode, dst, src)
if err > 0 { if err > 0 {
return t, CorruptInputError(f + err) return t, CorruptInputError(f + err)
} }
return t + w, nil return t + w, nil
} }
_, err := decodeChunk(dst[:4], src[:5]) _, err := decodeChunk(&decode, dst[:4], src[:5])
if err > 0 { if err > 0 {
return t, CorruptInputError(f + err) return t, CorruptInputError(f + err)
} else { } else {

View File

@@ -1,60 +1,54 @@
package base85 package base85
import ( import (
"testing"
"fmt" "fmt"
"testing"
"github.com/google/go-cmp/cmp"
) )
func TestChunked (t *testing.T) { var _ = fmt.Print
a := []byte("M")
fmt.Printf("a %#v\n", a)
b := make([]byte, EncodedLen(len(a)))
encodeChunk(b, a)
fmt.Printf("b %#v\n", b)
c := make([]byte, DecodedLen(len(b)))
decodeChunk(c, b)
fmt.Printf("c %#v\n", c)
a = []byte("Ma") type pair struct {
fmt.Printf("a %#v\n", a) src, encoded string
b = make([]byte, EncodedLen(len(a))) }
encodeChunk(b, a)
fmt.Printf("b %#v\n", b) func TestBase85(t *testing.T) {
c = make([]byte, DecodedLen(len(b))) dst, dst2 := [8]byte{}, [8]byte{}
decodeChunk(c, b) decode := decoder_array()
fmt.Printf("c %#v\n", c) var el int
for _, x := range []string{"M", "Ma", "Man", "Man "} {
a = []byte("Man") a := []byte(x)
fmt.Printf("a %#v\n", a) if el = encodeChunk(dst[:], a); el != EncodedLen(len(a)) {
b = make([]byte, EncodedLen(len(a))) t.Fatalf("Encoded len for %#v wrong: %d != %d", x, el, EncodedLen(len(a)))
encodeChunk(b, a) }
fmt.Printf("b %#v\n", b) encoded := dst[:el]
c = make([]byte, DecodedLen(len(b))) dl, bad_idx := decodeChunk(&decode, dst2[:], encoded)
decodeChunk(c, b) if bad_idx != 0 {
fmt.Printf("c %#v\n", c) t.Fatalf("Decode for %#v returned bad data at: %d (%#v)", x, bad_idx, encoded)
}
a = []byte("Man ") if dl != DecodedLen(len(encoded)) {
fmt.Printf("a %#v\n", a) t.Fatalf("Decoded len for %#v wrong: %d != %d", x, dl, DecodedLen(len(a)))
b = make([]byte, EncodedLen(len(a))) }
encodeChunk(b, a) decoded := string(dst2[:dl])
fmt.Printf("b %#v\n", b) if diff := cmp.Diff(x, decoded); diff != "" {
c = make([]byte, DecodedLen(len(b))) t.Fatalf("Roundtrip failed for %#v: %s", x, diff)
decodeChunk(c, b) }
fmt.Printf("c %#v\n", c) }
for _, p := range []pair{
a = []byte("Manual") {"M", "O#"},
fmt.Printf("a %#v\n", a) {"Manual", "O<`_zVQc"},
b = make([]byte, EncodedLen(len(a))) } {
n := Encode(b, a) q := EncodeToString([]byte(p.src))
fmt.Printf("b %#v %d\n", b, n) if diff := cmp.Diff(p.encoded, q); diff != "" {
c = make([]byte, DecodedLen(len(b))) t.Fatalf("Incorrect encoding of: %#v\n%s", p.src, diff)
n, _ = Decode(c, b) }
fmt.Printf("c %#v %d\n", c, n) sc, err := DecodeString(q)
if err != nil {
a = []byte("Manual") t.Fatalf("Failed to decode %#v with error: %s", p.src, err)
fmt.Printf("a %s\n", a) }
sb := EncodeToString(a) + "\"" if diff := cmp.Diff(p.src, string(sc)); diff != "" {
fmt.Printf("b %#v\n", sb) t.Fatalf("Failed to roundtrip %#v\n%s", p.src, diff)
sc, err := DecodeString(sb) }
fmt.Printf("c %s %s\n", sc, err) }
} }