Compare commits
31 Commits
quic-initi
...
99392d50fc
| Author | SHA1 | Date | |
|---|---|---|---|
| 99392d50fc | |||
| c4a985f622 | |||
| 58514f6afe | |||
| 83fa77ba13 | |||
| 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 |
100
codec/decode.go
Normal file
100
codec/decode.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package codec
|
||||
|
||||
import "io"
|
||||
|
||||
// Decoder wraps an [io.Reader] and decodes data from it.
|
||||
type Decoder struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// ReadFull calls [io.ReadFull] on the reader.
|
||||
func (this *Decoder) ReadFull(buffer []byte) (n int, err error) {
|
||||
return io.ReadFull(this, buffer)
|
||||
}
|
||||
|
||||
// ReadByte decodes a single byte from the input reader.
|
||||
func (this *Decoder) ReadByte() (value byte, n int, err error) {
|
||||
uncasted, n, err := this.ReadUint8()
|
||||
return byte(uncasted), n, err
|
||||
}
|
||||
|
||||
// ReadInt8 decodes an 8-bit signed integer from the input reader.
|
||||
func (this *Decoder) ReadInt8() (value int8, n int, err error) {
|
||||
uncasted, n, err := this.ReadUint8()
|
||||
return int8(uncasted), n, err
|
||||
}
|
||||
|
||||
// ReadUint8 decodes an 8-bit unsigned integer from the input reader.
|
||||
func (this *Decoder) ReadUint8() (value uint8, n int, err error) {
|
||||
buffer := [1]byte { }
|
||||
n, err = this.ReadFull(buffer[:])
|
||||
return uint8(buffer[0]), n, err
|
||||
}
|
||||
|
||||
// ReadInt16 decodes an 16-bit signed integer from the input reader.
|
||||
func (this *Decoder) ReadInt16() (value int16, n int, err error) {
|
||||
uncasted, n, err := this.ReadUint16()
|
||||
return int16(uncasted), n, err
|
||||
}
|
||||
|
||||
// ReadUint16 decodes an 16-bit unsigned integer from the input reader.
|
||||
func (this *Decoder) ReadUint16() (value uint16, n int, err error) {
|
||||
buffer := [2]byte { }
|
||||
n, err = this.ReadFull(buffer[:])
|
||||
return uint16(buffer[0]) << 8 |
|
||||
uint16(buffer[1]), n, err
|
||||
}
|
||||
|
||||
// ReadInt32 decodes an 32-bit signed integer from the input reader.
|
||||
func (this *Decoder) ReadInt32() (value int32, n int, err error) {
|
||||
uncasted, n, err := this.ReadUint32()
|
||||
return int32(uncasted), n, err
|
||||
}
|
||||
|
||||
// ReadUint32 decodes an 32-bit unsigned integer from the input reader.
|
||||
func (this *Decoder) ReadUint32() (value uint32, n int, err error) {
|
||||
buffer := [4]byte { }
|
||||
n, err = this.ReadFull(buffer[:])
|
||||
return uint32(buffer[0]) << 24 |
|
||||
uint32(buffer[1]) << 16 |
|
||||
uint32(buffer[2]) << 8 |
|
||||
uint32(buffer[3]), n, err
|
||||
}
|
||||
|
||||
// ReadInt64 decodes an 64-bit signed integer from the input reader.
|
||||
func (this *Decoder) ReadInt64() (value int64, n int, err error) {
|
||||
uncasted, n, err := this.ReadUint64()
|
||||
return int64(uncasted), n, err
|
||||
}
|
||||
|
||||
// ReadUint64 decodes an 64-bit unsigned integer from the input reader.
|
||||
func (this *Decoder) ReadUint64() (value uint64, n int, err error) {
|
||||
buffer := [8]byte { }
|
||||
n, err = this.ReadFull(buffer[:])
|
||||
return uint64(buffer[0]) << 56 |
|
||||
uint64(buffer[1]) << 48 |
|
||||
uint64(buffer[2]) << 48 |
|
||||
uint64(buffer[3]) << 32 |
|
||||
uint64(buffer[4]) << 24 |
|
||||
uint64(buffer[5]) << 16 |
|
||||
uint64(buffer[6]) << 8 |
|
||||
uint64(buffer[7]), n, err
|
||||
}
|
||||
|
||||
// ReadGBEU decodes a growing unsigned integer of up to 64 bits from the input
|
||||
// reader.
|
||||
func (this *Decoder) ReadGBEU() (value uint64, n int, err error) {
|
||||
var fullValue uint64
|
||||
for {
|
||||
chunk, nn, err := this.ReadByte()
|
||||
if err != nil { return 0, n, err }
|
||||
n += nn
|
||||
|
||||
fullValue *= 0x80
|
||||
fullValue += uint64(chunk & 0x7F)
|
||||
ccb := chunk >> 7
|
||||
if ccb == 0 {
|
||||
return fullValue, n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
95
codec/encode.go
Normal file
95
codec/encode.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package codec
|
||||
|
||||
import "io"
|
||||
|
||||
// Encoder wraps an [io.Writer] and encodes data to it.
|
||||
type Encoder struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// WriteByte encodes a single byte to the output writer.
|
||||
func (this *Encoder) WriteByte(value byte) (n int, err error) {
|
||||
return this.WriteByte(uint8(value))
|
||||
}
|
||||
|
||||
// WriteInt8 encodes an 8-bit signed integer to the output writer.
|
||||
func (this *Encoder) WriteInt8(value int8) (n int, err error) {
|
||||
return this.WriteUint8(uint8(value))
|
||||
}
|
||||
|
||||
// WriteUint8 encodes an 8-bit unsigned integer to the output writer.
|
||||
func (this *Encoder) WriteUint8(value uint8) (n int, err error) {
|
||||
return this.Write([]byte { byte(value) })
|
||||
}
|
||||
|
||||
// WriteInt16 encodes an 16-bit signed integer to the output writer.
|
||||
func (this *Encoder) WriteInt16(value int16) (n int, err error) {
|
||||
return this.WriteUint16(uint16(value))
|
||||
}
|
||||
|
||||
// WriteUint16 encodes an 16-bit unsigned integer to the output writer.
|
||||
func (this *Encoder) WriteUint16(value uint16) (n int, err error) {
|
||||
return this.Write([]byte {
|
||||
byte(value >> 8),
|
||||
byte(value),
|
||||
})
|
||||
}
|
||||
|
||||
// WriteInt32 encodes an 32-bit signed integer to the output writer.
|
||||
func (this *Encoder) WriteInt32(value int32) (n int, err error) {
|
||||
return this.WriteUint32(uint32(value))
|
||||
}
|
||||
|
||||
// WriteUint32 encodes an 32-bit unsigned integer to the output writer.
|
||||
func (this *Encoder) WriteUint32(value uint32) (n int, err error) {
|
||||
return this.Write([]byte {
|
||||
byte(value >> 24),
|
||||
byte(value >> 16),
|
||||
byte(value >> 8),
|
||||
byte(value),
|
||||
})
|
||||
}
|
||||
|
||||
// WriteInt64 encodes an 64-bit signed integer to the output writer.
|
||||
func (this *Encoder) WriteInt64(value int64) (n int, err error) {
|
||||
return this.WriteUint64(uint64(value))
|
||||
}
|
||||
|
||||
// WriteUint64 encodes an 64-bit unsigned integer to the output writer.
|
||||
func (this *Encoder) WriteUint64(value uint64) (n int, err error) {
|
||||
return this.Write([]byte {
|
||||
byte(value >> 56),
|
||||
byte(value >> 48),
|
||||
byte(value >> 40),
|
||||
byte(value >> 32),
|
||||
byte(value >> 24),
|
||||
byte(value >> 16),
|
||||
byte(value >> 8),
|
||||
byte(value),
|
||||
})
|
||||
}
|
||||
|
||||
// EncodeGBEU encodes a growing unsigned integer of up to 64 bits to the output
|
||||
// writer.
|
||||
func (this *Encoder) EncodeGBEU(value uint64) (n int, err error) {
|
||||
// increase if go somehow gets support for over 64 bit integers. we
|
||||
// could also make an expanding int type in goutil to use here, or maybe
|
||||
// there is one in the stdlib. keep this int64 version as well though
|
||||
// because its ergonomic.
|
||||
buffer := [16]byte { }
|
||||
|
||||
window := (GBEUSize(value) - 1) * 7
|
||||
index := 0
|
||||
for window >= 0 {
|
||||
chunk := uint8(value >> window) & 0x7F
|
||||
if window > 0 {
|
||||
chunk |= 0x80
|
||||
}
|
||||
buffer[index] = chunk
|
||||
|
||||
index += 1
|
||||
window -= 7
|
||||
}
|
||||
|
||||
return this.Write(buffer[:])
|
||||
}
|
||||
11
codec/measure.go
Normal file
11
codec/measure.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package codec
|
||||
|
||||
// GBEUSize returns the size (in octets) of a GBEU integer.
|
||||
func GBEUSize(value uint64) int {
|
||||
length := 0
|
||||
for {
|
||||
value >>= 7
|
||||
length ++
|
||||
if value == 0 { return length }
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,10 @@ dependant on which transport is being used.
|
||||
A message refers to a block of octets sent within a transaction, paired with an
|
||||
unsigned 16-bit method code. The order of messages within a given transaction is
|
||||
preserved, but the order of messages accross the entire connection is not
|
||||
guaranteed.
|
||||
|
||||
The message payload must be 65,535 (unsigned 16-bit integer limit) octets or
|
||||
smaller in length. This does not include the method code. Applications are free
|
||||
to send whatever data they wish as the payload, but TAPE is recommended for
|
||||
encoding it.
|
||||
guaranteed. There is no functional limit on the size of a message payload, but
|
||||
there may be one depending on which
|
||||
[METADAPT sub-protocol](#message-and-transaction-demarcation-protocol-metadapt)
|
||||
is in use.
|
||||
|
||||
Method codes should be written in upper-case base 16 with the prefix "M" in
|
||||
logs, error messages, documentation, etc. For example, the method code 62,206 in
|
||||
@@ -37,88 +35,74 @@ fucking with you.
|
||||
## Table Pair Encoding (TAPE)
|
||||
The Table Pair Encoding (TAPE) scheme is a method for encoding structured data
|
||||
within HOPP messages. It defines standard binary encoding methods for common
|
||||
data types, as well as a corruption-resistant table structure that maps numeric
|
||||
IDs to values. It is designed to allow applications to be presented with data
|
||||
they are not equipped to handle while continuing to function normally. This
|
||||
enables backwards compatibile application protocol changes.
|
||||
data types, as well as aggregate data types such as tables and arrays. It is
|
||||
designed to allow applications to be presented with data they are not equipped
|
||||
to handle while continuing to function normally. This enables backwards
|
||||
compatibile application protocol changes.
|
||||
|
||||
### Table Structure
|
||||
A table is divided into two sections: the header, and the values. The header
|
||||
begins with the number (U16) of pairs in the table, which is then followed by
|
||||
that many tag-offset pairs. A tag-offset pair consists of a numerical (U16) tag,
|
||||
followed the position (U16) of the value relative to the start of the values
|
||||
section. The values section contains the value data for each pair, where the
|
||||
start of each value is determined by its offset, and the end is determined by
|
||||
the offset of the next value, or the end of the message if there is no value
|
||||
after it.
|
||||
TAPE expresses types using tags. A tag is 8 bits in size, and is divided into
|
||||
two parts: the Type Number (TN), and the Configuration Number (CN). The TN is 3
|
||||
bits, and the CN is 5 bits. Both are interpreted as unsigned integers. Both
|
||||
sides of the connection must agree on the semantic meaning of the values and
|
||||
their arrangement.
|
||||
|
||||
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.
|
||||
TAPE is based on an encoding method previously developed by silt.
|
||||
|
||||
### 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. They are discussed
|
||||
in detail in the following sections.
|
||||
|
||||
| Name | Size | Description | Encoding Method
|
||||
| ----------- | --------------: | --------------------------- | ---------------
|
||||
| I8 | 1 | A signed 8-bit integer | BETC
|
||||
| I16 | 2 | A signed 16-bit integer | BETC
|
||||
| I32 | 4 | A signed 32-bit integer | BETC
|
||||
| I64 | 8 | A signed 64-bit integer | BETC
|
||||
| U8 | 1 | An unsigned 8-bit integer | BEU
|
||||
| U16 | 2 | An unsigned 16-bit integer | BEU
|
||||
| U32 | 4 | An unsigned 32-bit integer | BEU
|
||||
| U64 | 8 | An unsigned 64-bit integer | BEU
|
||||
| Array[^1] | SOP[^2] | An array of any above type | PASTA
|
||||
| String | N/A | A UTF-8 string | UTF-8
|
||||
| StringArray | n * 2 + SOP[^2] | An array the String type | VILA
|
||||
| TN | Bits | Name | Description
|
||||
| -: | ---: | ---- | -----------
|
||||
| 0 | 000 | SI | Small integer
|
||||
| 1 | 001 | LI | Large integer
|
||||
| 2 | 010 | FP | Floating point
|
||||
| 3 | 011 | SBA | Small byte array
|
||||
| 4 | 100 | LBA | Large byte array
|
||||
| 5 | 101 | OTA | One-tag array
|
||||
| 6 | 110 | KTV | Key-tag-value table
|
||||
| 7 | 111 | N/A | Reserved
|
||||
|
||||
[^1]: Array types are written as <E>Array, where <E> is the element type. For
|
||||
example, an array of I32 would be written as I32Array. StringArray still follows
|
||||
this rule, even though it is encoded differently from other arrays. Nesting
|
||||
arrays inside of arrays is prohibited. This problem can be avoided in most cases
|
||||
by effectively utilizing the table structure, or by improving the design of
|
||||
your protocol.
|
||||
#### Small Integer (SI)
|
||||
SI encodes an integer of up to 5 bits, which are stored in the CN. It has no
|
||||
payload. Whether the bits are interpreted as unsigned or as signed two's
|
||||
complement is semantic information and must be agreed upon by both sides of the
|
||||
connection. Thus, the value may range from 0 to 31 if unsigned, and from -16 to
|
||||
17 if signed.
|
||||
|
||||
[^2]: SOP (sum of parts) refers to the sum of the size of every item in a data
|
||||
structure.
|
||||
#### Large Integer (LI)
|
||||
LI encodes an integer of up to 256 bits, which are stored in the payload. The CN
|
||||
determine the length of the payload in bytes. The integer is big-endian. Whether
|
||||
the payload is interpreted as unsigned or as signed two's complement is semantic
|
||||
information and must be agreed upon by both sides of the connection. Thus, the
|
||||
value may range from 0 to 31 if unsigned, and from -16 to 17 if signed.
|
||||
|
||||
### Encoding Methods
|
||||
Below are all encoding methods supported by TAPE.
|
||||
#### Floating Point (FP)
|
||||
FP encodes an IEEE 754 floating point number of up to 256 bits, which are stored
|
||||
in the payload. The CN determines the length of the payload in bytes, and it may
|
||||
only be one of these values: 16, 32, 64, 128, or 256.
|
||||
|
||||
#### BETC
|
||||
Big-Endian, Two's Complement signed integer. The size is defined as the least
|
||||
amount of whole octets which can fit all bits in the integer, regardless if the
|
||||
bits are on or off. Therefore, the size cannot change at runtime.
|
||||
#### Small Byte Array (SBA)
|
||||
SBA encodes an array of up to 32 bytes, which are stored in the paylod. The
|
||||
CN determines the length of the payload in bytes.
|
||||
|
||||
#### BEU
|
||||
Big-Endian, Unsigned integer. The size is defined as the least amount of whole
|
||||
octets which can fit all bits in the integer, regardless if the bits are on or
|
||||
off. Therefore, the size cannot change at runtime.
|
||||
#### Large Byte Array (LBA)
|
||||
LBA encodes an array of up to 2^256 bytes, which are stored in the second part
|
||||
of the payload, directly after the length. The length of the data length field
|
||||
in bytes is determined by the CN.
|
||||
|
||||
#### PASTA
|
||||
Packed Single-Type Array. The size is defined as the size of an individual item
|
||||
times the number of items. Items are placed one after the other with no gaps
|
||||
in-between them, except as required to align the start of each item to the
|
||||
nearest whole octet. Items should be of the same type and must be of the same
|
||||
size.
|
||||
#### One-Tag Array (OTA)
|
||||
OTA encodes an array of up to 2^256 items, which are stored in the payload after
|
||||
the length field and the item tag, where the length field comes first. Each item
|
||||
must be the same length, as they all share the same tag. The length of the data
|
||||
length field in bytes is determined by the CN.
|
||||
|
||||
#### UTF-8
|
||||
UTF-8 string. The size is defined as the least amount of whole octets which can
|
||||
fit all bits in the string, regardless if the bits are on or off. The size of
|
||||
this type is not fixed and may change at runtime, so this needs to be accounted
|
||||
for during use.
|
||||
|
||||
#### VILA
|
||||
Variable Item Length Array. The size is defined as the least amount of whole
|
||||
octets which can fit each item plus one U16 per item. The size of this type is
|
||||
not fixed and may change at runtime, so this needs to be accounted for during
|
||||
use. The amount of items must be greater than zero. Items are each prefixed by
|
||||
their size (in octets) encoded as a U16, and they are placed one after the other
|
||||
with no gaps in-between them, except as required to align the start of each item
|
||||
to the nearest whole octet. Items should be of the same type but do not need to
|
||||
be of the same size.
|
||||
#### Key-Tag-Value Table (KTV)
|
||||
KTV encodes a table of up to 2^256 key/value pairs, which are stored in the
|
||||
payload after the length field. The pairs themselves consist of a 16-bit
|
||||
unsigned big-endian key followed by a tag and then the payload. Pair values can
|
||||
be of different types and sizes. The order of the pairs is not significant and
|
||||
should never be treated as such.
|
||||
|
||||
## Transports
|
||||
A transport is a protocol that HOPP connections can run on top of. HOPP
|
||||
@@ -169,7 +153,6 @@ sun will have expanded to swallow earth by then. Your connection will not last
|
||||
that long.
|
||||
|
||||
#### Message Chunking
|
||||
|
||||
The most significant bit of the payload size field of an MMB is called the Chunk
|
||||
Control Bit (CCB). If the CCB of a given MMB is zero, the represented message is
|
||||
interpreted as being self-contained and the data is processed immediately. If
|
||||
|
||||
23
dial.go
23
dial.go
@@ -1,9 +1,9 @@
|
||||
package hopp
|
||||
|
||||
import "net"
|
||||
import "errors"
|
||||
import "context"
|
||||
import "crypto/tls"
|
||||
import "github.com/quic-go/quic-go"
|
||||
|
||||
// Dial opens a connection to a server. The network must be one of "quic",
|
||||
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, "quic4" and
|
||||
@@ -19,9 +19,8 @@ type Dialer struct {
|
||||
}
|
||||
|
||||
// Dial opens a connection to a server. The network must be one of "quic",
|
||||
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, "quic4" and
|
||||
// "quic6" don't do anything as the quic-go package doesn't seem to support this
|
||||
// behavior.
|
||||
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is not
|
||||
// supported.
|
||||
func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, error) {
|
||||
switch network {
|
||||
case "quic", "quic4", "quic6": return diale.dialQUIC(ctx, network, address)
|
||||
@@ -31,12 +30,7 @@ func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, er
|
||||
}
|
||||
|
||||
func (diale Dialer) dialQUIC(ctx context.Context, network, address string) (Conn, error) {
|
||||
// sorry i fucking lied to you about the network parameter. for all
|
||||
// quic-go's bullshit bloat, it doesnt even support that. not even when
|
||||
// instantiating a transport. go figure :/
|
||||
conn, err := quic.DialAddr(ctx, address, tlsConfig(diale.TLSConfig), quicConfig())
|
||||
if err != nil { return nil, err }
|
||||
return AdaptB(quicMultiConn { underlying: conn }), nil
|
||||
return nil, errors.New("quic is not yet implemented")
|
||||
}
|
||||
|
||||
func (diale Dialer) dialUnix(ctx context.Context, network, address string) (Conn, error) {
|
||||
@@ -60,15 +54,6 @@ func tlsConfig(conf *tls.Config) *tls.Config {
|
||||
return conf
|
||||
}
|
||||
|
||||
func quicConfig() *quic.Config {
|
||||
return &quic.Config {
|
||||
// TODO: perhaps we might want to put something here
|
||||
// the quic config shouldn't be exported, just set up
|
||||
// automatically. we can't have that strangely built quic-go
|
||||
// package be part of the API, or any third-party packages for
|
||||
// that matter. it must all be abstracted away.
|
||||
}
|
||||
}
|
||||
|
||||
func quicNetworkToUDPNetwork(network string) (string, error) {
|
||||
switch network {
|
||||
|
||||
14
go.mod
14
go.mod
@@ -5,18 +5,4 @@ go 1.23.0
|
||||
require (
|
||||
git.tebibyte.media/sashakoshka/go-util v0.9.1
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62
|
||||
github.com/quic-go/quic-go v0.48.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
)
|
||||
|
||||
56
go.sum
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/go.mod h1:0Q1t+PePdx6tFYkRuJNcpM1Mru7wE6X+it1kwuOH+6Y=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
39
listen.go
39
listen.go
@@ -1,9 +1,8 @@
|
||||
package hopp
|
||||
|
||||
import "net"
|
||||
import "context"
|
||||
import "errors"
|
||||
import "crypto/tls"
|
||||
import "github.com/quic-go/quic-go"
|
||||
|
||||
// Listener is an object which listens for incoming HOPP connections.
|
||||
type Listener interface {
|
||||
@@ -17,7 +16,8 @@ type Listener interface {
|
||||
}
|
||||
|
||||
// Listen listens for incoming HOPP connections. The network must be one of
|
||||
// "quic", "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix".
|
||||
// "quic", "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is
|
||||
// not supported.
|
||||
func Listen(network, address string) (Listener, error) {
|
||||
switch network {
|
||||
case "quic", "quic4", "quic6": return ListenQUIC(network, address, nil)
|
||||
@@ -30,19 +30,8 @@ func Listen(network, address string) (Listener, error) {
|
||||
// The network must be one of "quic", "quic4", (IPv4-only) or "quic6"
|
||||
// (IPv6-only).
|
||||
func ListenQUIC(network, address string, tlsConf *tls.Config) (Listener, error) {
|
||||
tlsConf = tlsConfig(tlsConf)
|
||||
quicConf := quicConfig()
|
||||
udpNetwork, err := quicNetworkToUDPNetwork(network)
|
||||
if err != nil { return nil, err }
|
||||
addr, err := net.ResolveUDPAddr(udpNetwork, address)
|
||||
if err != nil { return nil, err }
|
||||
udpListener, err := net.ListenUDP(udpNetwork, addr)
|
||||
if err != nil { return nil, err }
|
||||
quicListener, err := quic.Listen(udpListener, tlsConf, quicConf)
|
||||
if err != nil { return nil, err }
|
||||
return &listenerQUIC {
|
||||
underlying: quicListener,
|
||||
}, nil
|
||||
// tlsConf = tlsConfig(tlsConf)
|
||||
return nil, errors.New("quic is not yet implemented")
|
||||
}
|
||||
|
||||
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
|
||||
@@ -58,24 +47,6 @@ func ListenUnix(network, address string) (Listener, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
type listenerQUIC struct {
|
||||
underlying *quic.Listener
|
||||
}
|
||||
|
||||
func (this *listenerQUIC) Accept() (Conn, error) {
|
||||
conn, err := this.underlying.Accept(context.Background())
|
||||
if err != nil { return nil, err }
|
||||
return AdaptB(quicMultiConn { underlying: conn }), nil
|
||||
}
|
||||
|
||||
func (this *listenerQUIC) Close() error {
|
||||
return this.underlying.Close()
|
||||
}
|
||||
|
||||
func (this *listenerQUIC) Addr() net.Addr {
|
||||
return this.underlying.Addr()
|
||||
}
|
||||
|
||||
type listenerUnix struct {
|
||||
underlying *net.UnixListener
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
// encoding and decoding functions must not make any allocations
|
||||
|
||||
const dataMaxSize = 0xFFFF
|
||||
const uint16Max = 0xFFFF
|
||||
|
||||
@@ -10,6 +12,7 @@ const uint16Max = 0xFFFF
|
||||
type Error string; const (
|
||||
ErrWrongBufferLength Error = "wrong buffer length"
|
||||
ErrDataTooLarge Error = "data too large"
|
||||
ErrGBEUNotTerminated Error = "GBEU not terminated"
|
||||
)
|
||||
|
||||
// Error implements the error interface.
|
||||
@@ -25,58 +28,60 @@ type Int16 interface { ~uint16 | ~int16 }
|
||||
type Int32 interface { ~uint32 | ~int32 }
|
||||
// Int64 is any 64-bit integer.
|
||||
type Int64 interface { ~uint64 | ~int64 }
|
||||
// Uint is any unsigned integer.
|
||||
type Uint interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }
|
||||
// String is any string.
|
||||
type String interface { ~string }
|
||||
type String interface { ~string | ~[]uint8 }
|
||||
|
||||
// DecodeI8 decodes an 8 bit integer from the given data.
|
||||
func DecodeI8[T Int8](data []byte) (T, error) {
|
||||
if len(data) != 1 { return 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]), nil
|
||||
func DecodeI8[T Int8](data []byte) (value T, n int, err error) {
|
||||
if len(data) < 1 { return 0, 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]), 1, nil
|
||||
}
|
||||
|
||||
// EncodeI8 encodes an 8 bit integer into the given buffer.
|
||||
func EncodeI8[T Int8](buffer []byte, value T) error {
|
||||
if len(buffer) != 1 { return fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
|
||||
func EncodeI8[T Int8](buffer []byte, value T) (n int, err error) {
|
||||
if len(buffer) < 1 { return 0, fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
|
||||
buffer[0] = byte(value)
|
||||
return nil
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// DecodeI16 decodes a 16 bit integer from the given data.
|
||||
func DecodeI16[T Int16](data []byte) (T, error) {
|
||||
if len(data) != 2 { return 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]) << 8 | T(data[1]), nil
|
||||
func DecodeI16[T Int16](data []byte) (value T, n int, err error) {
|
||||
if len(data) < 2 { return 0, 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]) << 8 | T(data[1]), 2, nil
|
||||
}
|
||||
|
||||
// EncodeI16 encodes a 16 bit integer into the given buffer.
|
||||
func EncodeI16[T Int16](buffer []byte, value T) error {
|
||||
if len(buffer) != 2 { return fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
|
||||
func EncodeI16[T Int16](buffer []byte, value T) (n int, err error) {
|
||||
if len(buffer) < 2 { return 0, fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
|
||||
buffer[0] = byte(value >> 8)
|
||||
buffer[1] = byte(value)
|
||||
return nil
|
||||
return 2, nil
|
||||
}
|
||||
|
||||
// DecodeI32 decodes a 32 bit integer from the given data.
|
||||
func DecodeI32[T Int32](data []byte) (T, error) {
|
||||
if len(data) != 4 { return 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
|
||||
func DecodeI32[T Int32](data []byte) (value T, n int, err error) {
|
||||
if len(data) < 4 { return 0, 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]) << 24 |
|
||||
T(data[1]) << 16 |
|
||||
T(data[2]) << 8 |
|
||||
T(data[3]), nil
|
||||
T(data[3]), 4, nil
|
||||
}
|
||||
|
||||
// EncodeI32 encodes a 32 bit integer into the given buffer.
|
||||
func EncodeI32[T Int32](buffer []byte, value T) error {
|
||||
if len(buffer) != 4 { return fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
|
||||
func EncodeI32[T Int32](buffer []byte, value T) (n int, err error) {
|
||||
if len(buffer) < 4 { return 0, fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
|
||||
buffer[0] = byte(value >> 24)
|
||||
buffer[1] = byte(value >> 16)
|
||||
buffer[2] = byte(value >> 8)
|
||||
buffer[3] = byte(value)
|
||||
return nil
|
||||
return 4, nil
|
||||
}
|
||||
|
||||
// DecodeI64 decodes a 64 bit integer from the given data.
|
||||
func DecodeI64[T Int64](data []byte) (T, error) {
|
||||
if len(data) != 8 { return 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
|
||||
func DecodeI64[T Int64](data []byte) (value T, n int, err error) {
|
||||
if len(data) < 8 { return 0, 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
|
||||
return T(data[0]) << 56 |
|
||||
T(data[1]) << 48 |
|
||||
T(data[2]) << 40 |
|
||||
@@ -84,12 +89,12 @@ func DecodeI64[T Int64](data []byte) (T, error) {
|
||||
T(data[4]) << 24 |
|
||||
T(data[5]) << 16 |
|
||||
T(data[6]) << 8 |
|
||||
T(data[7]), nil
|
||||
T(data[7]), 8, nil
|
||||
}
|
||||
|
||||
// EncodeI64 encodes a 64 bit integer into the given buffer.
|
||||
func EncodeI64[T Int64](buffer []byte, value T) error {
|
||||
if len(buffer) != 8 { return fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
|
||||
func EncodeI64[T Int64](buffer []byte, value T) (n int, err error) {
|
||||
if len(buffer) < 8 { return 0, fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
|
||||
buffer[0] = byte(value >> 56)
|
||||
buffer[1] = byte(value >> 48)
|
||||
buffer[2] = byte(value >> 40)
|
||||
@@ -98,19 +103,63 @@ func EncodeI64[T Int64](buffer []byte, value T) error {
|
||||
buffer[5] = byte(value >> 16)
|
||||
buffer[6] = byte(value >> 8)
|
||||
buffer[7] = byte(value)
|
||||
return nil
|
||||
return 8, nil
|
||||
}
|
||||
|
||||
// DecodeGBEU decodes an 8 to 64 bit growing integer into the given buffer.
|
||||
func DecodeGBEU[T Uint](data []byte) (value T, n int, err error) {
|
||||
var fullValue uint64
|
||||
for _, chunk := range data {
|
||||
fullValue *= 0x80
|
||||
fullValue += uint64(chunk & 0x7F)
|
||||
ccb := chunk >> 7
|
||||
n += 1
|
||||
if ccb == 0 {
|
||||
return T(fullValue), n, nil
|
||||
}
|
||||
}
|
||||
return 0, n, fmt.Errorf("decoding GBEU: %w", ErrGBEUNotTerminated)
|
||||
}
|
||||
|
||||
// EncodeGBEU encodes an 8 to 64 bit growing integer into a given buffer.
|
||||
func EncodeGBEU[T Uint] (buffer []byte, value T) (n int, err error) {
|
||||
window := (GBEUSize(value) - 1) * 7
|
||||
|
||||
index := 0
|
||||
for window >= 0 {
|
||||
if index >= len(buffer) { return index, fmt.Errorf("encoding GBEU: %w", ErrWrongBufferLength) }
|
||||
|
||||
chunk := uint8(value >> window) & 0x7F
|
||||
if window > 0 {
|
||||
chunk |= 0x80
|
||||
}
|
||||
buffer[index] = chunk
|
||||
|
||||
index += 1
|
||||
window -= 7
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// GBEUSize returns the size (in octets) of a GBEU integer.
|
||||
func GBEUSize[T Uint] (value T) int {
|
||||
length := 0
|
||||
for {
|
||||
value >>= 7
|
||||
length ++
|
||||
if value == 0 { return length }
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeString decodes a string from the given data.
|
||||
func DecodeString[T String](data []byte) (T, error) {
|
||||
return T(data), nil
|
||||
func DecodeString[T String](data []byte) (value T, n int, err error) {
|
||||
return T(data), len(data), nil
|
||||
}
|
||||
|
||||
// EncodeString encodes a string into the given buffer.
|
||||
func EncodeString[T String](data []byte, value T) error {
|
||||
if len(data) != len(value) { return fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
|
||||
copy(data, value)
|
||||
return nil
|
||||
func EncodeString[T String](data []byte, value T) (n int, err error) {
|
||||
if len(data) != len(value) { return 0, fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
|
||||
return copy(data, value), nil
|
||||
}
|
||||
|
||||
// StringSize returns the size of a string. Returns 0 and an error if the size
|
||||
@@ -120,186 +169,6 @@ func StringSize[T String](value T) (int, error) {
|
||||
return len(value), nil
|
||||
}
|
||||
|
||||
// DecodeStringArray decodes a packed string array from the given data.
|
||||
func DecodeStringArray[T String](data []byte) ([]T, error) {
|
||||
result := []T { }
|
||||
for len(data) > 0 {
|
||||
if len(data) < 2 { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
|
||||
itemSize16, _ := DecodeI16[uint16](data[:2])
|
||||
itemSize := int(itemSize16)
|
||||
data = data[2:]
|
||||
if len(data) < itemSize { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
|
||||
result = append(result, T(data[:itemSize]))
|
||||
data = data[itemSize:]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeStringArray encodes a packed string array into the given buffer.
|
||||
func EncodeStringArray[T String](buffer []byte, value []T) error {
|
||||
for _, item := range value {
|
||||
length, err := StringSize(item)
|
||||
if err != nil { return err }
|
||||
if len(buffer) < 2 + length { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
|
||||
EncodeI16(buffer[:2], uint16(length))
|
||||
buffer = buffer[2:]
|
||||
copy(buffer, item)
|
||||
buffer = buffer[length:]
|
||||
}
|
||||
if len(buffer) > 0 { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringArraySize returns the size of a packed string array. Returns 0 and an
|
||||
// error if the size is too large.
|
||||
func StringArraySize[T String](value []T) (int, error) {
|
||||
total := 0
|
||||
for _, item := range value {
|
||||
total += 2 + len(item)
|
||||
}
|
||||
if total > dataMaxSize { return 0, ErrDataTooLarge }
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// DecodeI8Array decodes a packed array of 8 bit integers from the given data.
|
||||
func DecodeI8Array[T Int8](data []byte) ([]T, error) {
|
||||
result := make([]T, len(data))
|
||||
for index, item := range data {
|
||||
result[index] = T(item)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer.
|
||||
func EncodeI8Array[T Int8](buffer []byte, value []T) error {
|
||||
if len(buffer) != len(value) { return fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) }
|
||||
for index, item := range value {
|
||||
buffer[index] = byte(item)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// I8ArraySize returns the size of a packed 8 bit integer array. Returns 0 and
|
||||
// an error if the size is too large.
|
||||
func I8ArraySize[T Int8](value []T) (int, error) {
|
||||
total := len(value)
|
||||
if total > dataMaxSize { return 0, ErrDataTooLarge }
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// DecodeI16Array decodes a packed array of 16 bit integers from the given data.
|
||||
func DecodeI16Array[T Int16](data []byte) ([]T, error) {
|
||||
if len(data) % 2 != 0 { return nil, fmt.Errorf("decoding []int16: %w", ErrWrongBufferLength) }
|
||||
length := len(data) / 2
|
||||
result := make([]T, length)
|
||||
for index := range length {
|
||||
offset := index * 2
|
||||
result[index] = T(data[offset]) << 8 | T(data[offset + 1])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer.
|
||||
func EncodeI16Array[T Int16](buffer []byte, value []T) error {
|
||||
if len(buffer) != len(value) * 2 { return fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) }
|
||||
for _, item := range value {
|
||||
buffer[0] = byte(item >> 8)
|
||||
buffer[1] = byte(item)
|
||||
buffer = buffer[2:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// I16ArraySize returns the size of a packed 16 bit integer array. Returns 0 and
|
||||
// an error if the size is too large.
|
||||
func I16ArraySize[T Int16](value []T) (int, error) {
|
||||
total := len(value) * 2
|
||||
if total > dataMaxSize { return 0, ErrDataTooLarge }
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// DecodeI32Array decodes a packed array of 32 bit integers from the given data.
|
||||
func DecodeI32Array[T Int32](data []byte) ([]T, error) {
|
||||
if len(data) % 4 != 0 { return nil, fmt.Errorf("decoding []int32: %w", ErrWrongBufferLength) }
|
||||
length := len(data) / 4
|
||||
result := make([]T, length)
|
||||
for index := range length {
|
||||
offset := index * 4
|
||||
result[index] =
|
||||
T(data[offset + 0]) << 24 |
|
||||
T(data[offset + 1]) << 16 |
|
||||
T(data[offset + 2]) << 8 |
|
||||
T(data[offset + 3])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer.
|
||||
func EncodeI32Array[T Int32](buffer []byte, value []T) error {
|
||||
if len(buffer) != len(value) * 4 { return fmt.Errorf("encoding []int32: %w", ErrWrongBufferLength) }
|
||||
for _, item := range value {
|
||||
buffer[0] = byte(item >> 24)
|
||||
buffer[1] = byte(item >> 16)
|
||||
buffer[2] = byte(item >> 8)
|
||||
buffer[3] = byte(item)
|
||||
buffer = buffer[4:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// I32ArraySize returns the size of a packed 32 bit integer array. Returns 0 and
|
||||
// an error if the size is too large.
|
||||
func I32ArraySize[T Int32](value []T) (int, error) {
|
||||
total := len(value) * 4
|
||||
if total > dataMaxSize { return 0, ErrDataTooLarge }
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// DecodeI64Array decodes a packed array of 32 bit integers from the given data.
|
||||
func DecodeI64Array[T Int64](data []byte) ([]T, error) {
|
||||
if len(data) % 8 != 0 { return nil, fmt.Errorf("decoding []int64: %w", ErrWrongBufferLength) }
|
||||
length := len(data) / 8
|
||||
result := make([]T, length)
|
||||
for index := range length {
|
||||
offset := index * 8
|
||||
result[index] =
|
||||
T(data[offset + 0]) << 56 |
|
||||
T(data[offset + 1]) << 48 |
|
||||
T(data[offset + 2]) << 40 |
|
||||
T(data[offset + 3]) << 32 |
|
||||
T(data[offset + 4]) << 24 |
|
||||
T(data[offset + 5]) << 16 |
|
||||
T(data[offset + 6]) << 8 |
|
||||
T(data[offset + 7])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer.
|
||||
func EncodeI64Array[T Int64](buffer []byte, value []T) error {
|
||||
if len(buffer) != len(value) * 8 { return fmt.Errorf("encoding []int64: %w", ErrWrongBufferLength) }
|
||||
for _, item := range value {
|
||||
buffer[0] = byte(item >> 56)
|
||||
buffer[1] = byte(item >> 48)
|
||||
buffer[2] = byte(item >> 40)
|
||||
buffer[3] = byte(item >> 32)
|
||||
buffer[4] = byte(item >> 24)
|
||||
buffer[5] = byte(item >> 16)
|
||||
buffer[6] = byte(item >> 8)
|
||||
buffer[7] = byte(item)
|
||||
buffer = buffer[8:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// I64ArraySize returns the size of a packed 64 bit integer array. Returns 0 and
|
||||
// an error if the size is too large.
|
||||
func I64ArraySize[T Int64](value []T) (int, error) {
|
||||
total := len(value) * 8
|
||||
if total > dataMaxSize { return 0, ErrDataTooLarge }
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// U16CastSafe safely casts an integer to a uint16. If an overflow or underflow
|
||||
// occurs, it will return (0, false).
|
||||
func U16CastSafe(n int) (uint16, bool) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package tape
|
||||
|
||||
import "fmt"
|
||||
import "slices"
|
||||
import "errors"
|
||||
import "testing"
|
||||
@@ -10,20 +11,26 @@ const randStringBytes = "-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU
|
||||
|
||||
func TestI8(test *testing.T) {
|
||||
var buffer [16]byte
|
||||
err := EncodeI8[uint8](buffer[:], 5)
|
||||
_, err := EncodeI8[uint8](buffer[:], 5)
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = EncodeI8[uint8](buffer[:0], 5)
|
||||
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
|
||||
err = EncodeI8[uint8](buffer[:0], 5)
|
||||
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI8[uint8](buffer[:])
|
||||
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI8[uint8](buffer[:0])
|
||||
_, _, err = DecodeI8[uint8](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, _, err = DecodeI8[uint8](buffer[:0])
|
||||
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
|
||||
|
||||
for number := range uint8(255) {
|
||||
err := EncodeI8[uint8](buffer[:1], number)
|
||||
n, err := EncodeI8[uint8](buffer[:1], number)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI8[uint8](buffer[:1])
|
||||
if correct, got := 1, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
decoded, n, err := DecodeI8[uint8](buffer[:1])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 1, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if decoded != number {
|
||||
test.Fatalf("%d != %d", decoded, number)
|
||||
}
|
||||
@@ -32,21 +39,27 @@ func TestI8(test *testing.T) {
|
||||
|
||||
func TestI16(test *testing.T) {
|
||||
var buffer [16]byte
|
||||
err := EncodeI16[uint16](buffer[:], 5)
|
||||
_, err := EncodeI16[uint16](buffer[:], 5)
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = EncodeI16[uint16](buffer[:0], 5)
|
||||
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
|
||||
err = EncodeI16[uint16](buffer[:0], 5)
|
||||
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI16[uint16](buffer[:])
|
||||
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI16[uint16](buffer[:0])
|
||||
_, _, err = DecodeI16[uint16](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, _, err = DecodeI16[uint16](buffer[:0])
|
||||
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
number := uint16(rand.Int())
|
||||
err := EncodeI16[uint16](buffer[:2], number)
|
||||
n, err := EncodeI16[uint16](buffer[:2], number)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI16[uint16](buffer[:2])
|
||||
if correct, got := 2, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
decoded, n, err := DecodeI16[uint16](buffer[:2])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 2, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if decoded != number {
|
||||
test.Fatalf("%d != %d", decoded, number)
|
||||
}
|
||||
@@ -55,21 +68,27 @@ func TestI16(test *testing.T) {
|
||||
|
||||
func TestI32(test *testing.T) {
|
||||
var buffer [16]byte
|
||||
err := EncodeI32[uint32](buffer[:], 5)
|
||||
_, err := EncodeI32[uint32](buffer[:], 5)
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = EncodeI32[uint32](buffer[:0], 5)
|
||||
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
|
||||
err = EncodeI32[uint32](buffer[:0], 5)
|
||||
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI32[uint32](buffer[:])
|
||||
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI32[uint32](buffer[:0])
|
||||
_, _, err = DecodeI32[uint32](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, _, err = DecodeI32[uint32](buffer[:0])
|
||||
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
number := uint32(rand.Int())
|
||||
err := EncodeI32[uint32](buffer[:4], number)
|
||||
n, err := EncodeI32[uint32](buffer[:4], number)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI32[uint32](buffer[:4])
|
||||
if correct, got := 4, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
decoded, n, err := DecodeI32[uint32](buffer[:4])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 4, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if decoded != number {
|
||||
test.Fatalf("%d != %d", decoded, number)
|
||||
}
|
||||
@@ -78,44 +97,182 @@ func TestI32(test *testing.T) {
|
||||
|
||||
func TestI64(test *testing.T) {
|
||||
var buffer [16]byte
|
||||
err := EncodeI64[uint64](buffer[:], 5)
|
||||
_, err := EncodeI64[uint64](buffer[:], 5)
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = EncodeI64[uint64](buffer[:0], 5)
|
||||
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
|
||||
err = EncodeI64[uint64](buffer[:0], 5)
|
||||
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI64[uint64](buffer[:])
|
||||
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = DecodeI64[uint64](buffer[:0])
|
||||
_, _, err = DecodeI64[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, _, err = DecodeI64[uint64](buffer[:0])
|
||||
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
number := uint64(rand.Int())
|
||||
err := EncodeI64[uint64](buffer[:8], number)
|
||||
n, err := EncodeI64[uint64](buffer[:8], number)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI64[uint64](buffer[:8])
|
||||
if correct, got := 8, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
decoded, n, err := DecodeI64[uint64](buffer[:8])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 8, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if decoded != number {
|
||||
test.Fatalf("%d != %d", decoded, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGBEU(test *testing.T) {
|
||||
var buffer = [16]byte {
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 255,
|
||||
}
|
||||
_, err := EncodeGBEU[uint64](buffer[:0], 5)
|
||||
if err == nil { test.Fatal("no error") }
|
||||
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
|
||||
_, err = EncodeGBEU[uint64](buffer[:2], 5555555)
|
||||
if err == nil { test.Fatal("no error") }
|
||||
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
|
||||
_, _, err = DecodeGBEU[uint64](buffer[:0])
|
||||
if err == nil { test.Fatal("no error") }
|
||||
if err.Error() != "decoding GBEU: GBEU not terminated" { test.Fatal(err) }
|
||||
|
||||
n, err := EncodeGBEU[uint64](buffer[:], 0x97)
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 2, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := []byte { 0x81, 0x17 }, buffer[:2]; !slices.Equal(correct, got) {
|
||||
message := "not equal:"
|
||||
for _, item := range got {
|
||||
message = fmt.Sprintf("%s %x", message, item)
|
||||
}
|
||||
test.Fatal(message)
|
||||
}
|
||||
decoded, n, err := DecodeGBEU[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 2, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := uint64(0x97), decoded; correct != got {
|
||||
test.Fatalf("not equal: %x", got)
|
||||
}
|
||||
|
||||
n, err = EncodeGBEU[uint64](buffer[:], 0x123456)
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 3, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := []byte { 0xc8, 0xe8, 0x56 }, buffer[:3]; !slices.Equal(correct, got) {
|
||||
message := "not equal:"
|
||||
for _, item := range got {
|
||||
message = fmt.Sprintf("%s %x", message, item)
|
||||
}
|
||||
test.Fatal(message)
|
||||
}
|
||||
decoded, n, err = DecodeGBEU[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 3, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := uint64(0x123456), decoded; correct != got {
|
||||
test.Fatalf("not equal: %x", got)
|
||||
}
|
||||
|
||||
maxGBEU64 := []byte { 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }
|
||||
n, err = EncodeGBEU[uint64](buffer[:], 0xFFFFFFFFFFFFFFFF)
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 10, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := maxGBEU64, buffer[:10]; !slices.Equal(correct, got) {
|
||||
message := "not equal:"
|
||||
for _, item := range got {
|
||||
message = fmt.Sprintf("%s %x", message, item)
|
||||
}
|
||||
test.Fatal(message)
|
||||
}
|
||||
decoded, n, err = DecodeGBEU[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 10, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := uint64(0xFFFFFFFFFFFFFFFF), decoded; correct != got {
|
||||
test.Fatalf("not equal: %x", got)
|
||||
}
|
||||
|
||||
n, err = EncodeGBEU[uint64](buffer[:], 11)
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 1, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := byte(0xb), buffer[0]; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
decoded, n, err = DecodeGBEU[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct, got := 1, n; correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := uint64(0xb), decoded; correct != got {
|
||||
test.Fatalf("not equal: %x", got)
|
||||
}
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
buffer = [16]byte { }
|
||||
number := uint64(rand.Int())
|
||||
_, err := EncodeGBEU[uint64](buffer[:], number)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, _, err := DecodeGBEU[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if decoded != number {
|
||||
test.Error("not equal:")
|
||||
test.Errorf("%d != %d", decoded, number)
|
||||
test.Errorf("%x != %x", decoded, number)
|
||||
test.Fatal(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGBEUSize(test *testing.T) {
|
||||
if correct, got := 3, GBEUSize(uint(0x100000)); correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := 3, GBEUSize(uint(0x123456)); correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := 4, GBEUSize(uint(0x223456)); correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := 1, GBEUSize(uint(0)); correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
if correct, got := 1, GBEUSize(uint(127)); correct != got {
|
||||
test.Fatal("not equal:", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestString(test *testing.T) {
|
||||
var buffer [16]byte
|
||||
err := EncodeString[string](buffer[:], "hello")
|
||||
_, err := EncodeString[string](buffer[:], "hello")
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeString[string](buffer[:0], "hello")
|
||||
_, err = EncodeString[string](buffer[:0], "hello")
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeString[string](buffer[:])
|
||||
_, _, err = DecodeString[string](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = DecodeString[string](buffer[:0])
|
||||
_, _, err = DecodeString[string](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
length := rand.Intn(16)
|
||||
str := randString(length)
|
||||
err := EncodeString[string](buffer[:length], str)
|
||||
_, err := EncodeString[string](buffer[:length], str)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeString[string](buffer[:length])
|
||||
decoded, _, err := DecodeString[string](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if decoded != str {
|
||||
test.Fatalf("%s != %s", decoded, str)
|
||||
@@ -123,129 +280,6 @@ func TestString(test *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestI8Array(test *testing.T) {
|
||||
var buffer [64]byte
|
||||
err := EncodeI8Array[uint8](buffer[:], []uint8 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeI8Array[uint8](buffer[:0], []uint8 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeI8Array[uint8](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = DecodeI8Array[uint8](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
array := randInts[uint8](rand.Intn(16))
|
||||
length, _ := I8ArraySize(array)
|
||||
if length != len(array) { test.Fatalf("%d != %d", length, len(array)) }
|
||||
err := EncodeI8Array[uint8](buffer[:length], array)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI8Array[uint8](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !slices.Equal(decoded, array) {
|
||||
test.Fatalf("%v != %v", decoded, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestI16Array(test *testing.T) {
|
||||
var buffer [128]byte
|
||||
err := EncodeI16Array[uint16](buffer[:], []uint16 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeI16Array[uint16](buffer[:0], []uint16 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeI16Array[uint16](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = DecodeI16Array[uint16](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
array := randInts[uint16](rand.Intn(16))
|
||||
length, _ := I16ArraySize(array)
|
||||
if length != 2 * len(array) { test.Fatalf("%d != %d", length, 2 * len(array)) }
|
||||
err := EncodeI16Array[uint16](buffer[:length], array)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI16Array[uint16](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !slices.Equal(decoded, array) {
|
||||
test.Fatalf("%v != %v", decoded, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestI32Array(test *testing.T) {
|
||||
var buffer [256]byte
|
||||
err := EncodeI32Array[uint32](buffer[:], []uint32 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeI32Array[uint32](buffer[:0], []uint32 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeI32Array[uint32](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = DecodeI32Array[uint32](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
array := randInts[uint32](rand.Intn(16))
|
||||
length, _ := I32ArraySize(array)
|
||||
if length != 4 * len(array) { test.Fatalf("%d != %d", length, 4 * len(array)) }
|
||||
err := EncodeI32Array[uint32](buffer[:length], array)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI32Array[uint32](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !slices.Equal(decoded, array) {
|
||||
test.Fatalf("%v != %v", decoded, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestI64Array(test *testing.T) {
|
||||
var buffer [512]byte
|
||||
err := EncodeI64Array[uint64](buffer[:], []uint64 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeI64Array[uint64](buffer[:0], []uint64 { 0, 4, 50, 19 })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeI64Array[uint64](buffer[:])
|
||||
if err != nil { test.Fatal(err) }
|
||||
_, err = DecodeI64Array[uint64](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
array := randInts[uint64](rand.Intn(16))
|
||||
length, _ := I64ArraySize(array)
|
||||
if length != 8 * len(array) { test.Fatalf("%d != %d", length, 8 * len(array)) }
|
||||
err := EncodeI64Array[uint64](buffer[:length], array)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeI64Array[uint64](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !slices.Equal(decoded, array) {
|
||||
test.Fatalf("%v != %v", decoded, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringArray(test *testing.T) {
|
||||
var buffer [8192]byte
|
||||
err := EncodeStringArray[string](buffer[:], []string { "0", "4", "50", "19" })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
|
||||
err = EncodeStringArray[string](buffer[:0], []string { "0", "4", "50", "19" })
|
||||
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
|
||||
_, err = DecodeStringArray[string](buffer[:0])
|
||||
if err != nil { test.Fatal(err) }
|
||||
|
||||
for _ = range largeNumberNTestRounds {
|
||||
array := randStrings[string](rand.Intn(16), 16)
|
||||
length, _ := StringArraySize(array)
|
||||
// TODO test length
|
||||
err := EncodeStringArray[string](buffer[:length], array)
|
||||
if err != nil { test.Fatal(err) }
|
||||
decoded, err := DecodeStringArray[string](buffer[:length])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !slices.Equal(decoded, array) {
|
||||
test.Fatalf("%v != %v", decoded, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestU16CastSafe(test *testing.T) {
|
||||
number, ok := U16CastSafe(90_000)
|
||||
if ok { test.Fatalf("false positive: %v, %v", number, ok) }
|
||||
|
||||
Reference in New Issue
Block a user