Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlibs): add encoding, encoding/{base32,binary,csv} #1290

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a58c9e0
feat: add encoding stdlib
notJoon Oct 25, 2023
a96c6ff
csv and varint
notJoon Oct 25, 2023
8a33d7f
create base32 and tests
notJoon Oct 26, 2023
7415d31
remove `fallthrough`
notJoon Oct 26, 2023
6a4d328
rewrite test for `varint`
notJoon Oct 27, 2023
7355668
update test and add some en/decode cases
notJoon Oct 31, 2023
1de0a69
porting base32 fin
notJoon Oct 31, 2023
febc76f
fix
notJoon Nov 12, 2023
ed78d3d
fix test
notJoon Nov 12, 2023
5d55e29
specifying package scope
notJoon Nov 12, 2023
e863269
type
notJoon Nov 12, 2023
d1ff5f1
another typo
notJoon Nov 12, 2023
de17d0e
nopadding
notJoon Nov 12, 2023
7b8c192
fix
notJoon Nov 12, 2023
eef4d2c
fix
notJoon Nov 12, 2023
62bb9c4
wip
notJoon Nov 12, 2023
62b297c
remove reflect on csv test
notJoon Nov 12, 2023
d69298a
fix
notJoon Nov 12, 2023
6abba1a
fix
notJoon Nov 13, 2023
97aa8a0
fix
notJoon Nov 13, 2023
30325f6
add simple reader test
notJoon Nov 13, 2023
0b63206
fmt
notJoon Nov 13, 2023
7ea23dc
Merge branch 'master' into add-encoding
notJoon Nov 13, 2023
ef756d6
package bufio -> bufio_test
thehowl Nov 16, 2023
d861226
fixes to pkg base32
thehowl Nov 16, 2023
ae61fca
rollback some changes
thehowl Nov 16, 2023
8b61830
revert chagnes in varint
thehowl Nov 16, 2023
bac1c37
csv fix-up
thehowl Nov 16, 2023
8efeb0d
fmt
thehowl Nov 16, 2023
2080c5d
Merge branch 'master' of github.com:gnolang/gno into add-encoding
thehowl Dec 8, 2024
a93e4c0
attempt reverting base32 to old state; side-quested on different issue
thehowl Dec 8, 2024
abddb96
fix(gnolang): use strconv.UnquoteChar to parse rune literals
thehowl Dec 8, 2024
25687c8
Merge branch 'dev/morgan/no-strconv-unquote' into add-encoding
thehowl Dec 8, 2024
f2ff51f
fixup
thehowl Dec 8, 2024
1fb863e
fix(gnovm): in op_binary, return typed booleans where appropriate
thehowl Dec 8, 2024
e92c78c
Merge branch 'dev/morgan/runtime-typed-bools' into add-encoding
thehowl Dec 8, 2024
183a1c1
fixup
thehowl Dec 8, 2024
9c28fbe
fmt
thehowl Dec 8, 2024
49a90df
update generated.go
thehowl Dec 8, 2024
fd5565d
Merge branch 'master' into add-encoding
thehowl Dec 8, 2024
0b20f19
Merge branch 'master' into add-encoding
thehowl Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
556 changes: 556 additions & 0 deletions gnovm/stdlibs/encoding/base32/base32.gno
thehowl marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

817 changes: 817 additions & 0 deletions gnovm/stdlibs/encoding/base32/base32_test.gno
thehowl marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions gnovm/stdlibs/encoding/binary/varint.gno
thehowl marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package binary

import (
"errors"
"io"
)

// MaxVariantLenN is the maximum length of a varint-encoded N-bit integer.
const (
MaxVarintLen16 = 3
MaxVarintLen32 = 5
MaxVarintLen64 = 10
)

// AppendUvarint appends the variant-encoded form of x.
// as generated by PutUvarint, to buffer and returns the extended buffer.
func AppendUvarint(buf []byte, x uint64) []byte {
for x >= 0x80 {
buf = append(buf, byte(x)|0x80)
x >>= 7
}
return append(buf, byte(x))
}

// PutUvarint encodes a uint64 into buffer and returns the number of bytes written.
// If the buffer is too small, PutUvarint will panic.
func PutUvarint(buf []byte, x uint64) int {
i := 0
for x >= 0x80 {
buf[i] = byte(x) | 0x80
x >>= 7
i++
}
buf[i] = byte(x)
return i + 1
}

// Uvarint decodes a uint64 from buf and returns that value and the
// number of bytes read (> 0). If an error occurred, the value is 0
// and the number of bytes n is <= 0 meaning:
//
// n == 0: buf too small
// n < 0: value larger than 64 bits (overflow)
// and -n is the number of bytes read
func Uvarint(buf []byte) (uint64, int) {
var x uint64
var s uint
for i, b := range buf {
if i == MaxVarintLen64 {
// Catch byte reads past MaxVarintLen64.
// See issue https://golang.org/issues/41185
return 0, -(i + 1) // overflow
}
if b < 0x80 {
if i == MaxVarintLen64-1 && b > 1 {
return 0, -(i + 1) // overflow
}
return x | uint64(b)<<s, i + 1
}
x |= uint64(b&0x7f) << s
s += 7
}
return 0, 0
}

// AppendVarint appends the variant-encoded form of x,
// as generated by PutVarint, to buffer and returns the extended buffer.
func AppendVarint(buf []byte, x int64) []byte {
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
return AppendUvarint(buf, ux)
}

// PutVarint encodes an int64 into buf and returns the number of bytes written.
// If the buffer is too small, PutVarint will panic.
func PutVarint(buf []byte, x int64) int {
ux := uint64(x) << 1
if x < 0 {
ux = ^ux
}
return PutUvarint(buf, ux)
}

// Varint decodes an int64 from buffer and returns that value and the
// number of bytes read (> 0). If an error occurred, the value is `0`
// and the number of bytes n <= 0 with the follwing meaning:
//
// n == 0: buf too small
// n < 0: value larger than 64 bits (overflow)
// and -n is the number of bytes read
func Varint(buf []byte) (int64, int) {
ux, n := Uvarint(buf) // ok to continue in presence of error
x := int64(ux >> 1)
if ux&1 != 0 {
x = ^x
}
return x, n
}

var errOverflow = errors.New("binary: varint overflows a 64-bit integer")

// ReadUvarint reads an encoded unsigned integer from r and returns it as a uint64.
// The error is [io.EOF] only if no bytes were read.
// If an [io.EOF] happens after reading some but not all the bytes,
// ReadUvarint returns [io.ErrUnexpectedEOF].
func ReadUvarint(r io.ByteReader) (uint64, error) {
var x uint64
var s uint
for i := 0; i < MaxVarintLen64; i++ {
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return x, err
}
if b < 0x80 {
if i == MaxVarintLen64-1 && b > 1 {
return x, errOverflow
}
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return x, errOverflow
}

// ReadVarint reads an encoded signed integer from r and returns it as an int64.
// The error is [io.EOF] only if no bytes were read.
// If an [io.EOF] happens after reading some but not all the bytes,
// ReadVarint returns [io.ErrUnexpectedEOF].
func ReadVarint(r io.ByteReader) (int64, error) {
ux, err := ReadUvarint(r) // ok to continue in presence of error
x := int64(ux >> 1)
if ux&1 != 0 {
x = ^x
}
return x, err
}
256 changes: 256 additions & 0 deletions gnovm/stdlibs/encoding/binary/varint_test.gno
thehowl marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package binary

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import (
"bytes"
"io"
"math"
"testing"
)

func testConstant(t *testing.T, w uint, max int) {
buf := make([]byte, MaxVarintLen64)
n := PutUvarint(buf, 1<<w-1)
if n != max {
t.Errorf("MaxVarintLen%d = %d; want %d", w, max, n)
}
}

func TestConstants(t *testing.T) {
testConstant(t, 16, MaxVarintLen16)
testConstant(t, 32, MaxVarintLen32)
testConstant(t, 64, MaxVarintLen64)
}

// XXX panic: runtime error: invalid memory address or nil pointer dereference
//
// func testVarint(t *testing.T, x int64) {
// buf := make([]byte, MaxVarintLen64)
// n := PutVarint(buf, x)
// y, m := Varint(buf[0:n])
// if x != y {
// t.Errorf("Varint(%d): got %d", x, y)
// }
// if n != m {
// t.Errorf("Varint(%d): got n = %d; want %d", x, m, n)
// }

// buf2 := []byte("prefix")
// buf2 = AppendVarint(buf2, x)
// if string(buf2) != "prefix"+string(buf[:n]) {
// t.Errorf("AppendVarint(%d): got %q, want %q", x, buf2, "prefix"+string(buf[:n]))
// }

// y, err := ReadVarint(bytes.NewReader(buf))
// if err != nil {
// t.Errorf("ReadVarint(%d): %s", x, err)
// }
// if x != y {
// t.Errorf("ReadVarint(%d): got %d", x, y)
// }
// }

// func testUvarint(t *testing.T, x uint64) {
// buf := make([]byte, MaxVarintLen64)
// n := PutUvarint(buf, x)
// y, m := Uvarint(buf[0:n])
// if x != y {
// t.Errorf("Uvarint(%d): got %d", x, y)
// }
// if n != m {
// t.Errorf("Uvarint(%d): got n = %d; want %d", x, m, n)
// }

// buf2 := []byte("prefix")
// buf2 = AppendUvarint(buf2, x)
// if string(buf2) != "prefix"+string(buf[:n]) {
// t.Errorf("AppendUvarint(%d): got %q, want %q", x, buf2, "prefix"+string(buf[:n]))
// }

// y, err := ReadUvarint(bytes.NewReader(buf))
// if err != nil {
// t.Errorf("ReadUvarint(%d): %s", x, err)
// }
// if x != y {
// t.Errorf("ReadUvarint(%d): got %d", x, y)
// }
// }

// var tests = []int64{
// -1 << 63,
// -1<<63 + 1,
// -1,
// 0,
// 1,
// 2,
// 10,
// 20,
// 63,
// 64,
// 65,
// 127,
// 128,
// 129,
// 255,
// 256,
// 257,
// 1<<63 - 1,
// }

// func TestVarint(t *testing.T) {
notJoon marked this conversation as resolved.
Show resolved Hide resolved
// for _, x := range tests {
// testVarint(t, x)
// testVarint(t, -x)
// }
// for x := int64(0x7); x != 0; x <<= 1 {
// testVarint(t, x)
// testVarint(t, -x)
// }
// }

// func TestUvarint(t *testing.T) {
// for _, x := range tests {
// testUvarint(t, uint64(x))
// }
// for x := uint64(0x7); x != 0; x <<= 1 {
// testUvarint(t, x)
// }
// }

// func TestBufferTooSmall(t *testing.T) {
// buf := []byte{0x80, 0x80, 0x80, 0x80}
// for i := 0; i <= len(buf); i++ {
// buf := buf[0:i]
// x, n := Uvarint(buf)
// if x != 0 || n != 0 {
// t.Errorf("Uvarint(%v): got x = %d, n = %d", buf, x, n)
// }

// x, err := ReadUvarint(bytes.NewReader(buf))
// wantErr := io.EOF
// if i > 0 {
// wantErr = io.ErrUnexpectedEOF
// }
// if x != 0 || err != wantErr {
// t.Errorf("ReadUvarint(%v): got x = %d, err = %s", buf, x, err)
// }
// }
// }

// XXX panic: reflect: reflect.Value.SetString using value obtained using unexported field
//
// Ensure that we catch overflows of bytes going past MaxVarintLen64.
// See issue https://golang.org/issues/41185
// func TestBufferTooBigWithOverflow(t *testing.T) {
// tests := []struct {
// in []byte
// name string
// wantN int
// wantValue uint64
// }{
// {
// name: "invalid: 1000 bytes",
// in: func() []byte {
// b := make([]byte, 1000)
// for i := range b {
// b[i] = 0xff
// }
// b[999] = 0
// return b
// }(),
// wantN: -11,
// wantValue: 0,
// },
// {
// name: "valid: math.MaxUint64-40",
// in: []byte{0xd7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01},
// wantValue: math.MaxUint64 - 40,
// wantN: 10,
// },
// {
// name: "invalid: with more than MaxVarintLen64 bytes",
// in: []byte{0xd7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01},
// wantN: -11,
// wantValue: 0,
// },
// {
// name: "invalid: 10th byte",
// in: []byte{0xd7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
// wantN: -10,
// wantValue: 0,
// },
// }

// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// x, n := Uvarint(tt.in)
// if x != tt.wantValue || n != tt.wantN {
// t.Errorf("Uvarint(%v): got x = %d, n = %d; want %d, %d", tt.in, x, n, tt.wantValue, tt.wantN)
// }

// r := bytes.NewReader(tt.in)
// len := r.Len()
// x, err := ReadUvarint(r)
// if x != tt.wantValue || err != errOverflow {
// t.Errorf("ReadUvarint(%v): got x = %d, err = %s; want %d, %s", tt.in, x, err, tt.wantValue, errOverflow)
// }
// if read := len - r.Len(); read > MaxVarintLen64 {
// t.Errorf("ReadUvarint(%v): read more than MaxVarintLen64 bytes, got %d", tt.in, read)
// }
// })
// }
// }

func testOverflow(t *testing.T, buf []byte, x0 uint64, n0 int, err0 error) {
x, n := Uvarint(buf)
if x != 0 || n != n0 {
t.Errorf("Uvarint(% X): got x = %d, n = %d; want 0, %d", buf, x, n, n0)
}

r := bytes.NewReader(buf)
len := r.Len()
x, err := ReadUvarint(r)
if x != x0 || err != err0 {
t.Errorf("ReadUvarint(%v): got x = %d, err = %s; want %d, %s", buf, x, err, x0, err0)
}
if read := len - r.Len(); read > MaxVarintLen64 {
t.Errorf("ReadUvarint(%v): read more than MaxVarintLen64 bytes, got %d", buf, read)
}
}

func TestOverflow(t *testing.T) {
testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2}, 0, -10, errOverflow)
testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0}, 0, -11, errOverflow)
testOverflow(t, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 1<<64-1, -11, errOverflow) // 11 bytes, should overflow
}

func TestNonCanonicalZero(t *testing.T) {
buf := []byte{0x80, 0x80, 0x80, 0}
x, n := Uvarint(buf)
if x != 0 || n != 4 {
t.Errorf("Uvarint(%v): got x = %d, n = %d; want 0, 4", buf, x, n)
}
}

// func BenchmarkPutUvarint32(b *testing.B) {
// buf := make([]byte, MaxVarintLen32)
// b.SetBytes(4)
// for i := 0; i < b.N; i++ {
// for j := uint(0); j < MaxVarintLen32; j++ {
// PutUvarint(buf, 1<<(j*7))
// }
// }
// }

// func BenchmarkPutUvarint64(b *testing.B) {
// buf := make([]byte, MaxVarintLen64)
// b.SetBytes(8)
// for i := 0; i < b.N; i++ {
// for j := uint(0); j < MaxVarintLen64; j++ {
// PutUvarint(buf, 1<<(j*7))
// }
// }
// }
Loading
Loading