basex: inital commit

This commit is contained in:
Emery Hemingway 2014-06-24 21:41:10 -04:00
parent d24541ae9c
commit ccb40b5c67
2 changed files with 306 additions and 0 deletions

132
basex/basex.go Normal file
View File

@ -0,0 +1,132 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// Package basex implements encoding to an alphabet with an arbitrary length.
package basex
import (
"bytes"
"fmt"
"math/big"
//"math"
)
// An Encoding is an encoding/decoding scheme defined by an alphabet.
type Encoding struct {
alphabet []byte
radix *big.Int
}
const encodeBitcoin = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
// NewEncoding returns a new Encoding defined by the given alphabet.
func NewEncoding(alphabet string) *Encoding {
b := []byte(alphabet)
return &Encoding{b, big.NewInt(int64(len(b)))}
}
var BitcoinEncoding = NewEncoding(encodeBitcoin)
var bigZero = big.NewInt(0)
// EncodedLen returns a guaranteed maximum length in bytes an encoding can consume for
// an input buffer of length n.
//
// It estimates very poorly.
func (enc *Encoding) EncodedLen(n int) int {
if n == 0 {
return 0
}
// not right
return (0xff / len(enc.alphabet)) * n
}
// EncodedLen returns a guaranteed maximum length in bytes an encoding can represent for
// an input buffer of length n.
//
// It estimates very poorly.
func (enc *Encoding) DecodedLen(n int) int {
if n == 0 {
return 0
}
// not right
return (len(enc.alphabet) * n)
}
// Encode encodes src using the encoding enc, writing
// EncodedLen(len(src)) bytes to dst.
func (enc *Encoding) Encode(dst, src []byte) {
if len(src) == 0 {
return
}
x := new(big.Int)
x.SetBytes(src)
var i int
for x.Cmp(bigZero) > 0 {
mod := new(big.Int)
x.DivMod(x, enc.radix, mod)
dst[i] = enc.alphabet[mod.Int64()]
i++
}
// reverse
for j := 0; j < i; j++ {
i--
dst[j], dst[i] = dst[i], dst[j]
}
}
// EncodeToString returns the encoded form of src.
func (enc *Encoding) EncodeToString(src []byte) string {
buf := make([]byte, enc.EncodedLen(len(src)))
enc.Encode(buf, src)
if i := bytes.IndexByte(buf, 0x00); i != -1 {
return string(buf[:i])
}
return string(buf)
}
// Decode decodes src using the encoding enc. It writes at most
// DecodedLen(len(src)) bytes to dst and returns the number of bytes
// written. If src contains invalid encodings, it will return CorruptInputError.
func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
answer := big.NewInt(0)
j := big.NewInt(1)
for i := len(src) - 1; i >= 0; i-- {
tmp := bytes.IndexByte(enc.alphabet, src[i])
if tmp == -1 {
if src[i] == 0x00 {
continue
}
return 0, fmt.Errorf("character %q not in alphabet", src[i])
}
idx := big.NewInt(int64(tmp))
tmp1 := big.NewInt(0)
tmp1.Mul(j, idx)
answer.Add(answer, tmp1)
j.Mul(j, enc.radix)
}
tmp := answer.Bytes()
var numZeros int
for ; numZeros < len(src); numZeros++ {
if src[numZeros] != enc.alphabet[0] {
break
}
}
return copy(dst[numZeros:], tmp) + numZeros, nil
}
func (enc *Encoding) DecodeString(s string) ([]byte, error) {
src := []byte(s)
dst := make([]byte, enc.DecodedLen(len(src)))
n, err := enc.Decode(dst, src)
return dst[:n], err
}

174
basex/basex_test.go Normal file
View File

@ -0,0 +1,174 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
package basex
import (
"bytes"
"crypto/rand"
"encoding/hex"
"testing"
)
var stringTests = []struct {
in string
out string
}{
{"", ""},
{" ", "Z"},
{"-", "n"},
{"0", "q"},
{"1", "r"},
{"-1", "4SU"},
{"11", "4k8"},
{"abc", "ZiCa"},
{"1234598760", "3mJr7AoUXx2Wqd"},
{"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"},
{"00000000000000000000000000000000000000000000000000000000000000", "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"},
}
var invalidStringTests = []struct {
in string
out string
}{
{"0", ""},
{"O", ""},
{"I", ""},
{"l", ""},
{"3mJr0", ""},
{"O3yxU", ""},
{"3sNI", ""},
{"4kl8", ""},
{"0OIl", ""},
{"!@#$%^&*()-_=+~`", ""},
}
var hexTests = []struct {
in string
out string
}{
{"61", "2g"},
{"626262", "a3gV"},
{"636363", "aPEr"},
{"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"},
{"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"},
{"516b6fcd0f", "ABnLTmg"},
{"bf4f89001e670274dd", "3SEo3LWLoPntC"},
{"572e4794", "3EFU7m"},
{"ecac89cad93923c02321", "EJDM8drfXA6uyA"},
{"10c8511e", "Rt5zm"},
{"00000000000000000000", "1111111111"},
}
func TestBitcoin(t *testing.T) {
// BitcoinEncode tests
for x, test := range stringTests {
res := BitcoinEncoding.EncodeToString([]byte(test.in))
if string(res) != test.out {
t.Errorf("BitcoinEncode test #%d failed: got: %q want: %q",
x, res, test.out)
continue
}
}
// BitcoinDecode tests
for x, test := range hexTests {
b, err := hex.DecodeString(test.in)
if err != nil {
t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in)
continue
}
if res, _ := BitcoinEncoding.DecodeString(test.out); bytes.Equal(res, b) != true {
t.Errorf("BitcoinDecode test #%d failed: got: %q want: %q",
x, res, test.in)
continue
}
}
// BitcoinDecode with invalid input
for x, test := range invalidStringTests {
if res, _ := BitcoinEncoding.DecodeString(test.in); string(res) != test.out {
t.Errorf("BitcoinDecode invalidString test #%d failed: got: %q want: %q",
x, res, test.out)
continue
}
}
}
func TestEncodeMax(t *testing.T) {
encoding := NewEncoding("abc")
for i := 0; i < 4; i++ {
src := make([]byte, i)
dst := make([]byte, encoding.EncodedLen(i))
for j := 0; j < i; j++ {
src[j] = 0xff
}
encoding.Encode(dst, src)
//t.Errorf("%q", dst)
}
}
func TestDecodeMax(t *testing.T) {
encoding := NewEncoding("abc")
for i := 0; i < 4; i++ {
src := make([]byte, i)
dst := make([]byte, encoding.DecodedLen(i))
for j := 0; j < i; j++ {
src[j] = 'c'
}
encoding.Decode(dst, src)
//t.Errorf("%q", dst)
}
}
func TestRandom(t *testing.T) {
control := make([]byte, 1024)
rand.Read(control)
raw := make([]byte, 1024)
encoded := make([]byte, BitcoinEncoding.EncodedLen(1024))
BitcoinEncoding.Encode(encoded, control)
BitcoinEncoding.Decode(raw, encoded)
if !bytes.Equal(raw, control) {
t.Fatal("random test failed")
}
}
func TestRandomBase3(t *testing.T) {
encoding := NewEncoding("abc")
control := make([]byte, 64)
rand.Read(control)
raw := make([]byte, 64)
encoded := make([]byte, encoding.EncodedLen(64))
encoding.Encode(encoded, control)
encoding.Decode(raw, encoded)
if !bytes.Equal(raw, control) {
t.Fatal("random test failed")
}
}
func BenchmarkBitcoinEncoding(b *testing.B) {
src := make([]byte, 1024)
dst := make([]byte, BitcoinEncoding.EncodedLen(1024))
rand.Read(src)
b.ResetTimer()
for i := 0; i < b.N; i++ {
BitcoinEncoding.Encode(dst, src)
}
}
func BenchmarkBitcoinDecodeing(b *testing.B) {
control := make([]byte, 1024)
rand.Read(control)
dst := make([]byte, 1024)
src := make([]byte, BitcoinEncoding.EncodedLen(1024))
BitcoinEncoding.Encode(src, control)
b.ResetTimer()
for i := 0; i < b.N; i++ {
BitcoinEncoding.Decode(dst, src)
}
}