Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
nikolaydubina committed Nov 2, 2024
1 parent 244b979 commit 17dd135
Show file tree
Hide file tree
Showing 10 changed files with 414 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @nikolaydubina
38 changes: 38 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: test

on: [push]

permissions: read-all

jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: code
uses: actions/checkout@v4

- name: go
uses: actions/setup-go@v5
with:
go-version: ^1.22

- name: test
run: |
go install github.com/jstemmer/go-junit-report/v2@latest
go test -coverprofile=coverage.out -covermode=atomic -cover -json -v ./... 2>&1 | go-junit-report -set-exit-code > tests.xml
- name: fuzz
run: go test -list . | grep Fuzz | xargs -P 8 -I {} go test -fuzz {} -fuzztime 15s .

- name: Upload test results to Codecov
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: tests.xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.out
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# base32quant
fast `base32` encoding into `uint32` and `uint64`

```bash
$ go test -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: github.com/ndx-technologies/mm-go/base32
cpu: Apple M3 Max
BenchmarkEncodeDecode_Standard/uint32-16 32816378 36.46 ns/op 16 B/op 2 allocs/op
BenchmarkEncodeDecode_Standard/uint64-16 23218821 50.55 ns/op 24 B/op 2 allocs/op
BenchmarkEncodeDecode/uint32-16 128822170 9.676 ns/op 0 B/op 0 allocs/op
BenchmarkEncodeDecode/uint64-16 93620481 12.96 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/ndx-technologies/mm-go/base32 7.052s
```
39 changes: 39 additions & 0 deletions base32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package base32quant

import "errors"

var (
ErrInvalidLength = errors.New("invalid length")
ErrInvalidCharacter = errors.New("invalid character")
)

var digits = [...]byte{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '2', '3', '4', '5', '6', '7',
}

func Encode[T uint32 | uint64](b []byte, n T) error {
for i := range len(b) {
b[len(b)-i-1], n = digits[n&0x1f], n>>5
}
return nil
}

func Decode[T uint32 | uint64](b []byte) (n T, err error) {
for i := 0; i < len(b); i++ {
n <<= 5
switch {
case b[i] >= 'a' && b[i] <= 'z':
n |= T(b[i] - 'a')
case b[i] >= 'A' && b[i] <= 'Z':
n |= T(b[i] - 'A')
case b[i] >= '2' && b[i] <= '7':
n |= T(b[i] - '2' + 26)
default:
return 0, ErrInvalidCharacter
}
}
return n, nil
}
162 changes: 162 additions & 0 deletions base32_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package base32quant_test

import (
"encoding/base32"
"encoding/binary"
"fmt"
"testing"

base32quant "github.com/ndx-technologies/base32quant"
)

func Benchmark__________________________________(b *testing.B) { b.SkipNow() }

func BenchmarkEncodeDecode_Standard(b *testing.B) {
enc := base32.StdEncoding.WithPadding(base32.NoPadding)

b.Run("uint32", func(b *testing.B) {
v := make([]byte, 7)

b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range v {
v[j] = 0
}

enc.Encode(v, binary.BigEndian.AppendUint32(nil, uint32(i)))

g := [4]byte{}
_, err := enc.Decode(g[:], v)

o := binary.BigEndian.Uint32(g[:])

if o != uint32(i) || err != nil {
b.Fatalf("invalid value: %d %d %v, err: %s", o, i, v, err)
}
}
})

b.Run("uint64", func(b *testing.B) {
v := make([]byte, 13)

b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range v {
v[j] = 0
}

enc.Encode(v, binary.BigEndian.AppendUint64(nil, uint64(i)))

g := [8]byte{}
_, err := enc.Decode(g[:], v)

o := binary.BigEndian.Uint64(g[:])

if o != uint64(i) || err != nil {
b.Fatalf("invalid value: %d %d %v, err: %s", o, i, v, err)
}
}
})
}

func BenchmarkEncodeDecode(b *testing.B) {
b.Run("uint32", func(b *testing.B) {
v := make([]byte, 7)

b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range v {
v[j] = 0
}

base32quant.Encode(v, uint32(i))
o, err := base32quant.Decode[uint32](v)

if o != uint32(i) || err != nil {
b.Fatalf("invalid value: %d %d %v, err: %s", o, i, v, err)
}
}
})

b.Run("uint64", func(b *testing.B) {
v := make([]byte, 13)

b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := range v {
v[j] = 0
}

base32quant.Encode(v, uint64(i))
o, err := base32quant.Decode[uint64](v)

if o != uint64(i) || err != nil {
b.Fatalf("invalid value: %d %d %v, err: %s", o, i, v, err)
}
}
})
}

func ExampleEncode() {
var x uint32 = 1234567890

b := make([]byte, 7)
base32quant.Encode(b, x)

fmt.Println(string(b))
// Output: bezmaws
}

func ExampleDecode() {
x, _ := base32quant.Decode[uint32]([]byte("bezmaws"))
fmt.Println(x)
// Output: 1234567890
}

func ExampleDecode_mixedCase() {
x, _ := base32quant.Decode[uint32]([]byte("beZmAwS"))
fmt.Println(x)
// Output: 1234567890
}

func TestEncodeDecode(t *testing.T) {
s := "-1"
_, err := base32quant.Decode[uint32]([]byte(s))
if err == nil {
t.Fatalf("must be error")
}
}

func FuzzEncodeDecode_32(f *testing.F) {
b := make([]byte, 7)

f.Add(uint32(0))
f.Add(uint32(11))
f.Add(uint32(32123))
f.Add(uint32(222))

f.Fuzz(func(t *testing.T, v uint32) {
base32quant.Encode(b, v)
o, err := base32quant.Decode[uint32](b)

if o != v || err != nil {
t.Fatalf("invalid value: %d %d %v, err: %s", o, v, b, err)
}
})
}

func FuzzEncodeDecode_64(f *testing.F) {
b := make([]byte, 13)

f.Add(uint64(0))
f.Add(uint64(11))

f.Fuzz(func(t *testing.T, v uint64) {
base32quant.Encode(b, v)
o, err := base32quant.Decode[uint64](b)

if o != v || err != nil {
t.Fatalf("invalid value: %d %d %v, err: %s", o, v, b, err)
}
})
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/ndx-technologies/base32quant

go 1.23.0
33 changes: 33 additions & 0 deletions uint32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package base32quant

// UInt32 is a base32 encoded uint32 into 7 bytes.
type UInt32 struct{ v uint32 }

func NewUInt32(v uint32) UInt32 { return UInt32{v} }

func (s UInt32) UInt32() uint32 { return s.v }

func (s UInt32) MarshalText() ([]byte, error) {
b := make([]byte, 7)
Encode(b, s.v)
return b, nil
}

func (s *UInt32) UnmarshalText(b []byte) (err error) {
if len(b) != 7 {
return ErrInvalidLength
}
s.v, err = Decode[uint32](b)
return err
}

func NewUInt32FromString(s string) (v UInt32, err error) {
err = (&v).UnmarshalText([]byte(s))
return v, err
}

func (s UInt32) String() string {
b := make([]byte, 7)
Encode(b, s.v)
return string(b)
}
45 changes: 45 additions & 0 deletions uint32_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package base32quant_test

import (
"fmt"

"github.com/ndx-technologies/base32quant"
)

func ExampleNewUInt32() {
u := base32quant.NewUInt32(42)
fmt.Println(u.UInt32())
// Output: 42
}

func ExampleUInt32_MarshalText() {
u := base32quant.NewUInt32(42)
b, _ := u.MarshalText()
fmt.Println(string(b))
// Output: aaaaabk
}

func ExampleUInt32_UnmarshalText() {
var u base32quant.UInt32
u.UnmarshalText([]byte("aaaaabk"))
fmt.Println(u.UInt32())
// Output: 42
}

func ExampleUInt32_UnmarshalText_error() {
var u base32quant.UInt32
fmt.Println(u.UnmarshalText([]byte("aaaaaaaaaabk")))
// Output: invalid length
}

func ExampleUInt32_String() {
u := base32quant.NewUInt32(42)
fmt.Println(u.String())
// Output: aaaaabk
}

func ExampleNewUInt32FromString() {
u, _ := base32quant.NewUInt32FromString("aaaaabk")
fmt.Println(u.UInt32())
// Output: 42
}
33 changes: 33 additions & 0 deletions uint64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package base32quant

// UInt64 is a base32 encoded uint64 into 13 bytes.
type UInt64 struct{ v uint64 }

func NewUInt64(v uint64) UInt64 { return UInt64{v} }

func (s UInt64) UInt64() uint64 { return s.v }

func (s UInt64) MarshalText() ([]byte, error) {
b := make([]byte, 13)
Encode(b, s.v)
return b, nil
}

func (s *UInt64) UnmarshalText(b []byte) (err error) {
if len(b) != 13 {
return ErrInvalidLength
}
s.v, err = Decode[uint64](b)
return err
}

func NewUInt64FromString(s string) (v UInt64, err error) {
err = (&v).UnmarshalText([]byte(s))
return v, err
}

func (s UInt64) String() string {
b := make([]byte, 13)
Encode(b, s.v)
return string(b)
}
Loading

0 comments on commit 17dd135

Please sign in to comment.