Compare commits
27 Commits
quic-initi
...
717754644c
| Author | SHA1 | Date | |
|---|---|---|---|
| 717754644c | |||
| 134daacc03 | |||
| a927b9519e | |||
| 32df336c3e | |||
| 2b3a53052f | |||
| 23c3efa845 | |||
| 0e7e935374 | |||
| dd5325b351 | |||
| 37c3e49833 | |||
| 6e5a7115d3 | |||
| f4fb5e80b9 | |||
| 3a88619f9b | |||
| 568431f4c3 | |||
| f50b2ca0cd | |||
| 3d8a012477 | |||
| 4f3b256821 | |||
| 2080d60793 | |||
| c3337641bc | |||
| 4438210963 | |||
| 8d5ba2fa39 | |||
| a05c034313 | |||
| 1b25e306a6 | |||
| dd5e7e96d5 | |||
| 835d623087 | |||
| 83443b8c88 | |||
| 0b98c768b3 | |||
| 218949bd46 |
@@ -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
|
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
|
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
|
preserved, but the order of messages accross the entire connection is not
|
||||||
guaranteed.
|
guaranteed. There is no functional limit on the size of a message payload, but
|
||||||
|
there may be one depending on which
|
||||||
The message payload must be 65,535 (unsigned 16-bit integer limit) octets or
|
[METADAPT sub-protocol](#message-and-transaction-demarcation-protocol-metadapt)
|
||||||
smaller in length. This does not include the method code. Applications are free
|
is in use.
|
||||||
to send whatever data they wish as the payload, but TAPE is recommended for
|
|
||||||
encoding it.
|
|
||||||
|
|
||||||
Method codes should be written in upper-case base 16 with the prefix "M" in
|
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
|
logs, error messages, documentation, etc. For example, the method code 62,206 in
|
||||||
@@ -37,25 +35,17 @@ fucking with you.
|
|||||||
## Table Pair Encoding (TAPE)
|
## Table Pair Encoding (TAPE)
|
||||||
The Table Pair Encoding (TAPE) scheme is a method for encoding structured data
|
The Table Pair Encoding (TAPE) scheme is a method for encoding structured data
|
||||||
within HOPP messages. It defines standard binary encoding methods for common
|
within HOPP messages. It defines standard binary encoding methods for common
|
||||||
data types, as well as a corruption-resistant table structure that maps numeric
|
data types, as well as aggregate data types such as tables and arrays. It is
|
||||||
IDs to values. It is designed to allow applications to be presented with data
|
designed to allow applications to be presented with data they are not equipped
|
||||||
they are not equipped to handle while continuing to function normally. This
|
to handle while continuing to function normally. This enables backwards
|
||||||
enables backwards compatibile application protocol changes.
|
compatibile application protocol changes.
|
||||||
|
|
||||||
### Table Structure
|
The length of a TAPE structure is assumed to be given by the surrounding
|
||||||
A table is divided into two sections: the header, and the values. The header
|
protocol, which is usually METADAPT-A or B. The root of a TAPE structure can be
|
||||||
begins with the number (U16) of pairs in the table, which is then followed by
|
any data value, but is usually a table, which can contain several values that
|
||||||
that many tag-offset pairs. A tag-offset pair consists of a numerical (U16) tag,
|
each have a numeric key. Values can also be nested. Both sides of the connection
|
||||||
followed the position (U16) of the value relative to the start of the values
|
must agree on what data type should be the root value, the data type of each
|
||||||
section. The values section contains the value data for each pair, where the
|
known table value, etc.
|
||||||
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.
|
|
||||||
|
|
||||||
### Data Value Types
|
### Data Value Types
|
||||||
The table below lists all data value types supported by TAPE.
|
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
|
| U16 | 2 | An unsigned 16-bit integer | BEU
|
||||||
| U32 | 4 | An unsigned 32-bit integer | BEU
|
| U32 | 4 | An unsigned 32-bit integer | BEU
|
||||||
| U64 | 8 | An unsigned 64-bit integer | BEU
|
| U64 | 8 | An unsigned 64-bit integer | BEU
|
||||||
| Array[^1] | SOP[^2] | An array of any above type | PASTA
|
| Array[^1] | | An array of any above type | PASTA
|
||||||
| String | N/A | A UTF-8 string | UTF-8
|
| String | | A UTF-8 string | UTF-8
|
||||||
| StringArray | n * 2 + SOP[^2] | An array the String type | VILA
|
| 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
|
[^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
|
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
|
this rule, even though it is encoded differently from other arrays.
|
||||||
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.
|
|
||||||
|
|
||||||
[^2]: SOP (sum of parts) refers to the sum of the size of every item in a data
|
[^2]: SOP (sum of parts) refers to the sum of the size of every item in a data
|
||||||
structure.
|
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
|
octets which can fit all bits in the integer, regardless if the bits are on or
|
||||||
off. Therefore, the size cannot change at runtime.
|
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
|
#### PASTA
|
||||||
Packed Single-Type Array. The size is defined as the size of an individual item
|
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
|
times the number of items. Items are placed one after the other with no gaps
|
||||||
@@ -112,13 +109,23 @@ for during use.
|
|||||||
|
|
||||||
#### VILA
|
#### VILA
|
||||||
Variable Item Length Array. The size is defined as the least amount of whole
|
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
|
octets which can fit each item plus one GBEU per item describing that item's
|
||||||
not fixed and may change at runtime, so this needs to be accounted for during
|
size. The size of this type is not fixed and may change at runtime, so this
|
||||||
use. The amount of items must be greater than zero. Items are each prefixed by
|
needs to be accounted for during use. The amount of items must be greater than
|
||||||
their size (in octets) encoded as a U16, and they are placed one after the other
|
zero. Items are each prefixed by their size (in octets) encoded as a GBEU, and
|
||||||
with no gaps in-between them, except as required to align the start of each item
|
they are placed one after the other with no gaps in-between them, except as
|
||||||
to the nearest whole octet. Items should be of the same type but do not need to
|
required to align the start of each item to the nearest whole octet. Items
|
||||||
be of the same size.
|
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
|
## Transports
|
||||||
A transport is a protocol that HOPP connections can run on top of. HOPP
|
A transport is a protocol that HOPP connections can run on top of. HOPP
|
||||||
|
|||||||
23
dial.go
23
dial.go
@@ -1,9 +1,9 @@
|
|||||||
package hopp
|
package hopp
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
import "errors"
|
||||||
import "context"
|
import "context"
|
||||||
import "crypto/tls"
|
import "crypto/tls"
|
||||||
import "github.com/quic-go/quic-go"
|
|
||||||
|
|
||||||
// Dial opens a connection to a server. The network must be one of "quic",
|
// 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
|
// "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",
|
// 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
|
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is not
|
||||||
// "quic6" don't do anything as the quic-go package doesn't seem to support this
|
// supported.
|
||||||
// behavior.
|
|
||||||
func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, error) {
|
func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, error) {
|
||||||
switch network {
|
switch network {
|
||||||
case "quic", "quic4", "quic6": return diale.dialQUIC(ctx, network, address)
|
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) {
|
func (diale Dialer) dialQUIC(ctx context.Context, network, address string) (Conn, error) {
|
||||||
// sorry i fucking lied to you about the network parameter. for all
|
return nil, errors.New("quic is not yet implemented")
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (diale Dialer) dialUnix(ctx context.Context, network, address string) (Conn, error) {
|
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
|
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) {
|
func quicNetworkToUDPNetwork(network string) (string, error) {
|
||||||
switch network {
|
switch network {
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -5,18 +5,4 @@ go 1.23.0
|
|||||||
require (
|
require (
|
||||||
git.tebibyte.media/sashakoshka/go-util v0.9.1
|
git.tebibyte.media/sashakoshka/go-util v0.9.1
|
||||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62
|
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
56
go.sum
@@ -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 h1:eGAbLwYhOlh4aq/0w+YnJcxT83yPhXtxnYMzz6K7xGo=
|
||||||
git.tebibyte.media/sashakoshka/go-util v0.9.1/go.mod h1:0Q1t+PePdx6tFYkRuJNcpM1Mru7wE6X+it1kwuOH+6Y=
|
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 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
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=
|
|
||||||
|
|||||||
39
listen.go
39
listen.go
@@ -1,9 +1,8 @@
|
|||||||
package hopp
|
package hopp
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
import "context"
|
import "errors"
|
||||||
import "crypto/tls"
|
import "crypto/tls"
|
||||||
import "github.com/quic-go/quic-go"
|
|
||||||
|
|
||||||
// Listener is an object which listens for incoming HOPP connections.
|
// Listener is an object which listens for incoming HOPP connections.
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
@@ -17,7 +16,8 @@ type Listener interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen listens for incoming HOPP connections. The network must be one of
|
// 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) {
|
func Listen(network, address string) (Listener, error) {
|
||||||
switch network {
|
switch network {
|
||||||
case "quic", "quic4", "quic6": return ListenQUIC(network, address, nil)
|
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"
|
// The network must be one of "quic", "quic4", (IPv4-only) or "quic6"
|
||||||
// (IPv6-only).
|
// (IPv6-only).
|
||||||
func ListenQUIC(network, address string, tlsConf *tls.Config) (Listener, error) {
|
func ListenQUIC(network, address string, tlsConf *tls.Config) (Listener, error) {
|
||||||
tlsConf = tlsConfig(tlsConf)
|
// tlsConf = tlsConfig(tlsConf)
|
||||||
quicConf := quicConfig()
|
return nil, errors.New("quic is not yet implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
|
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
|
||||||
@@ -58,24 +47,6 @@ func ListenUnix(network, address string) (Listener, error) {
|
|||||||
}, nil
|
}, 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 {
|
type listenerUnix struct {
|
||||||
underlying *net.UnixListener
|
underlying *net.UnixListener
|
||||||
}
|
}
|
||||||
|
|||||||
54
quicwrap.go
54
quicwrap.go
@@ -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
284
tape/array.go
Normal 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
131
tape/array_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
74
tape/table.go
Normal 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
144
tape/table_test.go
Normal 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))]
|
||||||
|
}
|
||||||
291
tape/types.go
291
tape/types.go
@@ -3,6 +3,8 @@ package tape
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// encoding and decoding functions must not make any allocations
|
||||||
|
|
||||||
const dataMaxSize = 0xFFFF
|
const dataMaxSize = 0xFFFF
|
||||||
const uint16Max = 0xFFFF
|
const uint16Max = 0xFFFF
|
||||||
|
|
||||||
@@ -10,6 +12,7 @@ const uint16Max = 0xFFFF
|
|||||||
type Error string; const (
|
type Error string; const (
|
||||||
ErrWrongBufferLength Error = "wrong buffer length"
|
ErrWrongBufferLength Error = "wrong buffer length"
|
||||||
ErrDataTooLarge Error = "data too large"
|
ErrDataTooLarge Error = "data too large"
|
||||||
|
ErrGBEUNotTerminated Error = "GBEU not terminated"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
@@ -25,58 +28,60 @@ type Int16 interface { ~uint16 | ~int16 }
|
|||||||
type Int32 interface { ~uint32 | ~int32 }
|
type Int32 interface { ~uint32 | ~int32 }
|
||||||
// Int64 is any 64-bit integer.
|
// Int64 is any 64-bit integer.
|
||||||
type Int64 interface { ~uint64 | ~int64 }
|
type Int64 interface { ~uint64 | ~int64 }
|
||||||
|
// Uint is any unsigned integer.
|
||||||
|
type Uint interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }
|
||||||
// String is any string.
|
// String is any string.
|
||||||
type String interface { ~string }
|
type String interface { ~string | ~[]uint8 }
|
||||||
|
|
||||||
// DecodeI8 decodes an 8 bit integer from the given data.
|
// DecodeI8 decodes an 8 bit integer from the given data.
|
||||||
func DecodeI8[T Int8](data []byte) (T, error) {
|
func DecodeI8[T Int8](data []byte) (value T, n int, err error) {
|
||||||
if len(data) != 1 { return 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
|
if len(data) < 1 { return 0, 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
|
||||||
return T(data[0]), nil
|
return T(data[0]), 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeI8 encodes an 8 bit integer into the given buffer.
|
// EncodeI8 encodes an 8 bit integer into the given buffer.
|
||||||
func EncodeI8[T Int8](buffer []byte, value T) error {
|
func EncodeI8[T Int8](buffer []byte, value T) (n int, err error) {
|
||||||
if len(buffer) != 1 { return fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
|
if len(buffer) < 1 { return 0, fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
|
||||||
buffer[0] = byte(value)
|
buffer[0] = byte(value)
|
||||||
return nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeI16 decodes a 16 bit integer from the given data.
|
// DecodeI16 decodes a 16 bit integer from the given data.
|
||||||
func DecodeI16[T Int16](data []byte) (T, error) {
|
func DecodeI16[T Int16](data []byte) (value T, n int, err error) {
|
||||||
if len(data) != 2 { return 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
|
if len(data) < 2 { return 0, 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
|
||||||
return T(data[0]) << 8 | T(data[1]), nil
|
return T(data[0]) << 8 | T(data[1]), 2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeI16 encodes a 16 bit integer into the given buffer.
|
// EncodeI16 encodes a 16 bit integer into the given buffer.
|
||||||
func EncodeI16[T Int16](buffer []byte, value T) error {
|
func EncodeI16[T Int16](buffer []byte, value T) (n int, err error) {
|
||||||
if len(buffer) != 2 { return fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
|
if len(buffer) < 2 { return 0, fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
|
||||||
buffer[0] = byte(value >> 8)
|
buffer[0] = byte(value >> 8)
|
||||||
buffer[1] = byte(value)
|
buffer[1] = byte(value)
|
||||||
return nil
|
return 2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeI32 decodes a 32 bit integer from the given data.
|
// DecodeI32 decodes a 32 bit integer from the given data.
|
||||||
func DecodeI32[T Int32](data []byte) (T, error) {
|
func DecodeI32[T Int32](data []byte) (value T, n int, err error) {
|
||||||
if len(data) != 4 { return 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
|
if len(data) < 4 { return 0, 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
|
||||||
return T(data[0]) << 24 |
|
return T(data[0]) << 24 |
|
||||||
T(data[1]) << 16 |
|
T(data[1]) << 16 |
|
||||||
T(data[2]) << 8 |
|
T(data[2]) << 8 |
|
||||||
T(data[3]), nil
|
T(data[3]), 4, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeI32 encodes a 32 bit integer into the given buffer.
|
// EncodeI32 encodes a 32 bit integer into the given buffer.
|
||||||
func EncodeI32[T Int32](buffer []byte, value T) error {
|
func EncodeI32[T Int32](buffer []byte, value T) (n int, err error) {
|
||||||
if len(buffer) != 4 { return fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
|
if len(buffer) < 4 { return 0, fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
|
||||||
buffer[0] = byte(value >> 24)
|
buffer[0] = byte(value >> 24)
|
||||||
buffer[1] = byte(value >> 16)
|
buffer[1] = byte(value >> 16)
|
||||||
buffer[2] = byte(value >> 8)
|
buffer[2] = byte(value >> 8)
|
||||||
buffer[3] = byte(value)
|
buffer[3] = byte(value)
|
||||||
return nil
|
return 4, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeI64 decodes a 64 bit integer from the given data.
|
// DecodeI64 decodes a 64 bit integer from the given data.
|
||||||
func DecodeI64[T Int64](data []byte) (T, error) {
|
func DecodeI64[T Int64](data []byte) (value T, n int, err error) {
|
||||||
if len(data) != 8 { return 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
|
if len(data) < 8 { return 0, 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
|
||||||
return T(data[0]) << 56 |
|
return T(data[0]) << 56 |
|
||||||
T(data[1]) << 48 |
|
T(data[1]) << 48 |
|
||||||
T(data[2]) << 40 |
|
T(data[2]) << 40 |
|
||||||
@@ -84,12 +89,12 @@ func DecodeI64[T Int64](data []byte) (T, error) {
|
|||||||
T(data[4]) << 24 |
|
T(data[4]) << 24 |
|
||||||
T(data[5]) << 16 |
|
T(data[5]) << 16 |
|
||||||
T(data[6]) << 8 |
|
T(data[6]) << 8 |
|
||||||
T(data[7]), nil
|
T(data[7]), 8, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeI64 encodes a 64 bit integer into the given buffer.
|
// EncodeI64 encodes a 64 bit integer into the given buffer.
|
||||||
func EncodeI64[T Int64](buffer []byte, value T) error {
|
func EncodeI64[T Int64](buffer []byte, value T) (n int, err error) {
|
||||||
if len(buffer) != 8 { return fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
|
if len(buffer) < 8 { return 0, fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
|
||||||
buffer[0] = byte(value >> 56)
|
buffer[0] = byte(value >> 56)
|
||||||
buffer[1] = byte(value >> 48)
|
buffer[1] = byte(value >> 48)
|
||||||
buffer[2] = byte(value >> 40)
|
buffer[2] = byte(value >> 40)
|
||||||
@@ -98,19 +103,63 @@ func EncodeI64[T Int64](buffer []byte, value T) error {
|
|||||||
buffer[5] = byte(value >> 16)
|
buffer[5] = byte(value >> 16)
|
||||||
buffer[6] = byte(value >> 8)
|
buffer[6] = byte(value >> 8)
|
||||||
buffer[7] = byte(value)
|
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.
|
// DecodeString decodes a string from the given data.
|
||||||
func DecodeString[T String](data []byte) (T, error) {
|
func DecodeString[T String](data []byte) (value T, n int, err error) {
|
||||||
return T(data), nil
|
return T(data), len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeString encodes a string into the given buffer.
|
// EncodeString encodes a string into the given buffer.
|
||||||
func EncodeString[T String](data []byte, value T) error {
|
func EncodeString[T String](data []byte, value T) (n int, err error) {
|
||||||
if len(data) != len(value) { return fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
|
if len(data) != len(value) { return 0, fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
|
||||||
copy(data, value)
|
return copy(data, value), nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSize returns the size of a string. Returns 0 and an error if the size
|
// 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
|
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
|
// U16CastSafe safely casts an integer to a uint16. If an overflow or underflow
|
||||||
// occurs, it will return (0, false).
|
// occurs, it will return (0, false).
|
||||||
func U16CastSafe(n int) (uint16, bool) {
|
func U16CastSafe(n int) (uint16, bool) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package tape
|
package tape
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
import "slices"
|
import "slices"
|
||||||
import "errors"
|
import "errors"
|
||||||
import "testing"
|
import "testing"
|
||||||
@@ -10,20 +11,26 @@ const randStringBytes = "-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU
|
|||||||
|
|
||||||
func TestI8(test *testing.T) {
|
func TestI8(test *testing.T) {
|
||||||
var buffer [16]byte
|
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) }
|
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
|
||||||
err = EncodeI8[uint8](buffer[:0], 5)
|
_, _, err = DecodeI8[uint8](buffer[:])
|
||||||
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
_, err = DecodeI8[uint8](buffer[:])
|
_, _, err = DecodeI8[uint8](buffer[:0])
|
||||||
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
|
|
||||||
_, err = DecodeI8[uint8](buffer[:0])
|
|
||||||
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
|
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
|
||||||
|
|
||||||
for number := range uint8(255) {
|
for number := range uint8(255) {
|
||||||
err := EncodeI8[uint8](buffer[:1], number)
|
n, err := EncodeI8[uint8](buffer[:1], number)
|
||||||
if err != nil { test.Fatal(err) }
|
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 err != nil { test.Fatal(err) }
|
||||||
|
if correct, got := 1, n; correct != got {
|
||||||
|
test.Fatal("not equal:", got)
|
||||||
|
}
|
||||||
if decoded != number {
|
if decoded != number {
|
||||||
test.Fatalf("%d != %d", decoded, number)
|
test.Fatalf("%d != %d", decoded, number)
|
||||||
}
|
}
|
||||||
@@ -32,21 +39,27 @@ func TestI8(test *testing.T) {
|
|||||||
|
|
||||||
func TestI16(test *testing.T) {
|
func TestI16(test *testing.T) {
|
||||||
var buffer [16]byte
|
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) }
|
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
|
||||||
err = EncodeI16[uint16](buffer[:0], 5)
|
_, _, err = DecodeI16[uint16](buffer[:])
|
||||||
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
_, err = DecodeI16[uint16](buffer[:])
|
_, _, err = DecodeI16[uint16](buffer[:0])
|
||||||
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
|
|
||||||
_, err = DecodeI16[uint16](buffer[:0])
|
|
||||||
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
|
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
|
||||||
|
|
||||||
for _ = range largeNumberNTestRounds {
|
for _ = range largeNumberNTestRounds {
|
||||||
number := uint16(rand.Int())
|
number := uint16(rand.Int())
|
||||||
err := EncodeI16[uint16](buffer[:2], number)
|
n, err := EncodeI16[uint16](buffer[:2], number)
|
||||||
if err != nil { test.Fatal(err) }
|
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 err != nil { test.Fatal(err) }
|
||||||
|
if correct, got := 2, n; correct != got {
|
||||||
|
test.Fatal("not equal:", got)
|
||||||
|
}
|
||||||
if decoded != number {
|
if decoded != number {
|
||||||
test.Fatalf("%d != %d", decoded, number)
|
test.Fatalf("%d != %d", decoded, number)
|
||||||
}
|
}
|
||||||
@@ -55,21 +68,27 @@ func TestI16(test *testing.T) {
|
|||||||
|
|
||||||
func TestI32(test *testing.T) {
|
func TestI32(test *testing.T) {
|
||||||
var buffer [16]byte
|
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) }
|
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
|
||||||
err = EncodeI32[uint32](buffer[:0], 5)
|
_, _, err = DecodeI32[uint32](buffer[:])
|
||||||
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
_, err = DecodeI32[uint32](buffer[:])
|
_, _, err = DecodeI32[uint32](buffer[:0])
|
||||||
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
|
|
||||||
_, err = DecodeI32[uint32](buffer[:0])
|
|
||||||
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
|
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
|
||||||
|
|
||||||
for _ = range largeNumberNTestRounds {
|
for _ = range largeNumberNTestRounds {
|
||||||
number := uint32(rand.Int())
|
number := uint32(rand.Int())
|
||||||
err := EncodeI32[uint32](buffer[:4], number)
|
n, err := EncodeI32[uint32](buffer[:4], number)
|
||||||
if err != nil { test.Fatal(err) }
|
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 err != nil { test.Fatal(err) }
|
||||||
|
if correct, got := 4, n; correct != got {
|
||||||
|
test.Fatal("not equal:", got)
|
||||||
|
}
|
||||||
if decoded != number {
|
if decoded != number {
|
||||||
test.Fatalf("%d != %d", decoded, number)
|
test.Fatalf("%d != %d", decoded, number)
|
||||||
}
|
}
|
||||||
@@ -78,44 +97,182 @@ func TestI32(test *testing.T) {
|
|||||||
|
|
||||||
func TestI64(test *testing.T) {
|
func TestI64(test *testing.T) {
|
||||||
var buffer [16]byte
|
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) }
|
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
|
||||||
err = EncodeI64[uint64](buffer[:0], 5)
|
_, _, err = DecodeI64[uint64](buffer[:])
|
||||||
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
_, err = DecodeI64[uint64](buffer[:])
|
_, _, err = DecodeI64[uint64](buffer[:0])
|
||||||
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
|
|
||||||
_, err = DecodeI64[uint64](buffer[:0])
|
|
||||||
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
|
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
|
||||||
|
|
||||||
for _ = range largeNumberNTestRounds {
|
for _ = range largeNumberNTestRounds {
|
||||||
number := uint64(rand.Int())
|
number := uint64(rand.Int())
|
||||||
err := EncodeI64[uint64](buffer[:8], number)
|
n, err := EncodeI64[uint64](buffer[:8], number)
|
||||||
if err != nil { test.Fatal(err) }
|
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 err != nil { test.Fatal(err) }
|
||||||
|
if correct, got := 8, n; correct != got {
|
||||||
|
test.Fatal("not equal:", got)
|
||||||
|
}
|
||||||
if decoded != number {
|
if decoded != number {
|
||||||
test.Fatalf("%d != %d", 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) {
|
func TestString(test *testing.T) {
|
||||||
var buffer [16]byte
|
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) }
|
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) }
|
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) }
|
if err != nil { test.Fatal(err) }
|
||||||
_, err = DecodeString[string](buffer[:0])
|
_, _, err = DecodeString[string](buffer[:0])
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
|
|
||||||
for _ = range largeNumberNTestRounds {
|
for _ = range largeNumberNTestRounds {
|
||||||
length := rand.Intn(16)
|
length := rand.Intn(16)
|
||||||
str := randString(length)
|
str := randString(length)
|
||||||
err := EncodeString[string](buffer[:length], str)
|
_, err := EncodeString[string](buffer[:length], str)
|
||||||
if err != nil { test.Fatal(err) }
|
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 err != nil { test.Fatal(err) }
|
||||||
if decoded != str {
|
if decoded != str {
|
||||||
test.Fatalf("%s != %s", 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) {
|
func TestU16CastSafe(test *testing.T) {
|
||||||
number, ok := U16CastSafe(90_000)
|
number, ok := U16CastSafe(90_000)
|
||||||
if ok { test.Fatalf("false positive: %v, %v", number, ok) }
|
if ok { test.Fatalf("false positive: %v, %v", number, ok) }
|
||||||
|
|||||||
Reference in New Issue
Block a user