refactor: split into subpackages and add AEAD/options/stream APIs with comprehensive tests
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
package encodingx
|
||||
|
||||
import (
|
||||
"encoding/ascii85"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLength = errors.New("base128: invalid length base128 string")
|
||||
ErrBit = errors.New("base128: high bit set in base128 string")
|
||||
)
|
||||
|
||||
var enctab = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~'")
|
||||
|
||||
var dectab = map[byte]byte{
|
||||
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
|
||||
'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
|
||||
'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23,
|
||||
'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31,
|
||||
'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39,
|
||||
'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47,
|
||||
'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
|
||||
'4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '!': 62, '#': 63,
|
||||
'$': 64, '%': 65, '&': 66, '(': 67, ')': 68, '*': 69, '+': 70, ',': 71,
|
||||
'.': 72, '/': 73, ':': 74, ';': 75, '<': 76, '=': 77, '>': 78, '?': 79,
|
||||
'@': 80, '[': 81, ']': 82, '^': 83, '_': 84, '`': 85, '{': 86, '|': 87,
|
||||
'}': 88, '~': 89, '\'': 90,
|
||||
}
|
||||
|
||||
func Base91EncodeToString(d []byte) string {
|
||||
return string(Base91Encode(d))
|
||||
}
|
||||
|
||||
func Base91Encode(d []byte) []byte {
|
||||
var n, b uint
|
||||
var o []byte
|
||||
|
||||
for i := 0; i < len(d); i++ {
|
||||
b |= uint(d[i]) << n
|
||||
n += 8
|
||||
if n > 13 {
|
||||
v := b & 8191
|
||||
if v > 88 {
|
||||
b >>= 13
|
||||
n -= 13
|
||||
} else {
|
||||
v = b & 16383
|
||||
b >>= 14
|
||||
n -= 14
|
||||
}
|
||||
o = append(o, enctab[v%91], enctab[v/91])
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
o = append(o, enctab[b%91])
|
||||
if n > 7 || b > 90 {
|
||||
o = append(o, enctab[b/91])
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func Base91DecodeString(d string) []byte {
|
||||
return Base91Decode([]byte(d))
|
||||
}
|
||||
|
||||
func Base91Decode(d []byte) []byte {
|
||||
var b, n uint
|
||||
var o []byte
|
||||
v := -1
|
||||
|
||||
for i := 0; i < len(d); i++ {
|
||||
c, ok := dectab[d[i]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if v < 0 {
|
||||
v = int(c)
|
||||
} else {
|
||||
v += int(c) * 91
|
||||
b |= uint(v) << n
|
||||
if v&8191 > 88 {
|
||||
n += 13
|
||||
} else {
|
||||
n += 14
|
||||
}
|
||||
o = append(o, byte(b&255))
|
||||
b >>= 8
|
||||
n -= 8
|
||||
for n > 7 {
|
||||
o = append(o, byte(b&255))
|
||||
b >>= 8
|
||||
n -= 8
|
||||
}
|
||||
v = -1
|
||||
}
|
||||
}
|
||||
if v+1 > 0 {
|
||||
o = append(o, byte((b|uint(v)<<n)&255))
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func Base128Encode(dst, src []byte) int {
|
||||
ret := Base128EncodedLen(len(src))
|
||||
if len(dst) < ret {
|
||||
panic("dst has insufficient length")
|
||||
}
|
||||
buf := dst[:0]
|
||||
whichByte := uint(1)
|
||||
bufByte := byte(0)
|
||||
for _, v := range src {
|
||||
buf = append(buf, bufByte|(v>>whichByte))
|
||||
bufByte = (v & byte((1<<whichByte)-1)) << (7 - whichByte)
|
||||
if whichByte == 7 {
|
||||
buf = append(buf, bufByte)
|
||||
bufByte = 0
|
||||
whichByte = 0
|
||||
}
|
||||
whichByte++
|
||||
}
|
||||
buf = append(buf, bufByte)
|
||||
return ret
|
||||
}
|
||||
|
||||
func Base128Decode(dst, src []byte) (int, error) {
|
||||
dLen := Base128DecodedLen(len(src))
|
||||
if Base128EncodedLen(dLen) != len(src) {
|
||||
return 0, ErrLength
|
||||
}
|
||||
if len(dst) < dLen {
|
||||
panic("dst has insufficient length")
|
||||
}
|
||||
buf := dst[:0]
|
||||
whichByte := uint(1)
|
||||
bufByte := byte(0)
|
||||
for _, v := range src {
|
||||
if (v & 0x80) != 0 {
|
||||
return len(buf), ErrBit
|
||||
}
|
||||
if whichByte > 1 {
|
||||
buf = append(buf, bufByte|(v>>(8-whichByte)))
|
||||
}
|
||||
bufByte = v << whichByte
|
||||
if whichByte == 8 {
|
||||
whichByte = 0
|
||||
}
|
||||
whichByte++
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func Base128DecodeString(s string) ([]byte, error) {
|
||||
src := []byte(s)
|
||||
dst := make([]byte, Base128DecodedLen(len(src)))
|
||||
n, err := Base128Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
||||
|
||||
func Base128DecodedLen(encLen int) int {
|
||||
return encLen * 7 / 8
|
||||
}
|
||||
|
||||
func Base128EncodedLen(dataLen int) int {
|
||||
return ((dataLen * 8) + 6) / 7
|
||||
}
|
||||
|
||||
func Base128EncodeToString(src []byte) string {
|
||||
dst := make([]byte, Base128EncodedLen(len(src)))
|
||||
Base128Encode(dst, src)
|
||||
return string(dst)
|
||||
}
|
||||
|
||||
func Base64Encode(bstr []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(bstr)
|
||||
}
|
||||
|
||||
func Base64Decode(str string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(str)
|
||||
}
|
||||
|
||||
func Base85Encode(bstr []byte) string {
|
||||
out := make([]byte, ascii85.MaxEncodedLen(len(bstr)))
|
||||
n := ascii85.Encode(out, bstr)
|
||||
return string(out[:n])
|
||||
}
|
||||
|
||||
func Base85Decode(str string) ([]byte, error) {
|
||||
out := make([]byte, len(str))
|
||||
n, _, err := ascii85.Decode(out, []byte(str), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out[:n], nil
|
||||
}
|
||||
|
||||
func Base85EncodeFile(src, dst string, progress func(float64)) error {
|
||||
fpsrc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpsrc.Close()
|
||||
|
||||
stat, err := fpsrc.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpdst, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpdst.Close()
|
||||
|
||||
enc := ascii85.NewEncoder(fpdst)
|
||||
defer enc.Close()
|
||||
|
||||
var sum int64
|
||||
buf := make([]byte, 1000*1024)
|
||||
for {
|
||||
n, readErr := fpsrc.Read(buf)
|
||||
if n > 0 {
|
||||
sum += int64(n)
|
||||
if _, err := enc.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
reportProgress(progress, sum, stat.Size())
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Base85DecodeFile(src, dst string, progress func(float64)) error {
|
||||
fpsrc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpsrc.Close()
|
||||
|
||||
stat, err := fpsrc.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counter := &countingReader{r: fpsrc}
|
||||
dec := ascii85.NewDecoder(counter)
|
||||
|
||||
fpdst, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpdst.Close()
|
||||
|
||||
buf := make([]byte, 1250*1024)
|
||||
for {
|
||||
n, readErr := dec.Read(buf)
|
||||
if n > 0 {
|
||||
if _, err := fpdst.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
reportProgress(progress, counter.n, stat.Size())
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Base64EncodeFile(src, dst string, progress func(float64)) error {
|
||||
fpsrc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpsrc.Close()
|
||||
|
||||
stat, err := fpsrc.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpdst, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpdst.Close()
|
||||
|
||||
enc := base64.NewEncoder(base64.StdEncoding, fpdst)
|
||||
defer enc.Close()
|
||||
|
||||
var sum int64
|
||||
buf := make([]byte, 1024*1024)
|
||||
for {
|
||||
n, readErr := fpsrc.Read(buf)
|
||||
if n > 0 {
|
||||
sum += int64(n)
|
||||
if _, err := enc.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
reportProgress(progress, sum, stat.Size())
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Base64DecodeFile(src, dst string, progress func(float64)) error {
|
||||
fpsrc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpsrc.Close()
|
||||
|
||||
stat, err := fpsrc.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counter := &countingReader{r: fpsrc}
|
||||
dec := base64.NewDecoder(base64.StdEncoding, counter)
|
||||
|
||||
fpdst, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fpdst.Close()
|
||||
|
||||
buf := make([]byte, 1024*1024)
|
||||
for {
|
||||
n, readErr := dec.Read(buf)
|
||||
if n > 0 {
|
||||
if _, err := fpdst.Write(buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
reportProgress(progress, counter.n, stat.Size())
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type countingReader struct {
|
||||
r io.Reader
|
||||
n int64
|
||||
}
|
||||
|
||||
func (c *countingReader) Read(p []byte) (int, error) {
|
||||
n, err := c.r.Read(p)
|
||||
c.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func reportProgress(progress func(float64), current, total int64) {
|
||||
if progress == nil {
|
||||
return
|
||||
}
|
||||
if total <= 0 {
|
||||
progress(100)
|
||||
return
|
||||
}
|
||||
progress(float64(current) / float64(total) * 100)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package encodingx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBase64AndBase85RoundTrip(t *testing.T) {
|
||||
plain := []byte("encoding-roundtrip")
|
||||
|
||||
b64 := Base64Encode(plain)
|
||||
d64, err := Base64Decode(b64)
|
||||
if err != nil {
|
||||
t.Fatalf("Base64Decode failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(d64, plain) {
|
||||
t.Fatalf("base64 mismatch")
|
||||
}
|
||||
|
||||
b85 := Base85Encode(plain)
|
||||
d85, err := Base85Decode(b85)
|
||||
if err != nil {
|
||||
t.Fatalf("Base85Decode failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(d85, plain) {
|
||||
t.Fatalf("base85 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase91AndBase128RoundTrip(t *testing.T) {
|
||||
plain := []byte("base91-base128")
|
||||
|
||||
e91 := Base91Encode(plain)
|
||||
d91 := Base91Decode(e91)
|
||||
if !bytes.Equal(d91, plain) {
|
||||
t.Fatalf("base91 mismatch")
|
||||
}
|
||||
|
||||
e128 := Base128EncodeToString(plain)
|
||||
d128, err := Base128DecodeString(e128)
|
||||
if err != nil {
|
||||
t.Fatalf("Base128DecodeString failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(d128, plain) {
|
||||
t.Fatalf("base128 mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase128DecodeInvalid(t *testing.T) {
|
||||
_, err := Base128DecodeString(string([]byte{0x80}))
|
||||
if err == nil {
|
||||
t.Fatalf("expected base128 decode error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64AndBase85FileRoundTrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := filepath.Join(dir, "src.bin")
|
||||
b64 := filepath.Join(dir, "src.b64")
|
||||
dst64 := filepath.Join(dir, "src.64.out")
|
||||
b85 := filepath.Join(dir, "src.b85")
|
||||
dst85 := filepath.Join(dir, "src.85.out")
|
||||
|
||||
plain := []byte("file-roundtrip-encoding")
|
||||
if err := os.WriteFile(src, plain, 0o644); err != nil {
|
||||
t.Fatalf("WriteFile failed: %v", err)
|
||||
}
|
||||
|
||||
if err := Base64EncodeFile(src, b64, nil); err != nil {
|
||||
t.Fatalf("Base64EncodeFile failed: %v", err)
|
||||
}
|
||||
if err := Base64DecodeFile(b64, dst64, nil); err != nil {
|
||||
t.Fatalf("Base64DecodeFile failed: %v", err)
|
||||
}
|
||||
got64, err := os.ReadFile(dst64)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile dst64 failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got64, plain) {
|
||||
t.Fatalf("base64 file roundtrip mismatch")
|
||||
}
|
||||
|
||||
if err := Base85EncodeFile(src, b85, nil); err != nil {
|
||||
t.Fatalf("Base85EncodeFile failed: %v", err)
|
||||
}
|
||||
if err := Base85DecodeFile(b85, dst85, nil); err != nil {
|
||||
t.Fatalf("Base85DecodeFile failed: %v", err)
|
||||
}
|
||||
got85, err := os.ReadFile(dst85)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile dst85 failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got85, plain) {
|
||||
t.Fatalf("base85 file roundtrip mismatch")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package encodingx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzBase128RoundTrip(f *testing.F) {
|
||||
f.Add([]byte("base128"))
|
||||
f.Add([]byte{})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
e := Base128EncodeToString(data)
|
||||
d, err := Base128DecodeString(e)
|
||||
if err != nil {
|
||||
t.Fatalf("Base128DecodeString failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(d, data) {
|
||||
t.Fatalf("base128 roundtrip mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzBase91RoundTrip(f *testing.F) {
|
||||
f.Add([]byte("base91"))
|
||||
f.Add([]byte{})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
e := Base91Encode(data)
|
||||
d := Base91Decode(e)
|
||||
if !bytes.Equal(d, data) {
|
||||
t.Fatalf("base91 roundtrip mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user