27 Commits

Author SHA1 Message Date
717754644c tape: Fix capitalization of Uint 2025-05-23 00:15:56 -04:00
134daacc03 tape: Switch StringArray over to using VILA 2025-05-23 00:04:57 -04:00
a927b9519e tape: Update tape.String to include ~[]byte 2025-05-23 00:03:39 -04:00
32df336c3e tape: Add DecodeVILAIter 2025-05-22 23:44:20 -04:00
2b3a53052f tape: Implement PASTA and VILA encoding/decoding 2025-05-22 13:26:42 -04:00
23c3efa845 tape: Improve table tests 2025-05-18 21:32:08 -04:00
0e7e935374 tape: Make table decoding more robust 2025-05-18 17:59:05 -04:00
dd5325b351 tape: Fix table decoding restarting after each pull 2025-05-18 16:34:28 -04:00
37c3e49833 tape: Fix types_test.go not getting values for n in some cases 2025-05-18 16:32:27 -04:00
6e5a7115d3 tape: Fix "n" size returned by DecodeGBEU 2025-05-18 16:26:08 -04:00
f4fb5e80b9 tape: Test "n" sizes of integer types 2025-05-18 16:22:52 -04:00
3a88619f9b tape: Add back iter compatibility for table decoding 2025-05-18 16:12:55 -04:00
568431f4c3 tape: Improve table decoding 2025-05-18 16:08:47 -04:00
f50b2ca0cd tape: Remove old "pairs" encoding 2025-05-18 15:51:45 -04:00
3d8a012477 tape: Add table encoding/decoding functions 2025-05-18 15:50:24 -04:00
4f3b256821 tape: Integer encoding accepts oversize buffers now 2025-05-18 14:49:43 -04:00
2080d60793 tape: Remove array tests from types_test.go 2025-05-18 14:47:11 -04:00
c3337641bc tape: Break out array code into separate file 2025-05-18 00:06:50 -04:00
4438210963 tape: Add N length support to all types 2025-05-17 23:55:56 -04:00
8d5ba2fa39 tape: EncodeGBEU returns the amount of bytes written 2025-05-17 10:38:50 -04:00
a05c034313 tape: Clean up 2025-05-16 21:42:40 -04:00
1b25e306a6 tape: Add GBEU encoding/decoding support 2025-05-16 21:27:11 -04:00
dd5e7e96d5 design: Remove note about this limitation 2025-05-15 17:56:41 -04:00
835d623087 Change the protocol definition for tape to conform to #2 2025-05-15 17:49:29 -04:00
83443b8c88 design: Fix documentation on message payload length 2025-05-14 15:15:03 -04:00
0b98c768b3 Fix some outdated doc comments 2025-05-14 14:44:27 -04:00
218949bd46 Remove quic
It's clear it won't survive this change because I can't even test
it, so who knows if its good enough to have in main.
2025-05-14 14:39:19 -04:00
14 changed files with 962 additions and 732 deletions

View File

@@ -18,12 +18,10 @@ dependant on which transport is being used.
A message refers to a block of octets sent within a transaction, paired with an
unsigned 16-bit method code. The order of messages within a given transaction is
preserved, but the order of messages accross the entire connection is not
guaranteed.
The message payload must be 65,535 (unsigned 16-bit integer limit) octets or
smaller in length. This does not include the method code. Applications are free
to send whatever data they wish as the payload, but TAPE is recommended for
encoding it.
guaranteed. There is no functional limit on the size of a message payload, but
there may be one depending on which
[METADAPT sub-protocol](#message-and-transaction-demarcation-protocol-metadapt)
is in use.
Method codes should be written in upper-case base 16 with the prefix "M" in
logs, error messages, documentation, etc. For example, the method code 62,206 in
@@ -37,25 +35,17 @@ fucking with you.
## Table Pair Encoding (TAPE)
The Table Pair Encoding (TAPE) scheme is a method for encoding structured data
within HOPP messages. It defines standard binary encoding methods for common
data types, as well as a corruption-resistant table structure that maps numeric
IDs to values. It is designed to allow applications to be presented with data
they are not equipped to handle while continuing to function normally. This
enables backwards compatibile application protocol changes.
data types, as well as aggregate data types such as tables and arrays. It is
designed to allow applications to be presented with data they are not equipped
to handle while continuing to function normally. This enables backwards
compatibile application protocol changes.
### Table Structure
A table is divided into two sections: the header, and the values. The header
begins with the number (U16) of pairs in the table, which is then followed by
that many tag-offset pairs. A tag-offset pair consists of a numerical (U16) tag,
followed the position (U16) of the value relative to the start of the values
section. The values section contains the value data for each pair, where the
start of each value is determined by its offset, and the end is determined by
the offset of the next value, or the end of the message if there is no value
after it.
Both sections must be in the same order, and because of this, each value offset
must be greater than or equal to the last. If a message has erratic structure
(such as unordered or out-of-bounds offsets), implementations may opt to discard
only the erratic pairs, as well as the pairs directly before those.
The length of a TAPE structure is assumed to be given by the surrounding
protocol, which is usually METADAPT-A or B. The root of a TAPE structure can be
any data value, but is usually a table, which can contain several values that
each have a numeric key. Values can also be nested. Both sides of the connection
must agree on what data type should be the root value, the data type of each
known table value, etc.
### Data Value Types
The table below lists all data value types supported by TAPE.
@@ -70,16 +60,14 @@ The table below lists all data value types supported by TAPE.
| U16 | 2 | An unsigned 16-bit integer | BEU
| U32 | 4 | An unsigned 32-bit integer | BEU
| U64 | 8 | An unsigned 64-bit integer | BEU
| Array[^1] | SOP[^2] | An array of any above type | PASTA
| String | N/A | A UTF-8 string | UTF-8
| StringArray | n * 2 + SOP[^2] | An array the String type | VILA
| Array[^1] | | An array of any above type | PASTA
| String | | A UTF-8 string | UTF-8
| StringArray | | An array the String type | VILA
| Table | | A table of any type | TTLV
[^1]: Array types are written as <E>Array, where <E> is the element type. For
example, an array of I32 would be written as I32Array. StringArray still follows
this rule, even though it is encoded differently from other arrays. Nesting
arrays inside of arrays is prohibited. This problem can be avoided in most cases
by effectively utilizing the table structure, or by improving the design of
your protocol.
this rule, even though it is encoded differently from other arrays.
[^2]: SOP (sum of parts) refers to the sum of the size of every item in a data
structure.
@@ -97,6 +85,15 @@ Big-Endian, Unsigned integer. The size is defined as the least amount of whole
octets which can fit all bits in the integer, regardless if the bits are on or
off. Therefore, the size cannot change at runtime.
#### GBEU
Growing Big-Endian, Unsigned integer. The integer is broken up into 8-bit
chunks, where the first bit of each chunk is a CCB. The chunk with its CCB set
to zero instead of one is the last chunk in the integer. Chunks are ordered from
most significant to least significant (big endian). The size is defined as the
least amount of whole octets which can fit all chunks of the integer. The size
of this type is not fixed and may change at runtime, so this needs to be
accounted for during use.
#### PASTA
Packed Single-Type Array. The size is defined as the size of an individual item
times the number of items. Items are placed one after the other with no gaps
@@ -112,13 +109,23 @@ for during use.
#### VILA
Variable Item Length Array. The size is defined as the least amount of whole
octets which can fit each item plus one U16 per item. The size of this type is
not fixed and may change at runtime, so this needs to be accounted for during
use. The amount of items must be greater than zero. Items are each prefixed by
their size (in octets) encoded as a U16, and they are placed one after the other
with no gaps in-between them, except as required to align the start of each item
to the nearest whole octet. Items should be of the same type but do not need to
be of the same size.
octets which can fit each item plus one GBEU per item describing that item's
size. The size of this type is not fixed and may change at runtime, so this
needs to be accounted for during use. The amount of items must be greater than
zero. Items are each prefixed by their size (in octets) encoded as a GBEU, and
they are placed one after the other with no gaps in-between them, except as
required to align the start of each item to the nearest whole octet. Items
should be of the same type but do not need to be of the same size.
#### TTLV
TAPE Tag Length Value. The size is defined as the least amount of whole octets
which can fit each item plus one U16 and one GBEU per item, where the latter of
which describes that item's size. The size of this type is not fixed and may
change at runtime, so this needs to be accounted for during use. Items are each
prefixed by their numerical tag encoded as a U16, and their size (in octets)
encoded as a GBEU. Items are placed one after the other with no gaps in-between
them, except as required to align the start of each item to the nearest whole
octet. Items need not be of the same type nor the same size.
## Transports
A transport is a protocol that HOPP connections can run on top of. HOPP

23
dial.go
View File

@@ -1,9 +1,9 @@
package hopp
import "net"
import "errors"
import "context"
import "crypto/tls"
import "github.com/quic-go/quic-go"
// Dial opens a connection to a server. The network must be one of "quic",
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, "quic4" and
@@ -19,9 +19,8 @@ type Dialer struct {
}
// Dial opens a connection to a server. The network must be one of "quic",
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, "quic4" and
// "quic6" don't do anything as the quic-go package doesn't seem to support this
// behavior.
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is not
// supported.
func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, error) {
switch network {
case "quic", "quic4", "quic6": return diale.dialQUIC(ctx, network, address)
@@ -31,12 +30,7 @@ func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, er
}
func (diale Dialer) dialQUIC(ctx context.Context, network, address string) (Conn, error) {
// sorry i fucking lied to you about the network parameter. for all
// quic-go's bullshit bloat, it doesnt even support that. not even when
// instantiating a transport. go figure :/
conn, err := quic.DialAddr(ctx, address, tlsConfig(diale.TLSConfig), quicConfig())
if err != nil { return nil, err }
return AdaptB(quicMultiConn { underlying: conn }), nil
return nil, errors.New("quic is not yet implemented")
}
func (diale Dialer) dialUnix(ctx context.Context, network, address string) (Conn, error) {
@@ -60,15 +54,6 @@ func tlsConfig(conf *tls.Config) *tls.Config {
return conf
}
func quicConfig() *quic.Config {
return &quic.Config {
// TODO: perhaps we might want to put something here
// the quic config shouldn't be exported, just set up
// automatically. we can't have that strangely built quic-go
// package be part of the API, or any third-party packages for
// that matter. it must all be abstracted away.
}
}
func quicNetworkToUDPNetwork(network string) (string, error) {
switch network {

14
go.mod
View File

@@ -5,18 +5,4 @@ go 1.23.0
require (
git.tebibyte.media/sashakoshka/go-util v0.9.1
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62
github.com/quic-go/quic-go v0.48.2
)
require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)

56
go.sum
View File

@@ -1,60 +1,4 @@
git.tebibyte.media/sashakoshka/go-util v0.9.1 h1:eGAbLwYhOlh4aq/0w+YnJcxT83yPhXtxnYMzz6K7xGo=
git.tebibyte.media/sashakoshka/go-util v0.9.1/go.mod h1:0Q1t+PePdx6tFYkRuJNcpM1Mru7wE6X+it1kwuOH+6Y=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g=
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,9 +1,8 @@
package hopp
import "net"
import "context"
import "errors"
import "crypto/tls"
import "github.com/quic-go/quic-go"
// Listener is an object which listens for incoming HOPP connections.
type Listener interface {
@@ -17,7 +16,8 @@ type Listener interface {
}
// Listen listens for incoming HOPP connections. The network must be one of
// "quic", "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix".
// "quic", "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is
// not supported.
func Listen(network, address string) (Listener, error) {
switch network {
case "quic", "quic4", "quic6": return ListenQUIC(network, address, nil)
@@ -30,19 +30,8 @@ func Listen(network, address string) (Listener, error) {
// The network must be one of "quic", "quic4", (IPv4-only) or "quic6"
// (IPv6-only).
func ListenQUIC(network, address string, tlsConf *tls.Config) (Listener, error) {
tlsConf = tlsConfig(tlsConf)
quicConf := quicConfig()
udpNetwork, err := quicNetworkToUDPNetwork(network)
if err != nil { return nil, err }
addr, err := net.ResolveUDPAddr(udpNetwork, address)
if err != nil { return nil, err }
udpListener, err := net.ListenUDP(udpNetwork, addr)
if err != nil { return nil, err }
quicListener, err := quic.Listen(udpListener, tlsConf, quicConf)
if err != nil { return nil, err }
return &listenerQUIC {
underlying: quicListener,
}, nil
// tlsConf = tlsConfig(tlsConf)
return nil, errors.New("quic is not yet implemented")
}
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
@@ -58,24 +47,6 @@ func ListenUnix(network, address string) (Listener, error) {
}, nil
}
type listenerQUIC struct {
underlying *quic.Listener
}
func (this *listenerQUIC) Accept() (Conn, error) {
conn, err := this.underlying.Accept(context.Background())
if err != nil { return nil, err }
return AdaptB(quicMultiConn { underlying: conn }), nil
}
func (this *listenerQUIC) Close() error {
return this.underlying.Close()
}
func (this *listenerQUIC) Addr() net.Addr {
return this.underlying.Addr()
}
type listenerUnix struct {
underlying *net.UnixListener
}

View File

@@ -1,54 +0,0 @@
package hopp
import "net"
import "context"
import "github.com/quic-go/quic-go"
var _ MultiConn = quicMultiConn { }
type quicMultiConn struct {
underlying quic.Connection
}
func (conn quicMultiConn) Close() error {
return conn.underlying.CloseWithError(0, "good bye")
}
func (conn quicMultiConn) LocalAddr() net.Addr {
return conn.underlying.LocalAddr()
}
func (conn quicMultiConn) RemoteAddr() net.Addr {
return conn.underlying.RemoteAddr()
}
func (conn quicMultiConn) AcceptStream(ctx context.Context) (Stream, error) {
strea, err := conn.underlying.AcceptStream(ctx)
if err != nil { return nil, err }
return quicStream { underlying: strea }, nil
}
func (conn quicMultiConn) OpenStream() (Stream, error) {
strea, err := conn.underlying.OpenStream()
if err != nil { return nil, err }
return quicStream { underlying: strea }, nil
}
type quicStream struct {
underlying quic.Stream
}
func (strea quicStream) Read(buffer []byte) (n int, err error) {
return strea.underlying.Read(buffer)
}
func (strea quicStream) Write(buffer []byte) (n int, err error) {
return strea.underlying.Read(buffer)
}
func (strea quicStream) Close() error {
return strea.underlying.Close()
}
func (strea quicStream) ID() int64 {
return int64(strea.underlying.StreamID())
}

284
tape/array.go Normal file
View File

@@ -0,0 +1,284 @@
package tape
import "fmt"
import "iter"
import "slices"
// encoding and decoding functions must not make any allocations
// ArrayPushFunc adds an item to an array that is being encoded.
type ArrayPushFunc func(value []byte) (n int, err error)
// ArrayPullFunc gets the next item of an array that is being decoded.
type ArrayPullFunc func() (value []byte, n int, err error)
// DecodePASTA decodes a packed single-type array.
func DecodePASTA(data []byte, itemLength int) ArrayPullFunc {
n := 0
return func() (value []byte, n_ int, err error) {
if n > len(data) - itemLength {
return nil, 0, fmt.Errorf("decoding PASTA: %w", ErrWrongBufferLength)
}
value = data[n:n + itemLength]
n += itemLength
return value, itemLength, nil
}
}
// DecodePASTAIter decodes a packed single-type array and returns it as an
// iterator.
func DecodePASTAIter(data []byte, itemLength int) iter.Seq[[]byte] {
return slices.Chunk(data, itemLength)
}
// EncodePASTA encodes a packed single-type array.
func EncodePASTA(data []byte, itemLength int) ArrayPushFunc {
n := 0
return func(value []byte) (n_ int, err error) {
if n > len(data) - itemLength {
return 0, fmt.Errorf("encoding PASTA: %w", ErrWrongBufferLength)
}
copy(data[n:], value)
n += itemLength
return itemLength, nil
}
}
// PASTASize returns the size of a packed single-type array.
func PASTASize(length, itemLength int) int {
return length * itemLength
}
// DecodeVILA encodes a variable item length array.
func DecodeVILA(data []byte) ArrayPullFunc {
n := 0
return func() (value []byte, n_ int, err error) {
if n >= len(data) { return nil, n_, fmt.Errorf("decoding VILA: %w", ErrWrongBufferLength) }
length, nn, err := DecodeGBEU[uint](data[n:])
n += nn
n_ += nn
if err != nil { return nil, n_, err }
if n > len(data) - int(length) {
return nil, n_, fmt.Errorf("decoding VILA: %w", ErrWrongBufferLength)
}
value = data[n:n + int(length)]
n += int(length)
n_ += int(length)
return value, int(length), nil
}
}
// DecodeVILAIter decodes a variable item length array and returns it as an
// iterator.
func DecodeVILAIter(data []byte) iter.Seq[[]byte] {
return func(yield func([]byte) bool) {
pull := DecodeVILA(data)
for {
value, _, err := pull()
if err != nil { return }
if !yield(value) { return }
}
}
}
// EncodeVILA encodes a variable item length array.
func EncodeVILA(data []byte) ArrayPushFunc {
n := 0
return func(value []byte) (n_ int, err error) {
if n >= len(data) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
nn, err := EncodeGBEU(data[n:], uint(len(value)))
n += nn
n_ += nn
if err != nil { return n, err }
if n >= len(data) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
nn = copy(data[n:], value)
n += nn
n_ += nn
if nn != len(value) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
return n_, err
}
}
// VILASize returns the size of a variable item length array.
func VILASize(items ...[]byte) int {
size := 0
for _, item := range items {
size += GBEUSize[uint](uint(len(item)))
size += len(item)
}
return size
}
// DecodeStringArray decodes a VILA string array from the given data.
func DecodeStringArray[T String](data []byte) (result []T, n int, err error) {
pull := DecodeVILA(data)
for {
item, nn, err := pull()
n += nn
if err != nil { return nil, n, err }
result = append(result, T(item))
}
return result, n, nil
}
// EncodeStringArray encodes a VILA string array into the given buffer.
func EncodeStringArray[T String](buffer []byte, value []T) (n int, err error) {
push := EncodeVILA(buffer)
for _, item := range value {
nn, err := push([]byte(item))
n += nn
if err != nil { return n, err }
}
return n, nil
}
// StringArraySize returns the size of a VILA string array.
func StringArraySize[T String](value []T) int {
size := 0
for _, item := range value {
size += GBEUSize[uint](uint(len(item)))
size += len(item)
}
return size
}
// DecodeI8Array decodes a packed array of 8 bit integers from the given data.
func DecodeI8Array[T Int8](data []byte) (result []T, n int, err error) {
result = make([]T, len(data))
for index, item := range data {
result[index] = T(item)
}
return result, len(data), nil
}
// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer.
func EncodeI8Array[T Int8](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) { return 0, fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) }
for index, item := range value {
buffer[index] = byte(item)
}
return len(buffer), nil
}
// I8ArraySize returns the size of a packed 8 bit integer array. Returns 0 and
// an error if the size is too large.
func I8ArraySize[T Int8](value []T) (int, error) {
total := len(value)
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI16Array decodes a packed array of 16 bit integers from the given data.
func DecodeI16Array[T Int16](data []byte) (value []T, n int, err error) {
if len(data) % 2 != 0 { return nil, 0, fmt.Errorf("decoding []int16: %w", ErrWrongBufferLength) }
length := len(data) / 2
result := make([]T, length)
for index := range length {
offset := index * 2
result[index] = T(data[offset]) << 8 | T(data[offset + 1])
}
return result, len(data) / 2, nil
}
// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer.
func EncodeI16Array[T Int16](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 2 { return 0, fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 8)
buffer[1] = byte(item)
buffer = buffer[2:]
}
return len(value) * 2, nil
}
// I16ArraySize returns the size of a packed 16 bit integer array. Returns 0 and
// an error if the size is too large.
func I16ArraySize[T Int16](value []T) (int, error) {
total := len(value) * 2
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI32Array decodes a packed array of 32 bit integers from the given data.
func DecodeI32Array[T Int32](data []byte) (value []T, n int, err error) {
if len(data) % 4 != 0 { return nil, 0, fmt.Errorf("decoding []int32: %w", ErrWrongBufferLength) }
length := len(data) / 4
result := make([]T, length)
for index := range length {
offset := index * 4
result[index] =
T(data[offset + 0]) << 24 |
T(data[offset + 1]) << 16 |
T(data[offset + 2]) << 8 |
T(data[offset + 3])
}
return result, len(data) / 4, nil
}
// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer.
func EncodeI32Array[T Int32](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 4 { return 0, fmt.Errorf("encoding []int32: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 24)
buffer[1] = byte(item >> 16)
buffer[2] = byte(item >> 8)
buffer[3] = byte(item)
buffer = buffer[4:]
}
return len(value) * 4, nil
}
// I32ArraySize returns the size of a packed 32 bit integer array. Returns 0 and
// an error if the size is too large.
func I32ArraySize[T Int32](value []T) (int, error) {
total := len(value) * 4
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI64Array decodes a packed array of 32 bit integers from the given data.
func DecodeI64Array[T Int64](data []byte) (value []T, n int, err error) {
if len(data) % 8 != 0 { return nil, 0, fmt.Errorf("decoding []int64: %w", ErrWrongBufferLength) }
length := len(data) / 8
result := make([]T, length)
for index := range length {
offset := index * 8
result[index] =
T(data[offset + 0]) << 56 |
T(data[offset + 1]) << 48 |
T(data[offset + 2]) << 40 |
T(data[offset + 3]) << 32 |
T(data[offset + 4]) << 24 |
T(data[offset + 5]) << 16 |
T(data[offset + 6]) << 8 |
T(data[offset + 7])
}
return result, len(data) / 8, nil
}
// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer.
func EncodeI64Array[T Int64](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 8 { return 0, fmt.Errorf("encoding []int64: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 56)
buffer[1] = byte(item >> 48)
buffer[2] = byte(item >> 40)
buffer[3] = byte(item >> 32)
buffer[4] = byte(item >> 24)
buffer[5] = byte(item >> 16)
buffer[6] = byte(item >> 8)
buffer[7] = byte(item)
buffer = buffer[8:]
}
return len(value) * 8, nil
}
// I64ArraySize returns the size of a packed 64 bit integer array. Returns 0 and
// an error if the size is too large.
func I64ArraySize[T Int64](value []T) (int, error) {
total := len(value) * 8
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}

131
tape/array_test.go Normal file
View File

@@ -0,0 +1,131 @@
package tape
// import "fmt"
import "slices"
// import "errors"
import "testing"
import "math/rand"
func TestI8Array(test *testing.T) {
var buffer [64]byte
_, err := EncodeI8Array[uint8](buffer[:], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI8Array[uint8](buffer[:0], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI8Array[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI8Array[uint8](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint8](rand.Intn(16))
length, _ := I8ArraySize(array)
if length != len(array) { test.Fatalf("%d != %d", length, len(array)) }
_, err := EncodeI8Array[uint8](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI8Array[uint8](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI16Array(test *testing.T) {
var buffer [128]byte
_, err := EncodeI16Array[uint16](buffer[:], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI16Array[uint16](buffer[:0], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI16Array[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI16Array[uint16](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint16](rand.Intn(16))
length, _ := I16ArraySize(array)
if length != 2 * len(array) { test.Fatalf("%d != %d", length, 2 * len(array)) }
_, err := EncodeI16Array[uint16](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI16Array[uint16](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI32Array(test *testing.T) {
var buffer [256]byte
_, err := EncodeI32Array[uint32](buffer[:], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI32Array[uint32](buffer[:0], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI32Array[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI32Array[uint32](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint32](rand.Intn(16))
length, _ := I32ArraySize(array)
if length != 4 * len(array) { test.Fatalf("%d != %d", length, 4 * len(array)) }
_, err := EncodeI32Array[uint32](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI32Array[uint32](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI64Array(test *testing.T) {
var buffer [512]byte
_, err := EncodeI64Array[uint64](buffer[:], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI64Array[uint64](buffer[:0], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI64Array[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI64Array[uint64](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint64](rand.Intn(16))
length, _ := I64ArraySize(array)
if length != 8 * len(array) { test.Fatalf("%d != %d", length, 8 * len(array)) }
_, err := EncodeI64Array[uint64](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI64Array[uint64](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestStringArray(test *testing.T) {
var buffer [8192]byte
_, err := EncodeStringArray[string](buffer[:], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, err = EncodeStringArray[string](buffer[:0], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeStringArray[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randStrings[string](rand.Intn(16), 16)
length, _ := StringArraySize(array)
// TODO test length
_, err := EncodeStringArray[string](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeStringArray[string](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}

View File

@@ -1,83 +0,0 @@
package tape
import "iter"
// DecodePairs decodes message tag/value pairs from a byte slice. It returns an
// iterator over all pairs, where the first value is the tag and the second is
// the value. If data yielded by the iterator is retained, it must be copied
// first.
func DecodePairs(data []byte) (iter.Seq2[uint16, []byte], error) {
// determine section bounds
if len(data) < 2 { return nil, ErrDataTooLarge }
length16, _ := DecodeI16[uint16](data[0:2])
data = data[2:]
length := int(length16)
headerSize := length * 4
if len(data) < headerSize { return nil, ErrDataTooLarge }
valuesData := data[headerSize:]
// ensure the value buffer is big enough
var valuesSize int
for index := range length {
offset := index * 4
end, _ := DecodeI16[uint16](data[offset + 2:offset + 4])
valuesSize = int(end)
}
if valuesSize > len(valuesData) {
return nil, ErrDataTooLarge
}
// return iterator
return func(yield func(uint16, []byte) bool) {
start := uint16(0)
for index := range length {
offset := index * 4
key , _ := DecodeI16[uint16](data[offset + 0:offset + 2])
end, _ := DecodeI16[uint16](data[offset + 2:offset + 4])
// if nextValuesOffset < len(valuesData) {
if !yield(key, valuesData[start:end]) {
return
}
// } else {
// if !yield(key, nil) {
// return
// }
// }
start = end
}
}, nil
}
// EncodePairs encodes message tag/value pairs into a byte slice.
func EncodePairs(pairs map[uint16] []byte) ([]byte, error) {
// determine section bounds
headerSize := 2 + len(pairs) * 4
valuesSize := 0
for _, value := range pairs {
valuesSize += len(value)
}
// generate data
buffer := make([]byte, headerSize + valuesSize)
length16, ok := U16CastSafe(len(pairs))
if !ok { return nil, ErrDataTooLarge }
EncodeI16[uint16](buffer[0:2], length16)
index := 0
end := headerSize
for key, value := range pairs {
start := end
end += len(value)
tagOffset := 2 + index * 4
end16, ok := U16CastSafe(end - headerSize)
if !ok { return nil, ErrDataTooLarge }
// write tag and length
EncodeI16[uint16](buffer[tagOffset + 0:tagOffset + 2], key)
EncodeI16[uint16](buffer[tagOffset + 2:tagOffset + 4], end16)
// write value
copy(buffer[start:end], value)
index ++
}
return buffer, nil
}

View File

@@ -1,62 +0,0 @@
package tape
import "slices"
import "testing"
func TestDecodePairs(test *testing.T) {
pairs := map[uint16] []byte {
3894: []byte("foo"),
7: []byte("br"),
}
got, err := DecodePairs([]byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
if err != nil { test.Fatal(err) }
length := 0
for key, value := range got {
test.Log(key, value)
if !slices.Equal(pairs[key], value) { test.Fatal("not equal") }
length ++
}
test.Log("length")
if length != len(pairs) { test.Fatal("wrong length") }
}
func TestEncodePairs(test *testing.T) {
pairs := map[uint16] []byte {
3894: []byte("foo"),
7: []byte("br"),
}
got, err := EncodePairs(pairs)
if err != nil { test.Fatal(err) }
test.Log(got)
valid := slices.Equal(got, []byte {
0, 2,
15, 54, 0, 3,
0, 7, 0, 5,
102, 111, 111,
98, 114}) ||
slices.Equal(got, []byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
if !valid { test.Fatal("not equal") }
}
func FuzzDecodePairs(fuzz *testing.F) {
fuzz.Add([]byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
fuzz.Fuzz(func(t *testing.T, buffer []byte) {
// ensure it does not panic :P
DecodePairs(buffer)
})
}

74
tape/table.go Normal file
View File

@@ -0,0 +1,74 @@
package tape
import "iter"
// encoding and decoding functions must not make any allocations
type TablePushFunc func(tag uint16, value []byte) (n int, err error)
type TablePullFunc func() (tag uint16, value []byte, n int, err error)
func DecodeTable(data []byte) TablePullFunc {
n := 0
return func() (tag uint16, value []byte, n_ int, err error) {
if n >= len(data) { return 0, nil, n, ErrWrongBufferLength }
tag, nn, err := DecodeI16[uint16](data[n:])
if err != nil { return 0, nil, n, err }
n += nn
if n >= len(data) { return 0, nil, n, ErrWrongBufferLength }
length, nn, err := DecodeGBEU[uint64](data[n:])
if err != nil { return 0, nil, n, err }
n += nn
end := n + int(length)
if end > len(data) { return 0, nil, n, ErrWrongBufferLength }
value = data[n:end]
n += int(length)
return tag, value, n, err
}
}
func DecodeTableIter(data []byte) iter.Seq2[uint16, []byte] {
return func(yield func(uint16, []byte) bool) {
pull := DecodeTable(data)
for {
tag, value, _, err := pull()
if err != nil { return }
if !yield(tag, value) { return }
}
}
}
func EncodeTable(data []byte) TablePushFunc {
return func(tag uint16, value []byte) (n int, err error) {
if n >= len(data) { return n, ErrWrongBufferLength }
nn, err := EncodeI16(data[n:], uint16(tag))
if err != nil { return n, err }
n += nn
if n >= len(data) { return n, ErrWrongBufferLength }
nn, err = EncodeGBEU(data[n:], uint(len(value)))
if err != nil { return n, err }
n += nn
if n >= len(data) { return n, ErrWrongBufferLength }
nn = copy(data[n:], value)
n += nn
if nn < len(value) {
return n, ErrWrongBufferLength
}
if n >= len(data) { return n, ErrWrongBufferLength }
data = data[n:]
return n, nil
}
}
func TableSize(itemLengths ...int) int {
sum := 0
for _, length := range itemLengths {
sum += GBEUSize(uint(length)) + length
}
return sum
}

144
tape/table_test.go Normal file
View File

@@ -0,0 +1,144 @@
package tape
import "fmt"
import "slices"
// import "errors"
import "testing"
// import "math/rand"
var longText =
`Curious, I started off in that direction, only for Prism to stop me. "Wrong way, Void. It's right over here." He trotted over to a door to the left of us. It was marked with the number '4004'. He took a key out of his saddlebags, unlocked it, and pushed it open. "You know, some say this suite is haunted. They call the ghost that lives here the 'Spirit of 4004'. Ooooh!" He made paddling motions in the air with his hooves.`
func TestTable(test *testing.T) {
item5 := []byte("hello")
item7 := []byte("world")
item0 := []byte(longText)
item3249 := []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }
buffer := [512]byte { }
push := EncodeTable(buffer[:])
_, err := push(5, item5)
if err != nil { test.Fatal(err)}
_, err = push(7, item7)
if err != nil { test.Fatal(err)}
_, err = push(0, item0)
if err != nil { test.Fatal(err)}
_, err = push(3249, item3249)
if err != nil { test.Fatal(err)}
test.Logf("len of longText: %d 0x%X", len(longText), len(longText))
correct := []byte("\x00\x05\x05hello\x00\x07\x05world\x00\x00\x83\x28" + longText)
if got := buffer[:len(correct)]; !slices.Equal(got, correct) {
if !compareHexArray(test, got, correct) { test.Fatal("failed") }
}
pull := DecodeTable(buffer[:len(correct)])
tag, value, _, err := pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(5); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("hello")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(7); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("world")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(0); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte(longText)) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(3249); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }) { test.Fatal("failed") }
}
func TestTableSmall(test *testing.T) {
item2 := []byte("hello")
item3249 := []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }
buffer := [64]byte { }
push := EncodeTable(buffer[:])
_, err := push(2, item2)
if err != nil { test.Fatal(err) }
_, err = push(3249, item3249)
if err != nil { test.Fatal(err) }
correct := []byte("\x00\x02\x05hello\x0C\xB1\x06\x00\x01\x02\x03\xA0\x05")
if got := buffer[:len(correct)]; !slices.Equal(got, correct) {
if !compareHexArray(test, got, correct) { test.Fatal("failed") }
}
pull := DecodeTable(buffer[:len(correct)])
tag, value, _, err := pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(2); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("hello")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(3249); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }) { test.Fatal("failed") }
}
func dumpHexArray(data []byte) (message string) {
for _, item := range data {
message = fmt.Sprintf("%s %02X", message, item)
}
return message
}
func compareHexArray(test *testing.T, got, correct []byte) bool {
index := 0
for {
if index >= len(correct) {
if index < len(got) {
test.Log("correct longer than got")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
return false
}
break
}
if index >= len(got) {
if index < len(correct) {
test.Log("got longer than correct")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
return false
}
break
}
if correct[index] != got[index] {
test.Log("not equal")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
partLow := index - 8
partHigh := index + 8
test.Log("got part ", dumpHexArray(safeSlice(got, partLow, partHigh)))
test.Log("correct part", dumpHexArray(safeSlice(correct, partLow, partHigh)))
return false
}
index ++
}
return true
}
func safeSlice[T any](slice []T, low, high int) []T {
return slice[max(low, 0):min(high, len(slice))]
}

View File

@@ -3,6 +3,8 @@ package tape
import "fmt"
// encoding and decoding functions must not make any allocations
const dataMaxSize = 0xFFFF
const uint16Max = 0xFFFF
@@ -10,6 +12,7 @@ const uint16Max = 0xFFFF
type Error string; const (
ErrWrongBufferLength Error = "wrong buffer length"
ErrDataTooLarge Error = "data too large"
ErrGBEUNotTerminated Error = "GBEU not terminated"
)
// Error implements the error interface.
@@ -25,58 +28,60 @@ type Int16 interface { ~uint16 | ~int16 }
type Int32 interface { ~uint32 | ~int32 }
// Int64 is any 64-bit integer.
type Int64 interface { ~uint64 | ~int64 }
// Uint is any unsigned integer.
type Uint interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }
// String is any string.
type String interface { ~string }
type String interface { ~string | ~[]uint8 }
// DecodeI8 decodes an 8 bit integer from the given data.
func DecodeI8[T Int8](data []byte) (T, error) {
if len(data) != 1 { return 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
return T(data[0]), nil
func DecodeI8[T Int8](data []byte) (value T, n int, err error) {
if len(data) < 1 { return 0, 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
return T(data[0]), 1, nil
}
// EncodeI8 encodes an 8 bit integer into the given buffer.
func EncodeI8[T Int8](buffer []byte, value T) error {
if len(buffer) != 1 { return fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
func EncodeI8[T Int8](buffer []byte, value T) (n int, err error) {
if len(buffer) < 1 { return 0, fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
buffer[0] = byte(value)
return nil
return 1, nil
}
// DecodeI16 decodes a 16 bit integer from the given data.
func DecodeI16[T Int16](data []byte) (T, error) {
if len(data) != 2 { return 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
return T(data[0]) << 8 | T(data[1]), nil
func DecodeI16[T Int16](data []byte) (value T, n int, err error) {
if len(data) < 2 { return 0, 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
return T(data[0]) << 8 | T(data[1]), 2, nil
}
// EncodeI16 encodes a 16 bit integer into the given buffer.
func EncodeI16[T Int16](buffer []byte, value T) error {
if len(buffer) != 2 { return fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
func EncodeI16[T Int16](buffer []byte, value T) (n int, err error) {
if len(buffer) < 2 { return 0, fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 8)
buffer[1] = byte(value)
return nil
return 2, nil
}
// DecodeI32 decodes a 32 bit integer from the given data.
func DecodeI32[T Int32](data []byte) (T, error) {
if len(data) != 4 { return 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
func DecodeI32[T Int32](data []byte) (value T, n int, err error) {
if len(data) < 4 { return 0, 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
return T(data[0]) << 24 |
T(data[1]) << 16 |
T(data[2]) << 8 |
T(data[3]), nil
T(data[3]), 4, nil
}
// EncodeI32 encodes a 32 bit integer into the given buffer.
func EncodeI32[T Int32](buffer []byte, value T) error {
if len(buffer) != 4 { return fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
func EncodeI32[T Int32](buffer []byte, value T) (n int, err error) {
if len(buffer) < 4 { return 0, fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 24)
buffer[1] = byte(value >> 16)
buffer[2] = byte(value >> 8)
buffer[3] = byte(value)
return nil
return 4, nil
}
// DecodeI64 decodes a 64 bit integer from the given data.
func DecodeI64[T Int64](data []byte) (T, error) {
if len(data) != 8 { return 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
func DecodeI64[T Int64](data []byte) (value T, n int, err error) {
if len(data) < 8 { return 0, 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
return T(data[0]) << 56 |
T(data[1]) << 48 |
T(data[2]) << 40 |
@@ -84,12 +89,12 @@ func DecodeI64[T Int64](data []byte) (T, error) {
T(data[4]) << 24 |
T(data[5]) << 16 |
T(data[6]) << 8 |
T(data[7]), nil
T(data[7]), 8, nil
}
// EncodeI64 encodes a 64 bit integer into the given buffer.
func EncodeI64[T Int64](buffer []byte, value T) error {
if len(buffer) != 8 { return fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
func EncodeI64[T Int64](buffer []byte, value T) (n int, err error) {
if len(buffer) < 8 { return 0, fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 56)
buffer[1] = byte(value >> 48)
buffer[2] = byte(value >> 40)
@@ -98,19 +103,63 @@ func EncodeI64[T Int64](buffer []byte, value T) error {
buffer[5] = byte(value >> 16)
buffer[6] = byte(value >> 8)
buffer[7] = byte(value)
return nil
return 8, nil
}
// DecodeGBEU decodes an 8 to 64 bit growing integer into the given buffer.
func DecodeGBEU[T Uint](data []byte) (value T, n int, err error) {
var fullValue uint64
for _, chunk := range data {
fullValue *= 0x80
fullValue += uint64(chunk & 0x7F)
ccb := chunk >> 7
n += 1
if ccb == 0 {
return T(fullValue), n, nil
}
}
return 0, n, fmt.Errorf("decoding GBEU: %w", ErrGBEUNotTerminated)
}
// EncodeGBEU encodes an 8 to 64 bit growing integer into a given buffer.
func EncodeGBEU[T Uint] (buffer []byte, value T) (n int, err error) {
window := (GBEUSize(value) - 1) * 7
index := 0
for window >= 0 {
if index >= len(buffer) { return index, fmt.Errorf("encoding GBEU: %w", ErrWrongBufferLength) }
chunk := uint8(value >> window) & 0x7F
if window > 0 {
chunk |= 0x80
}
buffer[index] = chunk
index += 1
window -= 7
}
return index, nil
}
// GBEUSize returns the size (in octets) of a GBEU integer.
func GBEUSize[T Uint] (value T) int {
length := 0
for {
value >>= 7
length ++
if value == 0 { return length }
}
}
// DecodeString decodes a string from the given data.
func DecodeString[T String](data []byte) (T, error) {
return T(data), nil
func DecodeString[T String](data []byte) (value T, n int, err error) {
return T(data), len(data), nil
}
// EncodeString encodes a string into the given buffer.
func EncodeString[T String](data []byte, value T) error {
if len(data) != len(value) { return fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
copy(data, value)
return nil
func EncodeString[T String](data []byte, value T) (n int, err error) {
if len(data) != len(value) { return 0, fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
return copy(data, value), nil
}
// StringSize returns the size of a string. Returns 0 and an error if the size
@@ -120,186 +169,6 @@ func StringSize[T String](value T) (int, error) {
return len(value), nil
}
// DecodeStringArray decodes a packed string array from the given data.
func DecodeStringArray[T String](data []byte) ([]T, error) {
result := []T { }
for len(data) > 0 {
if len(data) < 2 { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
itemSize16, _ := DecodeI16[uint16](data[:2])
itemSize := int(itemSize16)
data = data[2:]
if len(data) < itemSize { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
result = append(result, T(data[:itemSize]))
data = data[itemSize:]
}
return result, nil
}
// EncodeStringArray encodes a packed string array into the given buffer.
func EncodeStringArray[T String](buffer []byte, value []T) error {
for _, item := range value {
length, err := StringSize(item)
if err != nil { return err }
if len(buffer) < 2 + length { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
EncodeI16(buffer[:2], uint16(length))
buffer = buffer[2:]
copy(buffer, item)
buffer = buffer[length:]
}
if len(buffer) > 0 { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
return nil
}
// StringArraySize returns the size of a packed string array. Returns 0 and an
// error if the size is too large.
func StringArraySize[T String](value []T) (int, error) {
total := 0
for _, item := range value {
total += 2 + len(item)
}
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI8Array decodes a packed array of 8 bit integers from the given data.
func DecodeI8Array[T Int8](data []byte) ([]T, error) {
result := make([]T, len(data))
for index, item := range data {
result[index] = T(item)
}
return result, nil
}
// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer.
func EncodeI8Array[T Int8](buffer []byte, value []T) error {
if len(buffer) != len(value) { return fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) }
for index, item := range value {
buffer[index] = byte(item)
}
return nil
}
// I8ArraySize returns the size of a packed 8 bit integer array. Returns 0 and
// an error if the size is too large.
func I8ArraySize[T Int8](value []T) (int, error) {
total := len(value)
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI16Array decodes a packed array of 16 bit integers from the given data.
func DecodeI16Array[T Int16](data []byte) ([]T, error) {
if len(data) % 2 != 0 { return nil, fmt.Errorf("decoding []int16: %w", ErrWrongBufferLength) }
length := len(data) / 2
result := make([]T, length)
for index := range length {
offset := index * 2
result[index] = T(data[offset]) << 8 | T(data[offset + 1])
}
return result, nil
}
// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer.
func EncodeI16Array[T Int16](buffer []byte, value []T) error {
if len(buffer) != len(value) * 2 { return fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 8)
buffer[1] = byte(item)
buffer = buffer[2:]
}
return nil
}
// I16ArraySize returns the size of a packed 16 bit integer array. Returns 0 and
// an error if the size is too large.
func I16ArraySize[T Int16](value []T) (int, error) {
total := len(value) * 2
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI32Array decodes a packed array of 32 bit integers from the given data.
func DecodeI32Array[T Int32](data []byte) ([]T, error) {
if len(data) % 4 != 0 { return nil, fmt.Errorf("decoding []int32: %w", ErrWrongBufferLength) }
length := len(data) / 4
result := make([]T, length)
for index := range length {
offset := index * 4
result[index] =
T(data[offset + 0]) << 24 |
T(data[offset + 1]) << 16 |
T(data[offset + 2]) << 8 |
T(data[offset + 3])
}
return result, nil
}
// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer.
func EncodeI32Array[T Int32](buffer []byte, value []T) error {
if len(buffer) != len(value) * 4 { return fmt.Errorf("encoding []int32: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 24)
buffer[1] = byte(item >> 16)
buffer[2] = byte(item >> 8)
buffer[3] = byte(item)
buffer = buffer[4:]
}
return nil
}
// I32ArraySize returns the size of a packed 32 bit integer array. Returns 0 and
// an error if the size is too large.
func I32ArraySize[T Int32](value []T) (int, error) {
total := len(value) * 4
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI64Array decodes a packed array of 32 bit integers from the given data.
func DecodeI64Array[T Int64](data []byte) ([]T, error) {
if len(data) % 8 != 0 { return nil, fmt.Errorf("decoding []int64: %w", ErrWrongBufferLength) }
length := len(data) / 8
result := make([]T, length)
for index := range length {
offset := index * 8
result[index] =
T(data[offset + 0]) << 56 |
T(data[offset + 1]) << 48 |
T(data[offset + 2]) << 40 |
T(data[offset + 3]) << 32 |
T(data[offset + 4]) << 24 |
T(data[offset + 5]) << 16 |
T(data[offset + 6]) << 8 |
T(data[offset + 7])
}
return result, nil
}
// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer.
func EncodeI64Array[T Int64](buffer []byte, value []T) error {
if len(buffer) != len(value) * 8 { return fmt.Errorf("encoding []int64: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 56)
buffer[1] = byte(item >> 48)
buffer[2] = byte(item >> 40)
buffer[3] = byte(item >> 32)
buffer[4] = byte(item >> 24)
buffer[5] = byte(item >> 16)
buffer[6] = byte(item >> 8)
buffer[7] = byte(item)
buffer = buffer[8:]
}
return nil
}
// I64ArraySize returns the size of a packed 64 bit integer array. Returns 0 and
// an error if the size is too large.
func I64ArraySize[T Int64](value []T) (int, error) {
total := len(value) * 8
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// U16CastSafe safely casts an integer to a uint16. If an overflow or underflow
// occurs, it will return (0, false).
func U16CastSafe(n int) (uint16, bool) {

View File

@@ -1,5 +1,6 @@
package tape
import "fmt"
import "slices"
import "errors"
import "testing"
@@ -10,20 +11,26 @@ const randStringBytes = "-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU
func TestI8(test *testing.T) {
var buffer [16]byte
err := EncodeI8[uint8](buffer[:], 5)
_, err := EncodeI8[uint8](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI8[uint8](buffer[:0], 5)
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
err = EncodeI8[uint8](buffer[:0], 5)
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI8[uint8](buffer[:])
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI8[uint8](buffer[:0])
_, _, err = DecodeI8[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI8[uint8](buffer[:0])
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
for number := range uint8(255) {
err := EncodeI8[uint8](buffer[:1], number)
n, err := EncodeI8[uint8](buffer[:1], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI8[uint8](buffer[:1])
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI8[uint8](buffer[:1])
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
@@ -32,21 +39,27 @@ func TestI8(test *testing.T) {
func TestI16(test *testing.T) {
var buffer [16]byte
err := EncodeI16[uint16](buffer[:], 5)
_, err := EncodeI16[uint16](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI16[uint16](buffer[:0], 5)
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
err = EncodeI16[uint16](buffer[:0], 5)
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI16[uint16](buffer[:])
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI16[uint16](buffer[:0])
_, _, err = DecodeI16[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI16[uint16](buffer[:0])
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint16(rand.Int())
err := EncodeI16[uint16](buffer[:2], number)
n, err := EncodeI16[uint16](buffer[:2], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI16[uint16](buffer[:2])
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI16[uint16](buffer[:2])
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
@@ -55,21 +68,27 @@ func TestI16(test *testing.T) {
func TestI32(test *testing.T) {
var buffer [16]byte
err := EncodeI32[uint32](buffer[:], 5)
_, err := EncodeI32[uint32](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI32[uint32](buffer[:0], 5)
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
err = EncodeI32[uint32](buffer[:0], 5)
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI32[uint32](buffer[:])
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI32[uint32](buffer[:0])
_, _, err = DecodeI32[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI32[uint32](buffer[:0])
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint32(rand.Int())
err := EncodeI32[uint32](buffer[:4], number)
n, err := EncodeI32[uint32](buffer[:4], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI32[uint32](buffer[:4])
if correct, got := 4, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI32[uint32](buffer[:4])
if err != nil { test.Fatal(err) }
if correct, got := 4, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
@@ -78,44 +97,182 @@ func TestI32(test *testing.T) {
func TestI64(test *testing.T) {
var buffer [16]byte
err := EncodeI64[uint64](buffer[:], 5)
_, err := EncodeI64[uint64](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI64[uint64](buffer[:0], 5)
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
err = EncodeI64[uint64](buffer[:0], 5)
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI64[uint64](buffer[:])
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI64[uint64](buffer[:0])
_, _, err = DecodeI64[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI64[uint64](buffer[:0])
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint64(rand.Int())
err := EncodeI64[uint64](buffer[:8], number)
n, err := EncodeI64[uint64](buffer[:8], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI64[uint64](buffer[:8])
if correct, got := 8, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI64[uint64](buffer[:8])
if err != nil { test.Fatal(err) }
if correct, got := 8, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestGBEU(test *testing.T) {
var buffer = [16]byte {
255, 255, 255, 255,
255, 255, 255, 255,
255, 255, 255, 255,
255, 255, 255, 255,
}
_, err := EncodeGBEU[uint64](buffer[:0], 5)
if err == nil { test.Fatal("no error") }
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
_, err = EncodeGBEU[uint64](buffer[:2], 5555555)
if err == nil { test.Fatal("no error") }
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeGBEU[uint64](buffer[:0])
if err == nil { test.Fatal("no error") }
if err.Error() != "decoding GBEU: GBEU not terminated" { test.Fatal(err) }
n, err := EncodeGBEU[uint64](buffer[:], 0x97)
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := []byte { 0x81, 0x17 }, buffer[:2]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err := DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0x97), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
n, err = EncodeGBEU[uint64](buffer[:], 0x123456)
if err != nil { test.Fatal(err) }
if correct, got := 3, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := []byte { 0xc8, 0xe8, 0x56 }, buffer[:3]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 3, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0x123456), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
maxGBEU64 := []byte { 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }
n, err = EncodeGBEU[uint64](buffer[:], 0xFFFFFFFFFFFFFFFF)
if err != nil { test.Fatal(err) }
if correct, got := 10, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := maxGBEU64, buffer[:10]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 10, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0xFFFFFFFFFFFFFFFF), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
n, err = EncodeGBEU[uint64](buffer[:], 11)
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := byte(0xb), buffer[0]; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0xb), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
for _ = range largeNumberNTestRounds {
buffer = [16]byte { }
number := uint64(rand.Int())
_, err := EncodeGBEU[uint64](buffer[:], number)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Error("not equal:")
test.Errorf("%d != %d", decoded, number)
test.Errorf("%x != %x", decoded, number)
test.Fatal(buffer)
}
}
}
func TestGBEUSize(test *testing.T) {
if correct, got := 3, GBEUSize(uint(0x100000)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 3, GBEUSize(uint(0x123456)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 4, GBEUSize(uint(0x223456)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 1, GBEUSize(uint(0)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 1, GBEUSize(uint(127)); correct != got {
test.Fatal("not equal:", got)
}
}
func TestString(test *testing.T) {
var buffer [16]byte
err := EncodeString[string](buffer[:], "hello")
_, err := EncodeString[string](buffer[:], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
err = EncodeString[string](buffer[:0], "hello")
_, err = EncodeString[string](buffer[:0], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
_, err = DecodeString[string](buffer[:])
_, _, err = DecodeString[string](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeString[string](buffer[:0])
_, _, err = DecodeString[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
length := rand.Intn(16)
str := randString(length)
err := EncodeString[string](buffer[:length], str)
_, err := EncodeString[string](buffer[:length], str)
if err != nil { test.Fatal(err) }
decoded, err := DecodeString[string](buffer[:length])
decoded, _, err := DecodeString[string](buffer[:length])
if err != nil { test.Fatal(err) }
if decoded != str {
test.Fatalf("%s != %s", decoded, str)
@@ -123,129 +280,6 @@ func TestString(test *testing.T) {
}
}
func TestI8Array(test *testing.T) {
var buffer [64]byte
err := EncodeI8Array[uint8](buffer[:], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
err = EncodeI8Array[uint8](buffer[:0], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI8Array[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI8Array[uint8](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint8](rand.Intn(16))
length, _ := I8ArraySize(array)
if length != len(array) { test.Fatalf("%d != %d", length, len(array)) }
err := EncodeI8Array[uint8](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI8Array[uint8](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI16Array(test *testing.T) {
var buffer [128]byte
err := EncodeI16Array[uint16](buffer[:], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
err = EncodeI16Array[uint16](buffer[:0], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI16Array[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI16Array[uint16](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint16](rand.Intn(16))
length, _ := I16ArraySize(array)
if length != 2 * len(array) { test.Fatalf("%d != %d", length, 2 * len(array)) }
err := EncodeI16Array[uint16](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI16Array[uint16](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI32Array(test *testing.T) {
var buffer [256]byte
err := EncodeI32Array[uint32](buffer[:], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
err = EncodeI32Array[uint32](buffer[:0], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI32Array[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI32Array[uint32](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint32](rand.Intn(16))
length, _ := I32ArraySize(array)
if length != 4 * len(array) { test.Fatalf("%d != %d", length, 4 * len(array)) }
err := EncodeI32Array[uint32](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI32Array[uint32](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI64Array(test *testing.T) {
var buffer [512]byte
err := EncodeI64Array[uint64](buffer[:], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
err = EncodeI64Array[uint64](buffer[:0], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI64Array[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI64Array[uint64](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint64](rand.Intn(16))
length, _ := I64ArraySize(array)
if length != 8 * len(array) { test.Fatalf("%d != %d", length, 8 * len(array)) }
err := EncodeI64Array[uint64](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI64Array[uint64](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestStringArray(test *testing.T) {
var buffer [8192]byte
err := EncodeStringArray[string](buffer[:], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
err = EncodeStringArray[string](buffer[:0], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, err = DecodeStringArray[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randStrings[string](rand.Intn(16), 16)
length, _ := StringArraySize(array)
// TODO test length
err := EncodeStringArray[string](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeStringArray[string](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestU16CastSafe(test *testing.T) {
number, ok := U16CastSafe(90_000)
if ok { test.Fatalf("false positive: %v, %v", number, ok) }