Compare commits
147 Commits
272a4da3c2
...
message-si
| Author | SHA1 | Date | |
|---|---|---|---|
| 12fbfa6293 | |||
| 44fb561758 | |||
| 04c352fad6 | |||
| 0ea7e222cc | |||
| ae79a32309 | |||
| e28ab4dc6b | |||
| 80161b37f7 | |||
| 9d40b81e00 | |||
| 80c7d25c73 | |||
| 743a5d4ae0 | |||
| ea17e354a3 | |||
| 4dc8a30ebd | |||
| 15c5f0b2b8 | |||
| 087b6b6690 | |||
| 77bfc45fea | |||
| de6099fadc | |||
| 0097dbeedd | |||
| 2db7ff88c2 | |||
| 4fd15c79a4 | |||
| d6f6a3485c | |||
| 5d0b95d59a | |||
| 756bc79c16 | |||
| a59870cc69 | |||
| 782472aa8f | |||
| 52aa07a98f | |||
| 94041f2abc | |||
| 423f547da3 | |||
| 9278bdcb43 | |||
| 0acf44886a | |||
| a4da33536c | |||
| 2180d29615 | |||
| 2209763666 | |||
| 96c8d7924f | |||
| fdf0aa89a4 | |||
| 1bded9852d | |||
| 8beb9de256 | |||
| dc72cc2010 | |||
| 0e03f84b8a | |||
| 02196edf61 | |||
| 1058615f6f | |||
| 024edfa922 | |||
| fe973af99c | |||
| 52f0d6932e | |||
| 8e14a2c3f1 | |||
| 4fbb70081a | |||
| a108e53cb6 | |||
| 57c30ac669 | |||
| a270c22cb9 | |||
| a99d4dee66 | |||
| c18e251b4a | |||
| 170f79c914 | |||
| 77c6b67d65 | |||
| 195d0f9725 | |||
| fa4f591126 | |||
| 12142706e1 | |||
| 30e9ead1ab | |||
| 1118b11bcd | |||
| 7343cf5853 | |||
| a9f583d2e7 | |||
| c4dd129fc5 | |||
| 2cbf58d558 | |||
| 7dcfc08678 | |||
| 711ac30486 | |||
| b15c3aa76c | |||
| a1bfae443c | |||
| df3fe1280d | |||
| 41b3376fa3 | |||
| fae702edfd | |||
| c86f9b03f2 | |||
| dcbfbe9141 | |||
| 40444ee2f4 | |||
| 59cc90166f | |||
| f222fb02b7 | |||
| 6ecc33a46b | |||
| 5d84636b55 | |||
| f009a970cd | |||
| 8b63166ba1 | |||
| 3ef7de118b | |||
| 51ed6aed9f | |||
| 6017ac1fa3 | |||
| b8047585fb | |||
| ad3973dd9e | |||
| 0f626b2e93 | |||
| 272e47224d | |||
| 2c57423838 | |||
| e2b9e809a8 | |||
| 7e8b272ef0 | |||
| a257902705 | |||
| 4955f66ad6 | |||
| f646207ab1 | |||
| b50a199842 | |||
| b826cbf83e | |||
| b73f9fa7ce | |||
| d3d7b07a74 | |||
| daa6a44179 | |||
| af7669c783 | |||
| 2305814e10 | |||
| 5a3296a842 | |||
| 3bf365a7a9 | |||
| e48be0bc15 | |||
| a210f6112c | |||
| 9ff317d443 | |||
| cdba8ee601 | |||
| e75d7534c1 | |||
| 8a0ae9b03f | |||
| 9bc90b0e17 | |||
| c70c23d137 | |||
| a9d5bb83a2 | |||
| f1df5fa84d | |||
| 76a8f9444a | |||
| 0f20c4cdab | |||
| 1b82f2cd83 | |||
| 6ba70ed046 | |||
| c118a4d7ef | |||
| 877698d402 | |||
| 5989a82bee | |||
| c8a2f03ca1 | |||
| 07fc77c83e | |||
| 2138d47f07 | |||
| e9633770ad | |||
| dcf923b1f3 | |||
| 8f8cd91b5d | |||
| 81ac10508b | |||
| 4930215166 | |||
| e1f58a194a | |||
| 37eccc91c0 | |||
| 08fe3d45dd | |||
| 3eb826735b | |||
| 2a4e88d949 | |||
| aa718cfe9f | |||
| b174015319 | |||
| e16fec3a81 | |||
| 712b4f521c | |||
| 604faf0995 | |||
| 9932abd6c4 | |||
| 1bc0788ff2 | |||
| 477e56d359 | |||
| e3487d26a1 | |||
| 89153dd7bd | |||
| 65e8d51590 | |||
| 7b8240cec6 | |||
| 663cab6b77 | |||
| 376a3f1b46 | |||
| c4407d9759 | |||
| 285e83d995 | |||
| ce503c4689 | |||
| a1f297e5b5 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/generate/test
|
||||||
@@ -4,6 +4,7 @@ import "os"
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
import "strings"
|
import "strings"
|
||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
|
import "git.tebibyte.media/sashakoshka/goparse"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/generate"
|
import "git.tebibyte.media/sashakoshka/hopp/generate"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -18,7 +19,7 @@ func main() {
|
|||||||
input, err := os.Open(source)
|
input, err := os.Open(source)
|
||||||
handleErr(1, err)
|
handleErr(1, err)
|
||||||
defer input.Close()
|
defer input.Close()
|
||||||
protocol, err := generate.ParseReader(input)
|
protocol, err := generate.ParseReader(source, input)
|
||||||
handleErr(1, err)
|
handleErr(1, err)
|
||||||
|
|
||||||
absDestination, err := filepath.Abs(destination)
|
absDestination, err := filepath.Abs(destination)
|
||||||
@@ -30,14 +31,18 @@ func main() {
|
|||||||
|
|
||||||
output, err := os.Create(destination)
|
output, err := os.Create(destination)
|
||||||
handleErr(1, err)
|
handleErr(1, err)
|
||||||
err = protocol.Generate(output, packageName)
|
generator := generate.Generator {
|
||||||
|
Output: output,
|
||||||
|
PackageName: packageName,
|
||||||
|
}
|
||||||
|
_, err = generator.Generate(protocol)
|
||||||
handleErr(1, err)
|
handleErr(1, err)
|
||||||
fmt.Fprintf(os.Stderr, "%s: OK\n", name)
|
fmt.Fprintf(os.Stderr, "%s: OK\n", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleErr(code int, err error) {
|
func handleErr(code int, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], parse.Format(err))
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
codec.go
Normal file
47
codec.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package hopp
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type anyInt16 interface { ~uint16 | ~int16 }
|
||||||
|
type anyInt64 interface { ~uint64 | ~int64 }
|
||||||
|
|
||||||
|
// decodeI16 decodes a 16 bit integer from the given data.
|
||||||
|
func decodeI16[T anyInt16](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
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeI16 encodes a 16 bit integer into the given buffer.
|
||||||
|
func encodeI16[T anyInt16](buffer []byte, value T) error {
|
||||||
|
if len(buffer) != 2 { return fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
|
||||||
|
buffer[0] = byte(value >> 8)
|
||||||
|
buffer[1] = byte(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeI64 decodes a 64 bit integer from the given data.
|
||||||
|
func decodeI64[T anyInt64](data []byte) (T, error) {
|
||||||
|
if len(data) != 8 { return 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
|
||||||
|
return T(data[0]) << 56 |
|
||||||
|
T(data[1]) << 48 |
|
||||||
|
T(data[2]) << 40 |
|
||||||
|
T(data[3]) << 32 |
|
||||||
|
T(data[4]) << 24 |
|
||||||
|
T(data[5]) << 16 |
|
||||||
|
T(data[6]) << 8 |
|
||||||
|
T(data[7]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeI64 encodes a 64 bit integer into the given buffer.
|
||||||
|
func encodeI64[T anyInt64](buffer []byte, value T) error {
|
||||||
|
if len(buffer) != 8 { return fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
|
||||||
|
buffer[0] = byte(value >> 56)
|
||||||
|
buffer[1] = byte(value >> 48)
|
||||||
|
buffer[2] = byte(value >> 40)
|
||||||
|
buffer[3] = byte(value >> 32)
|
||||||
|
buffer[4] = byte(value >> 24)
|
||||||
|
buffer[5] = byte(value >> 16)
|
||||||
|
buffer[6] = byte(value >> 8)
|
||||||
|
buffer[7] = byte(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
108
codec/decode.go
108
codec/decode.go
@@ -1,108 +0,0 @@
|
|||||||
package codec
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// Decodable is any type that can decode itself from a decoder.
|
|
||||||
type Decodable interface {
|
|
||||||
// Decode reads data from decoder, replacing the data of the object. It
|
|
||||||
// returns the amount of bytes written, and an error if the write
|
|
||||||
// stopped early.
|
|
||||||
Decode(decoder *Decoder) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
102
codec/encode.go
102
codec/encode.go
@@ -1,102 +0,0 @@
|
|||||||
package codec
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// Encodable is any type that can write itself to an encoder.
|
|
||||||
type Encodable interface {
|
|
||||||
// Encode sends data to encoder. It returns the amount of bytes written,
|
|
||||||
// and an error if the write stopped early.
|
|
||||||
Encode(encoder *Encoder) (n int, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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[:])
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ package hopp
|
|||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
import "net"
|
import "net"
|
||||||
// import "time"
|
import "time"
|
||||||
|
|
||||||
const defaultSizeLimit int64 = 1024 * 1024 // 1 megabyte
|
const defaultSizeLimit int64 = 1024 * 1024 // 1 megabyte
|
||||||
|
|
||||||
@@ -23,8 +23,13 @@ type Conn interface {
|
|||||||
// be called in a loop to avoid the connection locking up.
|
// be called in a loop to avoid the connection locking up.
|
||||||
AcceptTrans() (Trans, error)
|
AcceptTrans() (Trans, error)
|
||||||
|
|
||||||
|
// SetDeadline operates is [net.Conn.SetDeadline] but for OpenTrans
|
||||||
|
// and AcceptTrans calls.
|
||||||
|
SetDeadline(t time.Time) error
|
||||||
// SetSizeLimit sets a limit (in bytes) for how large messages can be.
|
// SetSizeLimit sets a limit (in bytes) for how large messages can be.
|
||||||
// By default, this limit is 1 megabyte.
|
// By default, this limit is 1 megabyte. Note that this is only
|
||||||
|
// enforced when sending and receiving byte slices, and it does not
|
||||||
|
// apply to [Trans.SendWriter] or [Trans.ReceiveReader].
|
||||||
SetSizeLimit(limit int64)
|
SetSizeLimit(limit int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,8 +45,6 @@ type Trans interface {
|
|||||||
// unique within the connection. This method is safe for concurrent use.
|
// unique within the connection. This method is safe for concurrent use.
|
||||||
ID() int64
|
ID() int64
|
||||||
|
|
||||||
// TODO: add methods for setting send and receive deadlines
|
|
||||||
|
|
||||||
// Send sends a message. This method is not safe for concurrent use.
|
// Send sends a message. This method is not safe for concurrent use.
|
||||||
Send(method uint16, data []byte) error
|
Send(method uint16, data []byte) error
|
||||||
// SendWriter sends data written to an [io.Writer]. The writer must be
|
// SendWriter sends data written to an [io.Writer]. The writer must be
|
||||||
@@ -57,4 +60,12 @@ type Trans interface {
|
|||||||
// previously opened through this function will be discarded. This
|
// previously opened through this function will be discarded. This
|
||||||
// method is not safe for concurrent use, and neither is its result.
|
// method is not safe for concurrent use, and neither is its result.
|
||||||
ReceiveReader() (method uint16, data io.Reader, err error)
|
ReceiveReader() (method uint16, data io.Reader, err error)
|
||||||
|
|
||||||
|
// See the documentation for [net.Conn.SetDeadline].
|
||||||
|
SetDeadline(time.Time) error
|
||||||
|
// TODO
|
||||||
|
// // See the documentation for [net.Conn.SetReadDeadline].
|
||||||
|
// SetReadDeadline(t time.Time) error
|
||||||
|
// // See the documentation for [net.Conn.SetWriteDeadline].
|
||||||
|
// SetWriteDeadline(t time.Time) error
|
||||||
}
|
}
|
||||||
|
|||||||
128
design/branched-generated-encoder.md
Normal file
128
design/branched-generated-encoder.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Branched Generated Decoder
|
||||||
|
|
||||||
|
Pasted here because Tebitea is down
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
TAPE is designed so that the decoder can gloss over data it does not understand.
|
||||||
|
Technically the protocol allows for this, but I completely forgot to implement
|
||||||
|
this in the generated decoder, oops. This would be trivial if TAPE messages were
|
||||||
|
still flat tables, but they aren't, because those aren't useful enough. So,
|
||||||
|
let's analyze the problem.
|
||||||
|
|
||||||
|
## When it happens
|
||||||
|
|
||||||
|
There are two reasons something might not match up with the expected data:
|
||||||
|
|
||||||
|
The first and most obvious is unrecognized keys. If the key is not in the set of
|
||||||
|
recognized keys for a KTV, it should leave the corresponding struct field blank.
|
||||||
|
Once #6 has been implemented, throw an error if the data was not optional.
|
||||||
|
|
||||||
|
The second is wrong types. If we are expecting KTV and get SBA, we should leave
|
||||||
|
the data as empty. The aforementioned concern about #6 also applies here. We
|
||||||
|
don't need to worry about special cases at the structure root, because it would
|
||||||
|
be technically possible to make the structure root an option, so it really is
|
||||||
|
just a normal value. Until #6, we will leave that blank too.
|
||||||
|
|
||||||
|
## Preliminary ideas
|
||||||
|
|
||||||
|
The first is going to be pretty simple. All we need to do is have a skimmer
|
||||||
|
function that skims over TAPE data very, and then call that on the KTV value
|
||||||
|
each time we run into a mystery key. It should only return an error if the
|
||||||
|
structure of the data is malformed in such a way that it cannot continue to the
|
||||||
|
next one. This should be stored in the tape package alongside the dynamic
|
||||||
|
decoding functions, because they will essentially function the same way and
|
||||||
|
could probably share lots of code.
|
||||||
|
|
||||||
|
The second is a bit more complicated because of the existence of KTV and OTA
|
||||||
|
because they are aggregate types. Go types work a bit differently, as if you
|
||||||
|
have an array of an array of an array of ints, that information is represented
|
||||||
|
in one place, whereas TAPE doesn't really do that. All of that information is
|
||||||
|
sort of buried within the data structure, so we don't know what we will be
|
||||||
|
decoding before we actually do it. Whenever we encounter a type we don't expect,
|
||||||
|
we would need to abort decoding of the entire data structure, and then skim over
|
||||||
|
whatever detritus is left, which would literally be in a half-decoded state. The
|
||||||
|
fact that the code is generated flat and thus cannot use return or defer
|
||||||
|
statements contributes to the complexity of this problem. We need to go up, but
|
||||||
|
we can't. There is no up, only forward.
|
||||||
|
|
||||||
|
Of course, the dynamic decoder does not have this problem in the first place
|
||||||
|
because it doesn't expect anything, and constructs the destination to fit
|
||||||
|
whatever it sees in the TAPE structure as it is decoding it. KTVs are completely
|
||||||
|
dynamic because they are implemented as maps, so the only time it needs to
|
||||||
|
completely comprehend a type is with OTAs. There is a function called typeOf
|
||||||
|
that gets the type of the current tag and returns it as a reflect.Type, which
|
||||||
|
necessitates recursion and peeking at OTAs and their elements.
|
||||||
|
|
||||||
|
We could try to do the same thing in the generated decoder, comparing the
|
||||||
|
determined type against the expected type to try to figure out whether we should
|
||||||
|
decode an array or a table, etc. This is immediately problematic as it requires
|
||||||
|
memory to be allocated, both for the peek buffer and the resulting tree of type
|
||||||
|
information. If we end up with some crazy way to keep track of the types, that's
|
||||||
|
only one half of the allocation problem and we would still be spending extra
|
||||||
|
cycles going over all of that twice.
|
||||||
|
|
||||||
|
## Performance constraints
|
||||||
|
|
||||||
|
The generated decoder is supposed to blaze through data, and it can't do that if
|
||||||
|
it does all the singing and dancing that the dynamic decoder does. It's time for
|
||||||
|
some performance constraints:
|
||||||
|
|
||||||
|
- No allocations, except as required to build the destination for the data
|
||||||
|
- No redundant work
|
||||||
|
- So, no freaking peeking
|
||||||
|
- It should take well under 500 lines of generated code to decode one message of
|
||||||
|
reasonable size (i.e. be careful not to bloat the binary)
|
||||||
|
|
||||||
|
I'm not really going to do my usual thing here of making a slow version and
|
||||||
|
speeding it up over time based on evidence and experimentation because these
|
||||||
|
constraints inform the design so much it would be impossible to continue without
|
||||||
|
them. I am 99% confident that these constraints will allow for an acceptable
|
||||||
|
baseline of performance (for generated code) and we can still profile and
|
||||||
|
micro-optimize later. This is good enough for me.
|
||||||
|
Heavy solution
|
||||||
|
|
||||||
|
There is a solution that might work very well which involves completely redoing
|
||||||
|
the generated decoding code. We could create a function for every source type to
|
||||||
|
destination type mapping that exists in protocol, and then compose them all
|
||||||
|
together. The decoding methods for each message or type would be wrappers around
|
||||||
|
the correct function for their root TAPE -> Go type mapping. The main benefit of
|
||||||
|
this is it would make this problem a lot more manageable because the interface
|
||||||
|
points between the data would be represented by function boundaries. This would
|
||||||
|
allow the use of return and defer statements, and would allow more code sharing,
|
||||||
|
producing a smaller binary. Go would probably inline these where needed.
|
||||||
|
|
||||||
|
Would this work? Probably. More investigation is required to make sure. I want
|
||||||
|
to stop re-writing things I don't need to. On the other hand, it is just the
|
||||||
|
decoder.
|
||||||
|
|
||||||
|
## Light solution
|
||||||
|
|
||||||
|
TODO: find a solution that satisfies the performance constraints, keeps the same
|
||||||
|
identical interface, and works off the same code. I am convinced this is doable,
|
||||||
|
and it might even allow us to extract more data from an unexpected structure.
|
||||||
|
However, continuing this way might introduce unmanageable complexity. It is
|
||||||
|
already a little unmanageable and I am just one pony (kind of).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Heavy solution is going to work here, applied to only the points of
|
||||||
|
`Generator.generateDecodeValue` where it decodes an aggregate data structure.
|
||||||
|
That way, only minimal amounts of code need to be redone.
|
||||||
|
|
||||||
|
Whenever a branch needs to happen, a call shall be generated, a deferred
|
||||||
|
implementation request shall be added to a special FIFO queue within the
|
||||||
|
generator. After generating data structures and their root decoding functions,
|
||||||
|
the generator shall pick away at this queue until no requests remain. The
|
||||||
|
generator shall accept new items during this process, so that recursion is
|
||||||
|
possible. This is all to ensure it is only ever writing one function at a time
|
||||||
|
|
||||||
|
The functions shall take a pointer to a type that accepts any type like (~) the
|
||||||
|
destination's base type. We should also probably just call
|
||||||
|
`Generator.generateDecodeValue` directly on user defined types this way, keeping
|
||||||
|
their public `Decode` methods just for convenience.
|
||||||
|
|
||||||
|
The tape package shall contain a skimming function that takes a decoder and a
|
||||||
|
tag, and recursively consumes the decoder given the context of the tag. This
|
||||||
|
shall be utilized by the decoder functions to skip over values if their tags
|
||||||
|
or keys do not match up with what is expected.
|
||||||
@@ -56,20 +56,27 @@ static section.
|
|||||||
|
|
||||||
For each defined type, the compiler shall generate a Go type with the same name
|
For each defined type, the compiler shall generate a Go type with the same name
|
||||||
as written in its definition. The Go type shall be encodable, and shall have
|
as written in its definition. The Go type shall be encodable, and shall have
|
||||||
`Encode` and `Decode` methods as described below.
|
`EncodeValue`, `DecodeValue`, and `Tag` methods as described below.
|
||||||
|
|
||||||
## Encoding and Decoding Methods
|
## Encoding and Decoding Methods
|
||||||
|
|
||||||
Each encodable type shall be given an `Encode` method and a `Decode` method,
|
Each message shall be given an `Encode` method and a `Decode` method,
|
||||||
which will take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
|
which shall take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
|
||||||
return an `(int, error)` pair describing the amount of bytes written and an
|
return an `(int, error)` pair describing the amount of bytes written and an
|
||||||
error if the write stopped early. `Encode` will encode the data within the
|
error if the write stopped early. `Encode` shall encode the data within the
|
||||||
message to the given encoder, and `Decode` will decode data from the given
|
message to the given encoder, and `Decode` shall decode data from the given
|
||||||
decoder and place it in the type's value. The methods shall not retain or close
|
decoder and place it in the type's value. The methods shall not retain or close
|
||||||
any encoders or decoders they are given. Both methods shall have pointer
|
any encoders or decoders they are given. Both methods shall have pointer
|
||||||
receivers. In effect, these methods will satisfy `codec.Encodable` and
|
receivers. In effect, these methods shall satisfy `codec.Encodable` and
|
||||||
`codec.Decodable`.
|
`codec.Decodable`.
|
||||||
|
|
||||||
|
Each defined type shall be given an `EncodeValue` method and a `DecodeValue`
|
||||||
|
method, which shall both take in a `tape.Tag`, then a `codec.Encoder` and a
|
||||||
|
`codec.Decoder` respectively. These methods shall encode and decode the value
|
||||||
|
according to the CN given by the tag. The TN shall be ignored. The message shall
|
||||||
|
also have a method `Tag` that takes no arguments and returns the preferred tag
|
||||||
|
of the type including the TN and CN.
|
||||||
|
|
||||||
## Connection
|
## Connection
|
||||||
|
|
||||||
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which
|
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ PDL allows defining a protocol using HOPP and TAPE.
|
|||||||
| Syntax | TN | CN | Description
|
| Syntax | TN | CN | Description
|
||||||
| ---------- | ------- | -: | -----------
|
| ---------- | ------- | -: | -----------
|
||||||
| I5 | SI | |
|
| I5 | SI | |
|
||||||
| I8 | LI | 0 |
|
| I8 | LSI | 0 |
|
||||||
| I16 | LI | 1 |
|
| I16 | LSI | 1 |
|
||||||
| I32 | LI | 3 |
|
| I32 | LSI | 3 |
|
||||||
| I64 | LI | 7 |
|
| I64 | LSI | 7 |
|
||||||
| I128[^2] | LI | 15 |
|
| I128[^2] | LSI | 15 |
|
||||||
| I256[^2] | LI | 31 |
|
| I256[^2] | LSI | 31 |
|
||||||
| U5 | SI | |
|
| U5 | SI | |
|
||||||
| U8 | LI | 0 |
|
| U8 | LI | 0 |
|
||||||
| U16 | LI | 1 |
|
| U16 | LI | 1 |
|
||||||
|
|||||||
1
error.go
1
error.go
@@ -9,6 +9,7 @@ type Error string; const (
|
|||||||
ErrIntegerOverflow Error = "integer overflow"
|
ErrIntegerOverflow Error = "integer overflow"
|
||||||
ErrMessageMalformed Error = "message is malformed"
|
ErrMessageMalformed Error = "message is malformed"
|
||||||
ErrTablePairMissing Error = "required table pair is missing"
|
ErrTablePairMissing Error = "required table pair is missing"
|
||||||
|
ErrWrongBufferLength Error = "wrong buffer length"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
|
|||||||
1183
generate/generate.go
Normal file
1183
generate/generate.go
Normal file
File diff suppressed because it is too large
Load Diff
381
generate/generate_test.go
Normal file
381
generate/generate_test.go
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
// import "fmt"
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TODO: once everything has been ironed out, test that the public API of the
|
||||||
|
// generator is equal to something specific
|
||||||
|
|
||||||
|
var exampleProtocol = defaultProtocol()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exampleProtocol.Messages[0x0000] = Message {
|
||||||
|
Name: "Connect",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exampleProtocol.Messages[0x0001] = Message {
|
||||||
|
Name: "UserList",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exampleProtocol.Messages[0x0002] = Message {
|
||||||
|
Name: "Pulse",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Index", Type: TypeInt { Bits: 5 } },
|
||||||
|
0x0001: Field { Name: "Offset", Type: TypeInt { Bits: 16, Signed: true }},
|
||||||
|
0x0002: Field { Name: "X", Type: TypeFloat { Bits: 16 }},
|
||||||
|
0x0003: Field { Name: "Y", Type: TypeFloat { Bits: 32 }},
|
||||||
|
0x0004: Field { Name: "Z", Type: TypeFloat { Bits: 64 }},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exampleProtocol.Messages[0x0003] = Message {
|
||||||
|
Name: "NestedArray",
|
||||||
|
Type: TypeArray { Element: TypeArray { Element: TypeInt { Bits: 8 } } },
|
||||||
|
}
|
||||||
|
exampleProtocol.Messages[0x0004] = Message {
|
||||||
|
Name: "Integers",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "U5", Type: TypeInt { Bits: 5 } },
|
||||||
|
0x0001: Field { Name: "U8", Type: TypeInt { Bits: 8 } },
|
||||||
|
0x0002: Field { Name: "U16", Type: TypeInt { Bits: 16 } },
|
||||||
|
0x0003: Field { Name: "U32", Type: TypeInt { Bits: 32 } },
|
||||||
|
0x0004: Field { Name: "U64", Type: TypeInt { Bits: 64 } },
|
||||||
|
0x0006: Field { Name: "I8", Type: TypeInt { Bits: 8, Signed: true } },
|
||||||
|
0x0007: Field { Name: "I16", Type: TypeInt { Bits: 16, Signed: true } },
|
||||||
|
0x0008: Field { Name: "I32", Type: TypeInt { Bits: 32, Signed: true } },
|
||||||
|
0x0009: Field { Name: "I64", Type: TypeInt { Bits: 64, Signed: true } },
|
||||||
|
0x000B: Field { Name: "NI8", Type: TypeInt { Bits: 8, Signed: true } },
|
||||||
|
0x000C: Field { Name: "NI16",Type: TypeInt { Bits: 16, Signed: true } },
|
||||||
|
0x000D: Field { Name: "NI32",Type: TypeInt { Bits: 32, Signed: true } },
|
||||||
|
0x000E: Field { Name: "NI64",Type: TypeInt { Bits: 64, Signed: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
exampleProtocol.Types["User"] = TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateRunEncodeDecode(test *testing.T) {
|
||||||
|
testGenerateRun(test, &exampleProtocol, "encode-decode", `
|
||||||
|
// imports
|
||||||
|
`, `
|
||||||
|
log.Println("MessageConnect")
|
||||||
|
messageConnect := MessageConnect {
|
||||||
|
Name: "rarity",
|
||||||
|
Password: "gems",
|
||||||
|
}
|
||||||
|
testEncodeDecode(
|
||||||
|
&messageConnect,
|
||||||
|
tu.S(0xE1, 0x02).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||||
|
[]byte { 0x00, 0x01, 0x84, 'g', 'e', 'm', 's' },
|
||||||
|
))
|
||||||
|
log.Println("MessageUserList")
|
||||||
|
messageUserList := MessageUserList {
|
||||||
|
Users: []User {
|
||||||
|
User {
|
||||||
|
Name: "rarity",
|
||||||
|
Bio: "asdjads",
|
||||||
|
Followers: 0x324,
|
||||||
|
},
|
||||||
|
User {
|
||||||
|
Name: "deez nuts",
|
||||||
|
Bio: "logy",
|
||||||
|
Followers: 0x8000,
|
||||||
|
},
|
||||||
|
User {
|
||||||
|
Name: "creekflow",
|
||||||
|
Bio: "im creekflow",
|
||||||
|
Followers: 0x3894,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testEncodeDecode(
|
||||||
|
&messageUserList,
|
||||||
|
tu.S(0xE1, 0x01, 0x00, 0x00,
|
||||||
|
0xC1, 0x03, 0xE1,
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||||
|
[]byte { 0x00, 0x01, 0x87, 'a', 's', 'd', 'j', 'a', 'd', 's' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x03, 0x24 },
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x89, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' },
|
||||||
|
[]byte { 0x00, 0x01, 0x84, 'l', 'o', 'g', 'y' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x80, 0x00 },
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x89, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' },
|
||||||
|
[]byte { 0x00, 0x01, 0x8C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f',
|
||||||
|
'l', 'o', 'w' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x38, 0x94 },
|
||||||
|
))
|
||||||
|
log.Println("MessagePulse")
|
||||||
|
messagePulse := MessagePulse {
|
||||||
|
Index: 9,
|
||||||
|
Offset: -0x3521,
|
||||||
|
X: 45.375,
|
||||||
|
Y: 294.1,
|
||||||
|
Z: 384729384.234892034,
|
||||||
|
}
|
||||||
|
testEncodeDecode(
|
||||||
|
&messagePulse,
|
||||||
|
tu.S(0xE1, 0x05).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x09 },
|
||||||
|
[]byte { 0x00, 0x01, 0x41, 0xCA, 0xDF },
|
||||||
|
[]byte { 0x00, 0x02, 0x61, 0x51, 0xAC },
|
||||||
|
[]byte { 0x00, 0x03, 0x63, 0x43, 0x93, 0x0C, 0xCD },
|
||||||
|
[]byte { 0x00, 0x04, 0x67, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 },
|
||||||
|
))
|
||||||
|
log.Println("MessageNestedArray")
|
||||||
|
uint8s := func(n int) []uint8 {
|
||||||
|
array := make([]uint8, n)
|
||||||
|
for index := range array {
|
||||||
|
array[index] = uint8(index + 1) | 0xF0
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
messageNestedArray := MessageNestedArray {
|
||||||
|
uint8s(6),
|
||||||
|
uint8s(35),
|
||||||
|
}
|
||||||
|
testEncodeDecode(
|
||||||
|
&messageNestedArray,
|
||||||
|
tu.S(0xC1, 0x02, 0xC1,
|
||||||
|
0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||||
|
35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||||
|
0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
|
||||||
|
0xFD, 0xFE, 0xFF, 0xF0, 0xF1, 0xF2,
|
||||||
|
0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||||
|
0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE,
|
||||||
|
0xFF, 0xF0, 0xF1, 0xF2, 0xF3))
|
||||||
|
log.Println("MessageIntegers")
|
||||||
|
messageIntegers := MessageIntegers {
|
||||||
|
U5: 0x13,
|
||||||
|
U8: 0xC9,
|
||||||
|
U16: 0x34C9,
|
||||||
|
U32: 0x10E134C9,
|
||||||
|
U64: 0x639109BC10E134C9,
|
||||||
|
I8: 0x35,
|
||||||
|
I16: 0x34C9,
|
||||||
|
I32: 0x10E134C9,
|
||||||
|
I64: 0x639109BC10E134C9,
|
||||||
|
NI8: -0x35,
|
||||||
|
NI16: -0x34C9,
|
||||||
|
NI32: -0x10E134C9,
|
||||||
|
NI64: -0x639109BC10E134C9,
|
||||||
|
}
|
||||||
|
testEncodeDecode(
|
||||||
|
&messageIntegers,
|
||||||
|
tu.S(0xE1, 13).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x13 },
|
||||||
|
[]byte { 0x00, 0x01, 0x20, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x02, 0x21, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x03, 0x23, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x04, 0x27, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x06, 0x40, 0x35 },
|
||||||
|
[]byte { 0x00, 0x07, 0x41, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x08, 0x43, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x09, 0x47, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x0B, 0x40, 0xCB },
|
||||||
|
[]byte { 0x00, 0x0C, 0x41, 0xCB, 0x37 },
|
||||||
|
[]byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||||
|
[]byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||||
|
))
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateRunDecodeWrongType(test *testing.T) {
|
||||||
|
protocol := defaultProtocol()
|
||||||
|
protocol.Messages[0x0000] = Message {
|
||||||
|
Name: "Uint5",
|
||||||
|
Type: TypeInt { Bits: 5 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0001] = Message {
|
||||||
|
Name: "Uint8",
|
||||||
|
Type: TypeInt { Bits: 8 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0002] = Message {
|
||||||
|
Name: "Uint16",
|
||||||
|
Type: TypeInt { Bits: 16 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0003] = Message {
|
||||||
|
Name: "Uint32",
|
||||||
|
Type: TypeInt { Bits: 32 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0004] = Message {
|
||||||
|
Name: "Uint64",
|
||||||
|
Type: TypeInt { Bits: 64 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0005] = Message {
|
||||||
|
Name: "Int8",
|
||||||
|
Type: TypeInt { Bits: 8 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0006] = Message {
|
||||||
|
Name: "Int16",
|
||||||
|
Type: TypeInt { Bits: 16 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0007] = Message {
|
||||||
|
Name: "Int32",
|
||||||
|
Type: TypeInt { Bits: 32 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0008] = Message {
|
||||||
|
Name: "Int64",
|
||||||
|
Type: TypeInt { Bits: 64 },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0009] = Message {
|
||||||
|
Name: "String",
|
||||||
|
Type: TypeString { },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x000A] = Message {
|
||||||
|
Name: "Buffer",
|
||||||
|
Type: TypeBuffer { },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x000B] = Message {
|
||||||
|
Name: "StringArray",
|
||||||
|
Type: TypeArray { Element: TypeString { } },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x000C] = Message {
|
||||||
|
Name: "Table",
|
||||||
|
Type: TypeTable { },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x000D] = Message {
|
||||||
|
Name: "TableDefined",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testGenerateRun(test, &protocol, "decode-wrong-type", `
|
||||||
|
// imports
|
||||||
|
`, `
|
||||||
|
datas := [][]byte {
|
||||||
|
/* int8 */ []byte { byte(tape.LSI.WithCN(0)), 0x45 },
|
||||||
|
/* int16 */ []byte { byte(tape.LSI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* int32 */ []byte { byte(tape.LSI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* int64 */ []byte { byte(tape.LSI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* uint5 */ []byte { byte(tape.SI.WithCN(12)) },
|
||||||
|
/* uint8 */ []byte { byte(tape.LI.WithCN(0)), 0x45 },
|
||||||
|
/* uint16 */ []byte { byte(tape.LI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* uint32 */ []byte { byte(tape.LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* uint64 */ []byte { byte(tape.LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* string */ []byte { byte(tape.SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' },
|
||||||
|
/* []byte */ []byte { byte(tape.SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' },
|
||||||
|
/* []string */ []byte {
|
||||||
|
byte(tape.OTA.WithCN(0)), 2, byte(tape.LBA.WithCN(0)),
|
||||||
|
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
||||||
|
0x05, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||||
|
},
|
||||||
|
/* map[uint16] any */ []byte {
|
||||||
|
byte(tape.KTV.WithCN(0)), 2,
|
||||||
|
0x02, 0x23, byte(tape.LSI.WithCN(1)), 0x45, 0x67,
|
||||||
|
0x02, 0x23, byte(tape.LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for index, data := range datas {
|
||||||
|
log.Printf("data %2d %v [%s]", index, tape.Tag(data[0]), tu.HexBytes(data[1:]))
|
||||||
|
// integers should only assign to other integers
|
||||||
|
if index > 8 {
|
||||||
|
cas := func(destination Message) {
|
||||||
|
n, err := destination.Decode(tape.NewDecoder(bytes.NewBuffer(data)))
|
||||||
|
if err != nil { log.Fatalf("error: %v | n: %d", err, n) }
|
||||||
|
reflectValue := reflect.ValueOf(destination).Elem()
|
||||||
|
if reflectValue.CanInt() {
|
||||||
|
if reflectValue.Int() != 0 {
|
||||||
|
log.Fatalf(
|
||||||
|
"destination not zero: %v",
|
||||||
|
reflectValue.Elem().Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if reflectValue.Uint() != 0 {
|
||||||
|
log.Fatalf(
|
||||||
|
"destination not zero: %v",
|
||||||
|
reflectValue.Elem().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
log.Fatalf("n not equal: %d != %d", n, len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("- MessageInt8")
|
||||||
|
{ var dest MessageInt8; cas(&dest) }
|
||||||
|
log.Println("- MessageInt16")
|
||||||
|
{ var dest MessageInt16; cas(&dest) }
|
||||||
|
log.Println("- MessageInt32")
|
||||||
|
{ var dest MessageInt32; cas(&dest) }
|
||||||
|
log.Println("- MessageInt64")
|
||||||
|
{ var dest MessageInt64; cas(&dest) }
|
||||||
|
log.Println("- MessageUint8")
|
||||||
|
{ var dest MessageUint8; cas(&dest) }
|
||||||
|
log.Println("- MessageUint16")
|
||||||
|
{ var dest MessageUint16; cas(&dest) }
|
||||||
|
log.Println("- MessageUint32")
|
||||||
|
{ var dest MessageUint32; cas(&dest) }
|
||||||
|
log.Println("- MessageUint64")
|
||||||
|
{ var dest MessageUint64; cas(&dest) }
|
||||||
|
}
|
||||||
|
arrayCase := func(destination Message) {
|
||||||
|
n, err := destination.Decode(tape.NewDecoder(bytes.NewBuffer(data)),)
|
||||||
|
if err != nil { log.Fatalf("error: %v | n: %d", err, n) }
|
||||||
|
reflectDestination := reflect.ValueOf(destination)
|
||||||
|
reflectValue := reflectDestination.Elem()
|
||||||
|
if reflectValue.Len() != 0 {
|
||||||
|
log.Fatalf("len(destination) not zero: %v", reflectValue.Interface())
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
log.Fatalf("n not equal: %d != %d", n, len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyCase := func(destination Message) {
|
||||||
|
n, err := destination.Decode(tape.NewDecoder(bytes.NewBuffer(data)),)
|
||||||
|
if err != nil { log.Fatalf("error: %v | n: %d", err, n) }
|
||||||
|
reflectDestination := reflect.ValueOf(destination)
|
||||||
|
reflectValue := reflectDestination.Elem()
|
||||||
|
if reflectValue == reflect.Zero(reflectValue.Type()) {
|
||||||
|
log.Fatalf("len(destination) not zero: %v", reflectValue.Interface())
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
log.Fatalf("n not equal: %d != %d", n, len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SBA/LBA types should only assign to other SBA/LBA types
|
||||||
|
if index != 9 && index != 10 {
|
||||||
|
log.Println("- MessageString")
|
||||||
|
{ var dest MessageString; arrayCase(&dest) }
|
||||||
|
log.Println("- MessageBuffer")
|
||||||
|
{ var dest MessageBuffer; arrayCase(&dest) }
|
||||||
|
}
|
||||||
|
// arrays should only assign to other arrays
|
||||||
|
if index != 11 {
|
||||||
|
log.Println("- MessageStringArray")
|
||||||
|
{ var dest MessageStringArray; arrayCase(&dest) }
|
||||||
|
}
|
||||||
|
// tables should only assign to other tables
|
||||||
|
if index != 12 {
|
||||||
|
log.Println("- MessageTable")
|
||||||
|
{ var dest = make(MessageTable); arrayCase(&dest) }
|
||||||
|
log.Println("- MessageTableDefined")
|
||||||
|
{ var dest MessageTableDefined; anyCase(&dest) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
131
generate/misc_test.go
Normal file
131
generate/misc_test.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
import "fmt"
|
||||||
|
import "os/exec"
|
||||||
|
import "testing"
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCase string) {
|
||||||
|
// reset data directory
|
||||||
|
dir := filepath.Join("test", title)
|
||||||
|
err := os.RemoveAll(dir)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
err = os.MkdirAll(dir, 0750)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
|
||||||
|
// open files
|
||||||
|
sourceFile, err := os.Create(filepath.Join(dir, "protocol.go"))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
defer sourceFile.Close()
|
||||||
|
mainFile, err := os.Create(filepath.Join(dir, "main.go"))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
defer mainFile.Close()
|
||||||
|
|
||||||
|
// generate protocol
|
||||||
|
generator := Generator {
|
||||||
|
Output: sourceFile,
|
||||||
|
PackageName: "main",
|
||||||
|
}
|
||||||
|
_, err = generator.Generate(protocol)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
|
||||||
|
// build static source files
|
||||||
|
imports = `
|
||||||
|
import "log"
|
||||||
|
import "bytes"
|
||||||
|
import "reflect"
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
|
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||||
|
` + imports
|
||||||
|
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
|
||||||
|
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
|
||||||
|
static := `
|
||||||
|
func testEncode(message Message, correct tu.Snake) {
|
||||||
|
buffer := bytes.Buffer { }
|
||||||
|
encoder := tape.NewEncoder(&buffer)
|
||||||
|
n, err := message.Encode(encoder)
|
||||||
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
encoder.Flush()
|
||||||
|
got := buffer.Bytes()
|
||||||
|
log.Printf("got: [%s]", tu.HexBytes(got))
|
||||||
|
log.Println("correct:", correct)
|
||||||
|
if n != len(got) {
|
||||||
|
log.Fatalf("n incorrect: %d != %d\n", n, len(got))
|
||||||
|
}
|
||||||
|
if ok, n := correct.Check(got); !ok {
|
||||||
|
log.Fatalln("not equal at", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecode(correct Message, data any) {
|
||||||
|
var flat []byte
|
||||||
|
switch data := data.(type) {
|
||||||
|
case []byte: flat = data
|
||||||
|
case tu.Snake: flat = data.Flatten()
|
||||||
|
}
|
||||||
|
message := reflect.New(reflect.ValueOf(correct).Elem().Type()).Interface().(Message)
|
||||||
|
log.Println("before: ", message)
|
||||||
|
decoder := tape.NewDecoder(bytes.NewBuffer(flat))
|
||||||
|
n, err := message.Decode(decoder)
|
||||||
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
log.Println("got: ", message)
|
||||||
|
log.Println("correct:", correct)
|
||||||
|
if n != len(flat) {
|
||||||
|
log.Fatalf("n incorrect: %d != %d\n", n, len(flat))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(message, correct) {
|
||||||
|
log.Fatalln("not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: possibly combine the two above functions into this one,
|
||||||
|
// also take a data parameter here (snake)
|
||||||
|
func testEncodeDecode(message Message, data tu.Snake) {buffer := bytes.Buffer { }
|
||||||
|
log.Println("encoding:")
|
||||||
|
encoder := tape.NewEncoder(&buffer)
|
||||||
|
n, err := message.Encode(encoder)
|
||||||
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
encoder.Flush()
|
||||||
|
got := buffer.Bytes()
|
||||||
|
log.Printf("got: [%s]", tu.HexBytes(got))
|
||||||
|
log.Println("correct:", data)
|
||||||
|
if n != len(got) {
|
||||||
|
log.Fatalf("n incorrect: %d != %d\n", n, len(got))
|
||||||
|
}
|
||||||
|
if ok, n := data.Check(got); !ok {
|
||||||
|
log.Fatalln("not equal at", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("decoding:")
|
||||||
|
destination := reflect.New(reflect.ValueOf(message).Elem().Type()).Interface().(Message)
|
||||||
|
flat := data.Flatten()
|
||||||
|
log.Println("before: ", destination)
|
||||||
|
decoder := tape.NewDecoder(bytes.NewBuffer(flat))
|
||||||
|
n, err = destination.Decode(decoder)
|
||||||
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
log.Println("got: ", destination)
|
||||||
|
log.Println("correct:", message)
|
||||||
|
if n != len(flat) {
|
||||||
|
log.Fatalf("n incorrect: %d != %d\n", n, len(flat))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(destination, message) {
|
||||||
|
log.Fatalln("not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`
|
||||||
|
fmt.Fprintf(
|
||||||
|
mainFile, "package main\n%s\nfunc main() {\n%s\n%s\n%s\n}\n%s",
|
||||||
|
imports, setup, testCase, teardown, static)
|
||||||
|
|
||||||
|
// build and run test
|
||||||
|
command := exec.Command("go", "run", "./" + filepath.Join("generate", dir))
|
||||||
|
workingDirAbs, err := filepath.Abs("..")
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
command.Dir = workingDirAbs
|
||||||
|
command.Env = os.Environ()
|
||||||
|
output, err := command.CombinedOutput()
|
||||||
|
test.Logf("output of %v:\n%s", command, output)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
}
|
||||||
@@ -21,33 +21,12 @@ func Parse(lx parse.Lexer) (*Protocol, error) {
|
|||||||
func defaultProtocol() Protocol {
|
func defaultProtocol() Protocol {
|
||||||
return Protocol {
|
return Protocol {
|
||||||
Messages: make(map[uint16] Message),
|
Messages: make(map[uint16] Message),
|
||||||
Types: map[string] Type {
|
Types: map[string] Type { },
|
||||||
"U8": TypeInt { Bits: 8 },
|
|
||||||
"U16": TypeInt { Bits: 16 },
|
|
||||||
"U32": TypeInt { Bits: 32 },
|
|
||||||
"U64": TypeInt { Bits: 64 },
|
|
||||||
"U128": TypeInt { Bits: 128 },
|
|
||||||
"U256": TypeInt { Bits: 256 },
|
|
||||||
"I8": TypeInt { Bits: 8, Signed: true },
|
|
||||||
"I16": TypeInt { Bits: 16, Signed: true },
|
|
||||||
"I32": TypeInt { Bits: 32, Signed: true },
|
|
||||||
"I64": TypeInt { Bits: 64, Signed: true },
|
|
||||||
"I128": TypeInt { Bits: 128, Signed: true },
|
|
||||||
"I256": TypeInt { Bits: 256, Signed: true },
|
|
||||||
"F16": TypeFloat { Bits: 16 },
|
|
||||||
"F32": TypeFloat { Bits: 32 },
|
|
||||||
"F64": TypeFloat { Bits: 64 },
|
|
||||||
"F128": TypeFloat { Bits: 128 },
|
|
||||||
"F256": TypeFloat { Bits: 256 },
|
|
||||||
"String": TypeString { },
|
|
||||||
"Buffer": TypeBuffer { },
|
|
||||||
"Table": TypeTable { },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseReader(reader io.Reader) (*Protocol, error) {
|
func ParseReader(fileName string, reader io.Reader) (*Protocol, error) {
|
||||||
lx, err := Lex("test.pdl", reader)
|
lx, err := Lex(fileName, reader)
|
||||||
if err != nil { return nil, err }
|
if err != nil { return nil, err }
|
||||||
return Parse(lx)
|
return Parse(lx)
|
||||||
}
|
}
|
||||||
@@ -116,6 +95,28 @@ func (this *parser) parseType() (Type, error) {
|
|||||||
|
|
||||||
switch this.Kind() {
|
switch this.Kind() {
|
||||||
case TokenIdent:
|
case TokenIdent:
|
||||||
|
switch this.Value() {
|
||||||
|
case "U8": return TypeInt { Bits: 8 }, this.Next()
|
||||||
|
case "U16": return TypeInt { Bits: 16 }, this.Next()
|
||||||
|
case "U32": return TypeInt { Bits: 32 }, this.Next()
|
||||||
|
case "U64": return TypeInt { Bits: 64 }, this.Next()
|
||||||
|
case "U128": return TypeInt { Bits: 128 }, this.Next()
|
||||||
|
case "U256": return TypeInt { Bits: 256 }, this.Next()
|
||||||
|
case "I8": return TypeInt { Bits: 8, Signed: true }, this.Next()
|
||||||
|
case "I16": return TypeInt { Bits: 16, Signed: true }, this.Next()
|
||||||
|
case "I32": return TypeInt { Bits: 32, Signed: true }, this.Next()
|
||||||
|
case "I64": return TypeInt { Bits: 64, Signed: true }, this.Next()
|
||||||
|
case "I128": return TypeInt { Bits: 128, Signed: true }, this.Next()
|
||||||
|
case "I256": return TypeInt { Bits: 256, Signed: true }, this.Next()
|
||||||
|
case "F16": return TypeFloat { Bits: 16 }, this.Next()
|
||||||
|
case "F32": return TypeFloat { Bits: 32 }, this.Next()
|
||||||
|
case "F64": return TypeFloat { Bits: 64 }, this.Next()
|
||||||
|
case "F128": return TypeFloat { Bits: 128 }, this.Next()
|
||||||
|
case "F256": return TypeFloat { Bits: 256 }, this.Next()
|
||||||
|
case "String": return TypeString { }, this.Next()
|
||||||
|
case "Buffer": return TypeBuffer { }, this.Next()
|
||||||
|
case "Table": return TypeTable { }, this.Next()
|
||||||
|
}
|
||||||
return this.parseTypeNamed()
|
return this.parseTypeNamed()
|
||||||
case TokenLBracket:
|
case TokenLBracket:
|
||||||
return this.parseTypeArray()
|
return this.parseTypeArray()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package generate
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
import "strings"
|
import "strings"
|
||||||
import "testing"
|
import "testing"
|
||||||
// import "reflect"
|
|
||||||
import "git.tebibyte.media/sashakoshka/goparse"
|
import "git.tebibyte.media/sashakoshka/goparse"
|
||||||
|
|
||||||
func TestParse(test *testing.T) {
|
func TestParse(test *testing.T) {
|
||||||
@@ -12,8 +11,8 @@ func TestParse(test *testing.T) {
|
|||||||
Name: "Connect",
|
Name: "Connect",
|
||||||
Type: TypeTableDefined {
|
Type: TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -27,14 +26,14 @@ func TestParse(test *testing.T) {
|
|||||||
}
|
}
|
||||||
correct.Types["User"] = TypeTableDefined {
|
correct.Types["User"] = TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
test.Log("CORRECT:", &correct)
|
test.Log("CORRECT:", &correct)
|
||||||
|
|
||||||
got, err := ParseReader(strings.NewReader(`
|
got, err := ParseReader("test.pdl", strings.NewReader(`
|
||||||
M0000 Connect {
|
M0000 Connect {
|
||||||
0000 Name String,
|
0000 Name String,
|
||||||
0001 Password String,
|
0001 Password String,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "maps"
|
||||||
|
import "slices"
|
||||||
|
import "crypto/md5"
|
||||||
|
|
||||||
type Protocol struct {
|
type Protocol struct {
|
||||||
Messages map[uint16] Message
|
Messages map[uint16] Message
|
||||||
Types map[string] Type
|
Types map[string] Type
|
||||||
@@ -11,7 +16,7 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Type interface {
|
type Type interface {
|
||||||
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
type TypeInt struct {
|
type TypeInt struct {
|
||||||
@@ -19,29 +24,84 @@ type TypeInt struct {
|
|||||||
Signed bool
|
Signed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeInt) String() string {
|
||||||
|
output := ""
|
||||||
|
if typ.Signed {
|
||||||
|
output += "I"
|
||||||
|
} else {
|
||||||
|
output += "U"
|
||||||
|
}
|
||||||
|
output += fmt.Sprint(typ.Bits)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
type TypeFloat struct {
|
type TypeFloat struct {
|
||||||
Bits int
|
Bits int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeFloat) String() string {
|
||||||
|
return fmt.Sprintf("F%d", typ.Bits)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeString struct { }
|
type TypeString struct { }
|
||||||
|
|
||||||
|
func (TypeString) String() string {
|
||||||
|
return "String"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeBuffer struct { }
|
type TypeBuffer struct { }
|
||||||
|
|
||||||
|
func (TypeBuffer) String() string {
|
||||||
|
return "Buffer"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeArray struct {
|
type TypeArray struct {
|
||||||
Element Type
|
Element Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeArray) String() string {
|
||||||
|
return fmt.Sprintf("[]%v", typ.Element)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeTable struct { }
|
type TypeTable struct { }
|
||||||
|
|
||||||
|
func (TypeTable) String() string {
|
||||||
|
return "Table"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeTableDefined struct {
|
type TypeTableDefined struct {
|
||||||
Fields map[uint16] Field
|
Fields map[uint16] Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeTableDefined) String() string {
|
||||||
|
output := "{"
|
||||||
|
for _, key := range slices.Sorted(maps.Keys(typ.Fields)) {
|
||||||
|
output += fmt.Sprintf("%04X %v", key, typ.Fields[key])
|
||||||
|
}
|
||||||
|
output += "}"
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
Name string
|
Name string
|
||||||
Type Type
|
Type Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (field Field) String() string {
|
||||||
|
return fmt.Sprintf("%s %v", field.Name, field.Type)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeNamed struct {
|
type TypeNamed struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeNamed) String() string {
|
||||||
|
return typ.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashType(typ Type) [16]byte {
|
||||||
|
// TODO: if we ever want to make the compiler more efficient, this would
|
||||||
|
// be a good place to start, complex string concatenation in a hot path
|
||||||
|
// (sorta)
|
||||||
|
return md5.Sum([]byte(typ.String()))
|
||||||
|
}
|
||||||
|
|||||||
173
internal/testutil/testutil.go
Normal file
173
internal/testutil/testutil.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "slices"
|
||||||
|
import "strings"
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
// Snake lets you compare blocks of data where the ordering of certain parts may
|
||||||
|
// be swapped every which way. It is designed for comparing the encoding of
|
||||||
|
// maps where the ordering of individual elements is inconsistent.
|
||||||
|
//
|
||||||
|
// The snake is divided into sectors, which hold a number of variations. For a
|
||||||
|
// sector to be satisfied by some data, some ordering of it must match the data
|
||||||
|
// exactly. for the snake to be satisfied by some data, its sectors must match
|
||||||
|
// the data in order, but the internal ordering of each sector doesn't matter.
|
||||||
|
type Snake [] [] []byte
|
||||||
|
// snake sector variation
|
||||||
|
|
||||||
|
// S returns a new snake.
|
||||||
|
func S(data ...byte) Snake {
|
||||||
|
return (Snake { }).Add(data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddVar returns a new snake with the given sector added on to it. Successive
|
||||||
|
// calls of this method can be chained together to create a big ass snake.
|
||||||
|
func (sn Snake) AddVar(sector ...[]byte) Snake {
|
||||||
|
slice := make(Snake, len(sn) + 1)
|
||||||
|
copy(slice, sn)
|
||||||
|
slice[len(slice) - 1] = sector
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add is like AddVar, but adds a sector with only one variation, which means it
|
||||||
|
// does not vary, hence why the method is called that.
|
||||||
|
func (sn Snake) Add(data ...byte) Snake {
|
||||||
|
return sn.AddVar(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check determines if the data satisfies the snake.
|
||||||
|
func (sn Snake) Check(data []byte) (ok bool, n int) {
|
||||||
|
left := data
|
||||||
|
variations := map[int] []byte { }
|
||||||
|
for _, sector := range sn {
|
||||||
|
clear(variations)
|
||||||
|
for key, variation := range sector {
|
||||||
|
variations[key] = variation
|
||||||
|
}
|
||||||
|
for len(variations) > 0 {
|
||||||
|
found := false
|
||||||
|
for key, variation := range variations {
|
||||||
|
if len(left) < len(variation) { continue }
|
||||||
|
if !slices.Equal(left[:len(variation)], variation) { continue }
|
||||||
|
n += len(variation)
|
||||||
|
left = data[n:]
|
||||||
|
delete(variations, key)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if !found { return false, n }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n < len(data) {
|
||||||
|
return false, n
|
||||||
|
}
|
||||||
|
return true, n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten returns the snake flattened to a byte array. The result of this
|
||||||
|
// function always satisfies the snake.
|
||||||
|
func (sn Snake) Flatten() []byte {
|
||||||
|
flat := []byte { }
|
||||||
|
for _, sector := range sn {
|
||||||
|
for _, variation := range sector {
|
||||||
|
flat = append(flat, variation...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn Snake) String() string {
|
||||||
|
if len(sn) == 0 || len(sn[0]) == 0 || len(sn[0][0]) == 0 {
|
||||||
|
return "EMPTY"
|
||||||
|
}
|
||||||
|
|
||||||
|
out := strings.Builder { }
|
||||||
|
for index, sector := range sn {
|
||||||
|
if index > 0 { out.WriteString(" : ") }
|
||||||
|
out.WriteRune('[')
|
||||||
|
for index, variation := range sector {
|
||||||
|
if index > 0 { out.WriteString(" / ") }
|
||||||
|
for _, byt := range variation {
|
||||||
|
fmt.Fprintf(&out, "%02x", byt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.WriteRune(']')
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HexBytes formats bytes into a hexadecimal string.
|
||||||
|
func HexBytes(data []byte) string {
|
||||||
|
if len(data) == 0 { return "EMPTY" }
|
||||||
|
out := strings.Builder { }
|
||||||
|
for _, byt := range data {
|
||||||
|
fmt.Fprintf(&out, "%02x", byt)
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe returns a string representing the type and data of the given value.
|
||||||
|
func Describe(value any) string {
|
||||||
|
desc := describer { }
|
||||||
|
desc.describe(reflect.ValueOf(value))
|
||||||
|
return desc.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type describer struct {
|
||||||
|
strings.Builder
|
||||||
|
indent int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *describer) describe(value reflect.Value) {
|
||||||
|
value = reflect.ValueOf(value.Interface())
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
this.printf("[\n")
|
||||||
|
this.indent += 1
|
||||||
|
for index := 0; index < value.Len(); index ++ {
|
||||||
|
this.iprintf("")
|
||||||
|
this.describe(value.Index(index))
|
||||||
|
this.iprintf("\n")
|
||||||
|
}
|
||||||
|
this.indent -= 1
|
||||||
|
this.iprintf("]")
|
||||||
|
case reflect.Struct:
|
||||||
|
this.printf("struct {\n")
|
||||||
|
this.indent += 1
|
||||||
|
typ := value.Type()
|
||||||
|
for index := range typ.NumField() {
|
||||||
|
indexBuffer := [1]int { index }
|
||||||
|
this.iprintf("%s: ", typ.Field(index).Name)
|
||||||
|
this.describe(value.FieldByIndex(indexBuffer[:]))
|
||||||
|
this.iprintf("\n")
|
||||||
|
}
|
||||||
|
this.indent -= 1
|
||||||
|
this.iprintf("}\n")
|
||||||
|
case reflect.Map:
|
||||||
|
this.printf("map {\n")
|
||||||
|
this.indent += 1
|
||||||
|
iter := value.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
this.iprintf("")
|
||||||
|
this.describe(iter.Key())
|
||||||
|
this.printf(": ")
|
||||||
|
this.describe(iter.Value())
|
||||||
|
this.iprintf("\n")
|
||||||
|
}
|
||||||
|
this.indent -= 1
|
||||||
|
this.iprintf("}\n")
|
||||||
|
case reflect.Pointer:
|
||||||
|
this.printf("& ")
|
||||||
|
this.describe(value.Elem())
|
||||||
|
default:
|
||||||
|
this.printf("<%v %v>", value.Type(), value.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *describer) printf(format string, v ...any) {
|
||||||
|
fmt.Fprintf(this, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *describer) iprintf(format string, v ...any) {
|
||||||
|
fmt.Fprintf(this, strings.Repeat("\t", this.indent) + format, v...)
|
||||||
|
}
|
||||||
66
internal/testutil/testutil_test.go
Normal file
66
internal/testutil/testutil_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSnakeA(test *testing.T) {
|
||||||
|
snake := S(1, 6).AddVar(
|
||||||
|
[]byte { 1 },
|
||||||
|
[]byte { 2 },
|
||||||
|
[]byte { 3 },
|
||||||
|
[]byte { 4 },
|
||||||
|
[]byte { 5 },
|
||||||
|
).Add(9)
|
||||||
|
|
||||||
|
test.Log(snake)
|
||||||
|
|
||||||
|
ok, n := snake.Check([]byte { 1, 6, 1, 2, 3, 4, 5, 9 })
|
||||||
|
if !ok { test.Fatal("false negative:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 5, 4, 3, 2, 1, 9 })
|
||||||
|
if !ok { test.Fatal("false negative:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 3, 1, 4, 2, 5, 9 })
|
||||||
|
if !ok { test.Fatal("false negative:", n) }
|
||||||
|
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 1, 2, 3, 4, 5, 6, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 0, 2, 3, 4, 5, 6, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 7, 1, 4, 2, 5, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 1, 2, 3, 4, 5, 9, 10})
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnakeB(test *testing.T) {
|
||||||
|
snake := S(1, 6).AddVar(
|
||||||
|
[]byte { 1 },
|
||||||
|
[]byte { 2 },
|
||||||
|
).Add(9).AddVar(
|
||||||
|
[]byte { 3, 2 },
|
||||||
|
[]byte { 0 },
|
||||||
|
[]byte { 1, 1, 2, 3 },
|
||||||
|
)
|
||||||
|
|
||||||
|
test.Log(snake)
|
||||||
|
|
||||||
|
ok, n := snake.Check([]byte { 1, 6, 1, 2, 9, 3, 2, 0, 1, 1, 2, 3})
|
||||||
|
if !ok { test.Fatal("false negative:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 2, 1, 9, 0, 1, 1, 2, 3, 3, 2})
|
||||||
|
if !ok { test.Fatal("false negative:", n) }
|
||||||
|
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 1, 2, 9 })
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 9, 3, 2, 0, 1, 1, 2, 3})
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 2, 9, 0, 1, 1, 2, 3, 3, 2})
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
ok, n = snake.Check([]byte { 1, 6, 1, 2, 9, 3, 2, 1, 1, 2, 3})
|
||||||
|
if ok { test.Fatal("false positive:", n) }
|
||||||
|
}
|
||||||
52
message.go
52
message.go
@@ -1,52 +0,0 @@
|
|||||||
package hopp
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
import "encoding"
|
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|
||||||
|
|
||||||
// Message is any object that can be sent or received over a HOPP connection.
|
|
||||||
type Message interface {
|
|
||||||
// Method returns the method number of the message. This must be unique
|
|
||||||
// within the protocol, and should not change between calls.
|
|
||||||
Method() uint16
|
|
||||||
encoding.BinaryMarshaler
|
|
||||||
encoding.BinaryUnmarshaler
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Message = new(MessageData)
|
|
||||||
|
|
||||||
// MessageData represents a message that organizes its data into table pairs. It
|
|
||||||
// can be used to alter a protocol at runtime, transmit data with arbitrary
|
|
||||||
// keys, etc. Bear in mind that is less performant than generating code because
|
|
||||||
// it has to make extra memory allocations and such.
|
|
||||||
type MessageData struct {
|
|
||||||
// Methd holds the method number. This should only be set once.
|
|
||||||
Methd uint16
|
|
||||||
// Pairs maps tags to values.
|
|
||||||
Pairs map[uint16] []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method returns the message's method field.
|
|
||||||
func (this *MessageData) Method() uint16 {
|
|
||||||
return this.Methd
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements the [encoding.BinaryMarshaler] interface. The
|
|
||||||
// message is encoded using TAPE (Table Pair Encoding).
|
|
||||||
func (this *MessageData) MarshalBinary() ([]byte, error) {
|
|
||||||
buffer, err := tape.EncodePairs(this.Pairs)
|
|
||||||
if err != nil { return nil, fmt.Errorf("marshaling MessageData: %w", err) }
|
|
||||||
return buffer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. The
|
|
||||||
// message is decoded using TAPE (Table Pair Encoding).
|
|
||||||
func (this *MessageData) UnmarshalBinary(buffer []byte) error {
|
|
||||||
this.Pairs = make(map[uint16] []byte)
|
|
||||||
pairs, err := tape.DecodePairs(buffer)
|
|
||||||
if err != nil { return fmt.Errorf("unmarshaling MessageData: %w", err) }
|
|
||||||
for key, value := range pairs {
|
|
||||||
this.Pairs[key] = value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
88
metadapta.go
88
metadapta.go
@@ -1,11 +1,12 @@
|
|||||||
package hopp
|
package hopp
|
||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
|
import "os"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "net"
|
import "net"
|
||||||
import "sync"
|
import "sync"
|
||||||
|
import "time"
|
||||||
import "sync/atomic"
|
import "sync/atomic"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|
||||||
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
||||||
|
|
||||||
// TODO investigate why 30 never reaches the server, causing it to wait for ever
|
// TODO investigate why 30 never reaches the server, causing it to wait for ever
|
||||||
@@ -109,6 +110,10 @@ func (this *a) AcceptTrans() (Trans, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *a) SetDeadline(t time.Time) error {
|
||||||
|
return this.underlying.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *a) SetSizeLimit(limit int64) {
|
func (this *a) SetSizeLimit(limit int64) {
|
||||||
this.sizeLimit = limit
|
this.sizeLimit = limit
|
||||||
}
|
}
|
||||||
@@ -213,6 +218,10 @@ type transA struct {
|
|||||||
currentWriter io.Closer
|
currentWriter io.Closer
|
||||||
writeBuffer []byte
|
writeBuffer []byte
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
|
closeErr error
|
||||||
|
|
||||||
|
deadline *time.Timer
|
||||||
|
deadlineLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *transA) Close() error {
|
func (this *transA) Close() error {
|
||||||
@@ -222,6 +231,11 @@ func (this *transA) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *transA) closeWithError(err error) error {
|
||||||
|
this.closeErr = err
|
||||||
|
return this.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (this *transA) closeDontUnlist() (err error) {
|
func (this *transA) closeDontUnlist() (err error) {
|
||||||
// MUST be goroutine safe
|
// MUST be goroutine safe
|
||||||
this.incoming.Close()
|
this.incoming.Close()
|
||||||
@@ -270,9 +284,9 @@ func (this *transA) Receive() (method uint16, data []byte, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *transA) ReceiveReader() (uint16, io.Reader, error) {
|
func (this *transA) ReceiveReader() (uint16, io.Reader, error) {
|
||||||
// if the transaction has been closed, return an io.EOF
|
// if the transaction has been closed, return an appropriate error.
|
||||||
if this.closed.Load() {
|
if err := this.errIfClosed(); err != nil {
|
||||||
return 0, nil, io.EOF
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// drain previous reader if necessary
|
// drain previous reader if necessary
|
||||||
@@ -290,6 +304,54 @@ func (this *transA) ReceiveReader() (uint16, io.Reader, error) {
|
|||||||
return method, reader, nil
|
return method, reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *transA) SetDeadline(t time.Time) error {
|
||||||
|
this.deadlineLock.Lock()
|
||||||
|
defer this.deadlineLock.Unlock()
|
||||||
|
|
||||||
|
if t == (time.Time { }) {
|
||||||
|
if this.deadline != nil {
|
||||||
|
this.deadline.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
until := time.Until(t)
|
||||||
|
if this.deadline == nil {
|
||||||
|
this.deadline.Reset(until)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
this.deadline = time.AfterFunc(until, func () {
|
||||||
|
this.closeWithError(os.ErrDeadlineExceeded)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// func (this *transA) SetReadDeadline(t time.Time) error {
|
||||||
|
// // TODO
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (this *transA) SetWriteDeadline(t time.Time) error {
|
||||||
|
// // TODO
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (this *transA) errIfClosed() error {
|
||||||
|
if !this.closed.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return this.bestErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *transA) bestErr() error {
|
||||||
|
if this.parent.err != nil {
|
||||||
|
return this.parent.err
|
||||||
|
}
|
||||||
|
if this.closeErr != nil {
|
||||||
|
return this.closeErr
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
type readerA struct {
|
type readerA struct {
|
||||||
parent *transA
|
parent *transA
|
||||||
leftover []byte
|
leftover []byte
|
||||||
@@ -320,11 +382,7 @@ func (this *readerA) pull() (uint16, error) {
|
|||||||
// close and return error on failure
|
// close and return error on failure
|
||||||
this.eof = true
|
this.eof = true
|
||||||
this.parent.Close()
|
this.parent.Close()
|
||||||
if this.parent.parent.err == nil {
|
return 0, fmt.Errorf("could not receive message: %w", this.parent.bestErr())
|
||||||
return 0, fmt.Errorf("could not receive message: %w", io.EOF)
|
|
||||||
} else {
|
|
||||||
return 0, this.parent.parent.err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *readerA) Read(buffer []byte) (int, error) {
|
func (this *readerA) Read(buffer []byte) (int, error) {
|
||||||
@@ -406,9 +464,9 @@ func encodeMessageA(
|
|||||||
return ErrPayloadTooLarge
|
return ErrPayloadTooLarge
|
||||||
}
|
}
|
||||||
buffer := make([]byte, 18 + len(data))
|
buffer := make([]byte, 18 + len(data))
|
||||||
tape.EncodeI64(buffer[:8], trans)
|
encodeI64(buffer[:8], trans)
|
||||||
tape.EncodeI16(buffer[8:10], method)
|
encodeI16(buffer[8:10], method)
|
||||||
tape.EncodeI64(buffer[10:18], uint64(len(data)))
|
encodeI64(buffer[10:18], uint64(len(data)))
|
||||||
copy(buffer[18:], data)
|
copy(buffer[18:], data)
|
||||||
_, err := writer.Write(buffer)
|
_, err := writer.Write(buffer)
|
||||||
return err
|
return err
|
||||||
@@ -427,11 +485,11 @@ func decodeMessageA(
|
|||||||
headerBuffer := [18]byte { }
|
headerBuffer := [18]byte { }
|
||||||
_, err = io.ReadFull(reader, headerBuffer[:])
|
_, err = io.ReadFull(reader, headerBuffer[:])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
transID, err = tape.DecodeI64[int64](headerBuffer[:8])
|
transID, err = decodeI64[int64](headerBuffer[:8])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
method, err = tape.DecodeI16[uint16](headerBuffer[8:10])
|
method, err = decodeI16[uint16](headerBuffer[8:10])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
size, err := tape.DecodeI64[uint64](headerBuffer[10:18])
|
size, err := decodeI64[uint64](headerBuffer[10:18])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
chunked, size = splitCCBSize(size)
|
chunked, size = splitCCBSize(size)
|
||||||
if size > uint64(sizeLimit) {
|
if size > uint64(sizeLimit) {
|
||||||
|
|||||||
22
metadaptb.go
22
metadaptb.go
@@ -2,10 +2,10 @@ package hopp
|
|||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
import "net"
|
import "net"
|
||||||
|
import "time"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
import "errors"
|
import "errors"
|
||||||
import "context"
|
import "context"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|
||||||
|
|
||||||
// B implements METADAPT-B over a multiplexed stream-oriented transport such as
|
// B implements METADAPT-B over a multiplexed stream-oriented transport such as
|
||||||
// QUIC.
|
// QUIC.
|
||||||
@@ -51,6 +51,10 @@ func (this *b) SetSizeLimit(limit int64) {
|
|||||||
this.sizeLimit = limit
|
this.sizeLimit = limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *b) SetDeadline(t time.Time) error {
|
||||||
|
return this.underlying.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *b) newTrans(underlying Stream) *transB {
|
func (this *b) newTrans(underlying Stream) *transB {
|
||||||
return &transB {
|
return &transB {
|
||||||
sizeLimit: this.sizeLimit,
|
sizeLimit: this.sizeLimit,
|
||||||
@@ -125,6 +129,10 @@ func (this *transB) receiveReader() (uint16, int64, io.Reader, error) {
|
|||||||
return method, size, data, nil
|
return method, size, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *transB) SetDeadline(t time.Time) error {
|
||||||
|
return this.underlying.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
type writerB struct {
|
type writerB struct {
|
||||||
parent *transB
|
parent *transB
|
||||||
buffer bytes.Buffer
|
buffer bytes.Buffer
|
||||||
@@ -150,12 +158,16 @@ type MultiConn interface {
|
|||||||
AcceptStream(context.Context) (Stream, error)
|
AcceptStream(context.Context) (Stream, error)
|
||||||
// OpenStream opens a new stream.
|
// OpenStream opens a new stream.
|
||||||
OpenStream() (Stream, error)
|
OpenStream() (Stream, error)
|
||||||
|
// See the documentation for [net.Conn.SetDeadline].
|
||||||
|
SetDeadline(time.Time) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream represents a single stream returned by a [MultiConn].
|
// Stream represents a single stream returned by a [MultiConn].
|
||||||
type Stream interface {
|
type Stream interface {
|
||||||
// See documentation for [net.Conn].
|
// See documentation for [net.Conn].
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
|
// See the documentation for [net.Conn.SetDeadline].
|
||||||
|
SetDeadline(time.Time) error
|
||||||
// ID returns the stream ID
|
// ID returns the stream ID
|
||||||
ID() int64
|
ID() int64
|
||||||
}
|
}
|
||||||
@@ -165,8 +177,8 @@ func encodeMessageB(writer io.Writer, sizeLimit int64, method uint16, data []byt
|
|||||||
return ErrPayloadTooLarge
|
return ErrPayloadTooLarge
|
||||||
}
|
}
|
||||||
buffer := make([]byte, 10 + len(data))
|
buffer := make([]byte, 10 + len(data))
|
||||||
tape.EncodeI16(buffer[:2], method)
|
encodeI16(buffer[:2], method)
|
||||||
tape.EncodeI64(buffer[2:10], uint64(len(data)))
|
encodeI64(buffer[2:10], uint64(len(data)))
|
||||||
copy(buffer[10:], data)
|
copy(buffer[10:], data)
|
||||||
_, err := writer.Write(buffer)
|
_, err := writer.Write(buffer)
|
||||||
return err
|
return err
|
||||||
@@ -187,9 +199,9 @@ func decodeMessageB(
|
|||||||
if errors.Is(err, io.EOF) { return 0, 0, nil, io.ErrUnexpectedEOF }
|
if errors.Is(err, io.EOF) { return 0, 0, nil, io.ErrUnexpectedEOF }
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
method, err = tape.DecodeI16[uint16](headerBuffer[:2])
|
method, err = decodeI16[uint16](headerBuffer[:2])
|
||||||
if err != nil { return 0, 0, nil, err }
|
if err != nil { return 0, 0, nil, err }
|
||||||
length, err := tape.DecodeI64[uint64](headerBuffer[2:10])
|
length, err := decodeI64[uint64](headerBuffer[2:10])
|
||||||
if err != nil { return 0, 0, nil, err }
|
if err != nil { return 0, 0, nil, err }
|
||||||
if length > uint64(sizeLimit) {
|
if length > uint64(sizeLimit) {
|
||||||
return 0, 0, nil, ErrPayloadTooLarge
|
return 0, 0, nil, ErrPayloadTooLarge
|
||||||
|
|||||||
192
tape/decode.go
Normal file
192
tape/decode.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
import "math"
|
||||||
|
import "bufio"
|
||||||
|
|
||||||
|
// Decodable is any type that can decode itself from a decoder.
|
||||||
|
type Decodable interface {
|
||||||
|
// Decode reads data from decoder, replacing the data of the object. It
|
||||||
|
// returns the amount of bytes written, and an error if the write
|
||||||
|
// stopped early.
|
||||||
|
Decode(decoder *Decoder) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder decodes data from an [io.Reader].
|
||||||
|
type Decoder struct {
|
||||||
|
bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder creates a new decoder that reads from reader.
|
||||||
|
func NewDecoder(reader io.Reader) *Decoder {
|
||||||
|
decoder := &Decoder { }
|
||||||
|
decoder.Reader.Reset(reader)
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFull calls [io.ReadFull] on the reader.
|
||||||
|
func (this *Decoder) ReadFull(buffer []byte) (n int, err error) {
|
||||||
|
return io.ReadFull(this, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]) << 40 |
|
||||||
|
uint64(buffer[3]) << 32 |
|
||||||
|
uint64(buffer[4]) << 24 |
|
||||||
|
uint64(buffer[5]) << 16 |
|
||||||
|
uint64(buffer[6]) << 8 |
|
||||||
|
uint64(buffer[7]), n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadIntN decodes an N-byte signed integer from the input reader.
|
||||||
|
func (this *Decoder) ReadIntN(bytes int) (value int64, n int, err error) {
|
||||||
|
uncasted, n, err := this.ReadUintN(bytes)
|
||||||
|
return int64(uncasted), n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUintN decodes an N-byte unsigned integer from the input reader.
|
||||||
|
func (this *Decoder) ReadUintN(bytes int) (value uint64, n int, err error) {
|
||||||
|
// TODO: don't make multiple read calls (without allocating)
|
||||||
|
buffer := [1]byte { }
|
||||||
|
for bytesLeft := bytes; bytesLeft > 0; bytesLeft -- {
|
||||||
|
nn, err := this.ReadFull(buffer[:])
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
value |= uint64(buffer[0]) << ((bytesLeft - 1) * 8)
|
||||||
|
}
|
||||||
|
// *read* integers too big, but don't return them.
|
||||||
|
if bytes > 8 { value = 0 }
|
||||||
|
return value, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat16 decodes a 16-bit floating point value from the input reader.
|
||||||
|
func (this *Decoder) ReadFloat16() (value float32, n int, err error) {
|
||||||
|
bits, nn, err := this.ReadUint16()
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
return math.Float32frombits(f16bitsToF32bits(bits)), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat32 decldes a 32-bit floating point value from the input reader.
|
||||||
|
func (this *Decoder) ReadFloat32() (value float32, n int, err error) {
|
||||||
|
bits, nn, err := this.ReadUint32()
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
return math.Float32frombits(bits), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat64 decldes a 64-bit floating point value from the input reader.
|
||||||
|
func (this *Decoder) ReadFloat64() (value float64, n int, err error) {
|
||||||
|
bits, nn, err := this.ReadUint64()
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
return math.Float64frombits(bits), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadTag decodes a [Tag] from the input reader.
|
||||||
|
func (this *Decoder) ReadTag() (value Tag, n int, err error) {
|
||||||
|
uncasted, nn, err := this.ReadUint8()
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
return Tag(uncasted), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// f16bitsToF32bits returns uint32 (float32 bits) converted from specified uint16.
|
||||||
|
// Taken from https://github.com/x448/float16/blob/v0.8.4/float16
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
func f16bitsToF32bits(in uint16) uint32 {
|
||||||
|
// All 65536 conversions with this were confirmed to be correct
|
||||||
|
// by Montgomery Edwards⁴⁴⁸ (github.com/x448).
|
||||||
|
|
||||||
|
sign := uint32(in&0x8000) << 16 // sign for 32-bit
|
||||||
|
exp := uint32(in&0x7c00) >> 10 // exponenent for 16-bit
|
||||||
|
coef := uint32(in&0x03ff) << 13 // significand for 32-bit
|
||||||
|
|
||||||
|
if exp == 0x1f {
|
||||||
|
if coef == 0 {
|
||||||
|
// infinity
|
||||||
|
return sign | 0x7f800000 | coef
|
||||||
|
}
|
||||||
|
// NaN
|
||||||
|
return sign | 0x7fc00000 | coef
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp == 0 {
|
||||||
|
if coef == 0 {
|
||||||
|
// zero
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize subnormal numbers
|
||||||
|
exp++
|
||||||
|
for coef&0x7f800000 == 0 {
|
||||||
|
coef <<= 1
|
||||||
|
exp--
|
||||||
|
}
|
||||||
|
coef &= 0x007fffff
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign | ((exp + (0x7f - 0xf)) << 23) | coef
|
||||||
|
}
|
||||||
505
tape/dynamic.go
Normal file
505
tape/dynamic.go
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
// dont smoke reflection, kids!!!!!!!!!
|
||||||
|
// totally reflectric, reflectrified, etc. this is probably souper slow but
|
||||||
|
// certainly no slower than the built in json encoder i'd imagine.
|
||||||
|
// TODO: add support for struct tags: `tape:"0000"`, tape:"0001"` so they can get
|
||||||
|
// transformed into tables with a defined schema
|
||||||
|
|
||||||
|
// TODO: test all of these smaller functions individually
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
var dummyMap map[uint16] any
|
||||||
|
var dummyBuffer []byte
|
||||||
|
|
||||||
|
type errCantAssign string
|
||||||
|
func (err errCantAssign) Error() string {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
|
func errCantAssignf(format string, v ...any) errCantAssign {
|
||||||
|
return errCantAssign(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeAny encodes an "any" value. Returns an error if the underlying type is
|
||||||
|
// unsupported. Supported types are:
|
||||||
|
//
|
||||||
|
// - int
|
||||||
|
// - int<N>
|
||||||
|
// - uint
|
||||||
|
// - uint<N>
|
||||||
|
// - string
|
||||||
|
// - []<supported type>
|
||||||
|
// - map[uint16]<supported type>
|
||||||
|
func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||||
|
// primitives
|
||||||
|
reflectValue := reflect.ValueOf(value)
|
||||||
|
switch reflectValue.Kind() {
|
||||||
|
case reflect.Int: return encoder.WriteInt32(int32(reflectValue.Int()))
|
||||||
|
case reflect.Uint: return encoder.WriteUint32(uint32(reflectValue.Uint()))
|
||||||
|
case reflect.Int8: return encoder.WriteInt8(int8(reflectValue.Int()))
|
||||||
|
case reflect.Uint8: return encoder.WriteUint8(uint8(reflectValue.Uint()))
|
||||||
|
case reflect.Int16: return encoder.WriteInt16(int16(reflectValue.Int()))
|
||||||
|
case reflect.Uint16: return encoder.WriteUint16(uint16(reflectValue.Uint()))
|
||||||
|
case reflect.Int32: return encoder.WriteInt32(int32(reflectValue.Int()))
|
||||||
|
case reflect.Uint32: return encoder.WriteUint32(uint32(reflectValue.Uint()))
|
||||||
|
case reflect.Int64: return encoder.WriteInt64(int64(reflectValue.Int()))
|
||||||
|
case reflect.Uint64: return encoder.WriteUint64(uint64(reflectValue.Uint()))
|
||||||
|
case reflect.String:
|
||||||
|
if reflectValue.Len() > MaxStructureLength {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
return EncodeAny(encoder, []byte(reflectValue.String()), tag)
|
||||||
|
}
|
||||||
|
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
|
||||||
|
if reflectValue.Len() > MaxStructureLength {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
if tag.Is(LBA) {
|
||||||
|
nn, err := encoder.WriteUintN(uint64(reflectValue.Len()), tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
nn, err := encoder.Write(reflectValue.Bytes())
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregates
|
||||||
|
reflectType := reflect.TypeOf(value)
|
||||||
|
switch reflectType.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return encodeAnySlice(encoder, value, tag)
|
||||||
|
// case reflect.Array:
|
||||||
|
// TODO: we can encode arrays. but can we decode into them?
|
||||||
|
// that's the fucken question. maybe we just do the first
|
||||||
|
// return encodeAnySlice(encoder, reflect.ValueOf(value).Slice(0, reflectType.Len()).Interface(), tag)
|
||||||
|
case reflect.Map:
|
||||||
|
if reflectValue.Len() > MaxStructureLength {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
if reflectType.Key() == reflect.TypeOf(uint16(0)) {
|
||||||
|
return encodeAnyMap(encoder, value, tag)
|
||||||
|
}
|
||||||
|
return n, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
|
||||||
|
}
|
||||||
|
return n, fmt.Errorf("cannot encode type %T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeAny decodes data and places it into destination, which must be a
|
||||||
|
// pointer to a supported type. See [EncodeAny] for a list of supported types.
|
||||||
|
func DecodeAny(decoder *Decoder, destination any, tag Tag) (n int, err error) {
|
||||||
|
reflectDestination := reflect.ValueOf(destination)
|
||||||
|
if reflectDestination.Kind() != reflect.Pointer {
|
||||||
|
return n, fmt.Errorf("expected pointer destination, not %v", destination)
|
||||||
|
}
|
||||||
|
return decodeAny(decoder, reflectDestination.Elem(), tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknownSlicePlaceholder is inserted by skeletonValue and informs the program
|
||||||
|
// that the destination for the slice needs to be generated based on the item
|
||||||
|
// tag in the OTA.
|
||||||
|
type unknownSlicePlaceholder struct { }
|
||||||
|
var unknownSlicePlaceholderType = reflect.TypeOf(unknownSlicePlaceholder { })
|
||||||
|
|
||||||
|
// decodeAny is internal to [DecodeAny]. It takes in an addressable
|
||||||
|
// [reflect.Value] as the destination. If the decoded value cannot fit in the
|
||||||
|
// destination, it skims over the payload, leaves the destination empty, and
|
||||||
|
// returns without an error.
|
||||||
|
func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
|
||||||
|
n, err = decodeAnyOrError(decoder, destination, tag)
|
||||||
|
if _, ok := err.(errCantAssign); ok {
|
||||||
|
if n > 0 { panic(fmt.Sprintf("decodeAnyOrError decoded more than it should: %d", n)) }
|
||||||
|
nn, err := Skim(decoder, tag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAnyOrError is internal to [decodeAny]. It takes in an addressable
|
||||||
|
// [reflect.Value] as the destination. If the decoded value cannot fit in the
|
||||||
|
// destination, it decodes nothing and returns an error of type errCantAssign,
|
||||||
|
// except for the case of a mismatched OTA element tag, wherein it will skim
|
||||||
|
// over the rest of the payload, leave the destination empty, and return without
|
||||||
|
// an error.
|
||||||
|
func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
|
||||||
|
err = canSet(destination.Type(), tag)
|
||||||
|
if err != nil { return n, err }
|
||||||
|
|
||||||
|
switch tag.WithoutCN() {
|
||||||
|
case SI:
|
||||||
|
// SI: (none)
|
||||||
|
setInt(destination, uint64(tag.CN()))
|
||||||
|
case LI:
|
||||||
|
// LI: <value: IntN>
|
||||||
|
nn, err := decodeAndSetUint(decoder, destination, tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case LSI:
|
||||||
|
// LSI: <value: IntN>
|
||||||
|
nn, err := decodeAndSetInt(decoder, destination, tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case FP:
|
||||||
|
// FP: <value: FloatN>
|
||||||
|
nn, err := decodeAndSetFloat(decoder, destination, tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case SBA:
|
||||||
|
// SBA: <data: U8>*
|
||||||
|
length := tag.CN()
|
||||||
|
if length > MaxStructureLength {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
buffer := make([]byte, length)
|
||||||
|
nn, err := decoder.Read(buffer)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setByteArray(destination, buffer)
|
||||||
|
case LBA:
|
||||||
|
// LBA: <length: UN> <data: U8>*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if length > uint64(MaxStructureLength) {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
buffer := make([]byte, length)
|
||||||
|
nn, err = decoder.Read(buffer)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setByteArray(destination, buffer)
|
||||||
|
case OTA:
|
||||||
|
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if length > uint64(MaxStructureLength) {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
lengthCast, err := Uint64ToIntSafe(length)
|
||||||
|
if err != nil { return n, err }
|
||||||
|
oneTag, nn, err := decoder.ReadTag()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if destination.Cap() < lengthCast {
|
||||||
|
destination.Grow(lengthCast - destination.Cap())
|
||||||
|
}
|
||||||
|
// skip the rest of the array if the one tag doesn't
|
||||||
|
// match up with the destination
|
||||||
|
err = canSet(destination.Type().Elem(), oneTag)
|
||||||
|
if _, ok := err.(errCantAssign); ok {
|
||||||
|
for _ = range length {
|
||||||
|
nn, err := Skim(decoder, oneTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil { return n, err }
|
||||||
|
destination.SetLen(lengthCast)
|
||||||
|
for index := range length {
|
||||||
|
nn, err := decodeAny(decoder, destination.Index(int(index)), oneTag)
|
||||||
|
n += nn
|
||||||
|
if _, ok := err.(errCantAssign); ok {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case KTV:
|
||||||
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if length > uint64(MaxStructureLength) {
|
||||||
|
return 0, ErrTooLong
|
||||||
|
}
|
||||||
|
destination.Clear()
|
||||||
|
for _ = range length {
|
||||||
|
key, nn, err := decoder.ReadUint16()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
itemTag, nn, err := decoder.ReadTag()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
value, err := skeletonValue(decoder, itemTag)
|
||||||
|
if err != nil { return n, err }
|
||||||
|
nn, err = decodeAny(decoder, value.Elem(), itemTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
destination.SetMapIndex(reflect.ValueOf(key), value.Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return n, fmt.Errorf("unknown TN %d", tag.TN())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagAny returns the correct tag for an "any" value. Returns an error if the
|
||||||
|
// underlying type is unsupported. See [EncodeAny] for a list of supported
|
||||||
|
// types.
|
||||||
|
func TagAny(value any) (Tag, error) {
|
||||||
|
return tagAny(reflect.ValueOf(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagAny(reflectValue reflect.Value) (Tag, error) {
|
||||||
|
// primitives
|
||||||
|
switch reflectValue.Kind() {
|
||||||
|
case reflect.Int: return LSI.WithCN(3), nil
|
||||||
|
case reflect.Int8: return LSI.WithCN(0), nil
|
||||||
|
case reflect.Int16: return LSI.WithCN(1), nil
|
||||||
|
case reflect.Int32: return LSI.WithCN(3), nil
|
||||||
|
case reflect.Int64: return LSI.WithCN(7), nil
|
||||||
|
case reflect.Uint: return LI.WithCN(3), nil
|
||||||
|
case reflect.Uint8: return LI.WithCN(0), nil
|
||||||
|
case reflect.Uint16: return LI.WithCN(1), nil
|
||||||
|
case reflect.Uint32: return LI.WithCN(3), nil
|
||||||
|
case reflect.Uint64: return LI.WithCN(7), nil
|
||||||
|
case reflect.String: return bufferLenTag(reflectValue.Len()), nil
|
||||||
|
}
|
||||||
|
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
|
||||||
|
return bufferLenTag(reflectValue.Len()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregates
|
||||||
|
reflectType := reflectValue.Type()
|
||||||
|
switch reflectType.Kind() {
|
||||||
|
case reflect.Slice: return OTA.WithCN(IntBytes(uint64(reflectValue.Len())) - 1), nil
|
||||||
|
case reflect.Array: return OTA.WithCN(reflectType.Len()), nil
|
||||||
|
case reflect.Map:
|
||||||
|
if reflectType.Key() == reflect.TypeOf(uint16(0)) {
|
||||||
|
return KTV.WithCN(IntBytes(uint64(reflectValue.Len())) - 1), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot encode map key %v, key must be uint16", reflectType.Key())
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot get tag of type %v", reflectType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||||
|
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
reflectValue := reflect.ValueOf(value)
|
||||||
|
nn, err := encoder.WriteUintN(uint64(reflectValue.Len()), tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
reflectType := reflect.TypeOf(value)
|
||||||
|
oneTag, err := tagAny(reflect.Zero(reflectType.Elem()))
|
||||||
|
if err != nil { return n, err }
|
||||||
|
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||||
|
itemTag, err := tagAny(reflectValue.Index(index))
|
||||||
|
if err != nil { return n, err }
|
||||||
|
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
|
||||||
|
}
|
||||||
|
if oneTag.Is(SBA) { oneTag += 1 << 5 }
|
||||||
|
nn, err = encoder.WriteUint8(uint8(oneTag))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||||
|
item := reflectValue.Index(index).Interface()
|
||||||
|
nn, err = EncodeAny(encoder, item, oneTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||||
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
reflectValue := reflect.ValueOf(value)
|
||||||
|
nn, err := encoder.WriteUintN(uint64(reflectValue.Len()), tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
iter := reflectValue.MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
reflectValue := iter.Value().Elem()
|
||||||
|
key := iter.Key().Interface().(uint16)
|
||||||
|
value := reflectValue.Interface()
|
||||||
|
nn, err = encoder.WriteUint16(key)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
itemTag, err := tagAny(reflectValue)
|
||||||
|
if err != nil { return n, err }
|
||||||
|
nn, err = encoder.WriteUint8(uint8(itemTag))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = EncodeAny(encoder, value, itemTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func canSet(destination reflect.Type, tag Tag) error {
|
||||||
|
switch tag.WithoutCN() {
|
||||||
|
case SI, LI, LSI:
|
||||||
|
switch destination.Kind() {
|
||||||
|
case
|
||||||
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
default:
|
||||||
|
return errCantAssignf("cannot assign integer to %v", destination)
|
||||||
|
}
|
||||||
|
case FP:
|
||||||
|
switch destination.Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
default:
|
||||||
|
return errCantAssignf("cannot assign float to %v", destination)
|
||||||
|
}
|
||||||
|
case SBA, LBA:
|
||||||
|
if destination.Kind() != reflect.Slice {
|
||||||
|
return errCantAssignf("cannot assign byte array to %v", destination)
|
||||||
|
}
|
||||||
|
if destination.Elem() != reflect.TypeOf(byte(0)) {
|
||||||
|
return errCantAssignf("cannot convert %v to *[]byte", destination)
|
||||||
|
}
|
||||||
|
case OTA:
|
||||||
|
if destination.Kind() != reflect.Slice {
|
||||||
|
return errCantAssignf("cannot assign array to %v", destination)
|
||||||
|
}
|
||||||
|
case KTV:
|
||||||
|
if destination != reflect.TypeOf(dummyMap) {
|
||||||
|
return errCantAssignf("cannot assign table to %v", destination)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown TN %d", tag.TN())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setInt expects a settable destination.
|
||||||
|
func setInt[T int64 | uint64](destination reflect.Value, value T) {
|
||||||
|
switch {
|
||||||
|
case destination.CanInt():
|
||||||
|
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
|
||||||
|
case destination.CanUint():
|
||||||
|
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
|
||||||
|
default:
|
||||||
|
panic("setInt called on an unsupported type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFloat expects a settable destination.
|
||||||
|
func setFloat(destination reflect.Value, value float64) {
|
||||||
|
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setByteArrayexpects a settable destination.
|
||||||
|
func setByteArray(destination reflect.Value, value []byte) {
|
||||||
|
destination.Set(reflect.ValueOf(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAndSetInt expects a settable destination.
|
||||||
|
func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
||||||
|
value, nn, err := decoder.ReadIntN(bytes)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setInt(destination, value)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAndSetUint expects a settable destination.
|
||||||
|
func decodeAndSetUint(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
||||||
|
value, nn, err := decoder.ReadUintN(bytes)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setInt(destination, value)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAndSetInt expects a settable destination.
|
||||||
|
func decodeAndSetFloat(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
||||||
|
switch bytes {
|
||||||
|
case 8:
|
||||||
|
value, nn, err := decoder.ReadFloat64()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setFloat(destination, float64(value))
|
||||||
|
return n, nil
|
||||||
|
case 4:
|
||||||
|
value, nn, err := decoder.ReadFloat32()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
setFloat(destination, float64(value))
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, errCantAssignf("unsupported bit width float%d", bytes * 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// skeletonValue returns a pointer value. In order for it to be set, it must be
|
||||||
|
// dereferenced using Elem().
|
||||||
|
func skeletonValue(decoder *Decoder, tag Tag) (reflect.Value, error) {
|
||||||
|
typ, err := typeOf(decoder, tag)
|
||||||
|
if err != nil { return reflect.Value { }, err }
|
||||||
|
return reflect.New(typ), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeOf returns the type of the current tag being decoded. It does not use up
|
||||||
|
// the decoder, it only peeks.
|
||||||
|
func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
|
||||||
|
switch tag.WithoutCN() {
|
||||||
|
case SI:
|
||||||
|
return reflect.TypeOf(uint8(0)), nil
|
||||||
|
case LI:
|
||||||
|
switch tag.CN() {
|
||||||
|
case 0: return reflect.TypeOf(uint8(0)), nil
|
||||||
|
case 1: return reflect.TypeOf(uint16(0)), nil
|
||||||
|
case 3: return reflect.TypeOf(uint32(0)), nil
|
||||||
|
case 7: return reflect.TypeOf(uint64(0)), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown CN %d for LI", tag.CN())
|
||||||
|
case LSI:
|
||||||
|
switch tag.CN() {
|
||||||
|
case 0: return reflect.TypeOf(int8(0)), nil
|
||||||
|
case 1: return reflect.TypeOf(int16(0)), nil
|
||||||
|
case 3: return reflect.TypeOf(int32(0)), nil
|
||||||
|
case 7: return reflect.TypeOf(int64(0)), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown CN %d for LSI", tag.CN())
|
||||||
|
case FP:
|
||||||
|
switch tag.CN() {
|
||||||
|
case 3: return reflect.TypeOf(float32(0)), nil
|
||||||
|
case 7: return reflect.TypeOf(float64(0)), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown CN %d for FP", tag.CN())
|
||||||
|
case SBA: return reflect.SliceOf(reflect.TypeOf(byte(0))), nil
|
||||||
|
case LBA: return reflect.SliceOf(reflect.TypeOf(byte(0))), nil
|
||||||
|
case OTA:
|
||||||
|
elemTag, dimension, err := peekSlice(decoder, tag)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if elemTag.Is(OTA) { panic("peekSlice cannot return OTA") }
|
||||||
|
typ, err := typeOf(decoder, elemTag)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
for _ = range dimension {
|
||||||
|
typ = reflect.SliceOf(typ)
|
||||||
|
}
|
||||||
|
return typ, nil
|
||||||
|
case KTV: return reflect.TypeOf(dummyMap), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown TN %d", tag.TN())
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekSlice returns the element tag and dimension count of the OTA currently
|
||||||
|
// being decoded. It does not use up the decoder, it only peeks.
|
||||||
|
func peekSlice(decoder *Decoder, tag Tag) (Tag, int, error) {
|
||||||
|
offset := 0
|
||||||
|
dimension := 0
|
||||||
|
currentTag := tag
|
||||||
|
for {
|
||||||
|
elem, populated, n, err := peekSliceOnce(decoder, currentTag, offset)
|
||||||
|
if err != nil { return 0, 0, err }
|
||||||
|
currentTag = elem
|
||||||
|
offset = n
|
||||||
|
dimension += 1
|
||||||
|
if elem.Is(OTA) {
|
||||||
|
if !populated {
|
||||||
|
// default to a large byte array, will be
|
||||||
|
// interpreted as a string.
|
||||||
|
return LBA, dimension + 1, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return elem, dimension, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekSliceOnce returns the element tag of the OTA located offset bytes ahead
|
||||||
|
// of the current position. It does not use up the decoder, it only peeks. The n
|
||||||
|
// return value denotes how far away from 0 it peeked. If the OTA has more than
|
||||||
|
// zero items, populated will be set to true.
|
||||||
|
func peekSliceOnce(decoder *Decoder, tag Tag, offset int) (elem Tag, populated bool, n int, err error) {
|
||||||
|
lengthStart := offset
|
||||||
|
lengthEnd := lengthStart + tag.CN() + 1
|
||||||
|
elemTagStart := lengthEnd
|
||||||
|
elemTagEnd := elemTagStart + 1
|
||||||
|
|
||||||
|
headerBytes, err := decoder.Peek(elemTagEnd)
|
||||||
|
if err != nil { return 0, false, 0, err }
|
||||||
|
|
||||||
|
elem = Tag(headerBytes[len(headerBytes) - 1])
|
||||||
|
for index := lengthStart; index < lengthEnd; index += 1 {
|
||||||
|
if headerBytes[index] > 0 {
|
||||||
|
populated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = elemTagEnd
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
310
tape/dynamic_test.go
Normal file
310
tape/dynamic_test.go
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "bytes"
|
||||||
|
import "testing"
|
||||||
|
import "reflect"
|
||||||
|
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||||
|
|
||||||
|
type userDefinedInteger int16
|
||||||
|
|
||||||
|
func TestEncodeAnyInt(test *testing.T) {
|
||||||
|
err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
err = testEncodeAny(test, 400, LSI.WithCN(3), tu.S(
|
||||||
|
0, 0, 0x1, 0x90,
|
||||||
|
))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeAnyTable(test *testing.T) {
|
||||||
|
err := testEncodeAny(test, map[uint16] any {
|
||||||
|
0xF3B9: 1,
|
||||||
|
0x0102: 2,
|
||||||
|
0x0000: "hi!",
|
||||||
|
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||||
|
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||||
|
0x2345: [][]int16 { []int16 { 0x5 }, []int16 { 0x17, -0xAAA } },
|
||||||
|
0x3456: userDefinedInteger(0x3921),
|
||||||
|
}, KTV.WithCN(0), tu.S(7).AddVar(
|
||||||
|
[]byte {
|
||||||
|
0xF3, 0xB9,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 1,
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0x01, 0x02,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 2,
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0, 0,
|
||||||
|
byte(SBA.WithCN(3)),
|
||||||
|
'h', 'i', '!',
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0xFF, 0xFF,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(LI.WithCN(1)),
|
||||||
|
0xBE, 0xE5, 0x77, 0x77,
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0x12, 0x34,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xAA, 0xAA,
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0x23, 0x45,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xF5, 0x56,
|
||||||
|
},
|
||||||
|
[]byte {
|
||||||
|
0x34, 0x56,
|
||||||
|
byte(LSI.WithCN(1)),
|
||||||
|
0x39, 0x21,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeWrongType(test *testing.T) {
|
||||||
|
datas := [][]byte {
|
||||||
|
/* int8 */ []byte { byte(LSI.WithCN(0)), 0x45 },
|
||||||
|
/* int16 */ []byte { byte(LSI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* int32 */ []byte { byte(LSI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* int64 */ []byte { byte(LSI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* uint5 */ []byte { byte(SI.WithCN(12)) },
|
||||||
|
/* uint8 */ []byte { byte(LI.WithCN(0)), 0x45 },
|
||||||
|
/* uint16 */ []byte { byte(LI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* uint32 */ []byte { byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* uint64 */ []byte { byte(LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* string */ []byte { byte(SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' },
|
||||||
|
/* []byte */ []byte { byte(SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' },
|
||||||
|
/* []string */ []byte {
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(LBA.WithCN(0)),
|
||||||
|
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
||||||
|
0x05, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||||
|
},
|
||||||
|
/* map[uint16] any */ []byte {
|
||||||
|
byte(KTV.WithCN(0)), 2,
|
||||||
|
0x02, 0x23, byte(LSI.WithCN(1)), 0x45, 0x67,
|
||||||
|
0x02, 0x23, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, data := range datas {
|
||||||
|
test.Logf("data %2d %v [%s]", index, Tag(data[0]), tu.HexBytes(data[1:]))
|
||||||
|
// integers should only assign to other integers
|
||||||
|
if index > 8 {
|
||||||
|
cas := func(destination any) {
|
||||||
|
n, err := DecodeAny(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
|
||||||
|
if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
|
||||||
|
reflectValue := reflect.ValueOf(destination).Elem()
|
||||||
|
if reflectValue.CanInt() {
|
||||||
|
if reflectValue.Int() != 0 {
|
||||||
|
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if reflectValue.Uint() != 0 {
|
||||||
|
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n != len(data) - 1 {
|
||||||
|
test.Fatalf("n not equal: %d != %d", n, len(data) - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test.Log("- int8")
|
||||||
|
{ var dest int8; cas(&dest) }
|
||||||
|
test.Log("- int16")
|
||||||
|
{ var dest int16; cas(&dest) }
|
||||||
|
test.Log("- int32")
|
||||||
|
{ var dest int32; cas(&dest) }
|
||||||
|
test.Log("- int64")
|
||||||
|
{ var dest int64; cas(&dest) }
|
||||||
|
test.Log("- uint8")
|
||||||
|
{ var dest uint8; cas(&dest) }
|
||||||
|
test.Log("- uint16")
|
||||||
|
{ var dest uint16; cas(&dest) }
|
||||||
|
test.Log("- uint32")
|
||||||
|
{ var dest uint32; cas(&dest) }
|
||||||
|
test.Log("- uint64")
|
||||||
|
{ var dest uint64; cas(&dest) }
|
||||||
|
}
|
||||||
|
arrayCase := func(destination any) {
|
||||||
|
n, err := DecodeAny(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
|
||||||
|
if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
|
||||||
|
reflectDestination := reflect.ValueOf(destination)
|
||||||
|
reflectValue := reflectDestination.Elem()
|
||||||
|
if reflectValue.Len() != 0 {
|
||||||
|
test.Fatalf("len(destination) not zero: %v", reflectValue.Interface())
|
||||||
|
}
|
||||||
|
if n != len(data) - 1 {
|
||||||
|
test.Fatalf("n not equal: %d != %d", n, len(data) - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SBA/LBA types should only assign to other SBA/LBA types
|
||||||
|
if index != 9 && index != 10 {
|
||||||
|
test.Log("- string")
|
||||||
|
{ var dest string; arrayCase(&dest) }
|
||||||
|
test.Log("- []byte")
|
||||||
|
{ var dest []byte; arrayCase(&dest) }
|
||||||
|
}
|
||||||
|
// arrays should only assign to other arrays
|
||||||
|
if index != 11 {
|
||||||
|
test.Log("- []string")
|
||||||
|
{ var dest []string; arrayCase(&dest) }
|
||||||
|
}
|
||||||
|
// tables should only assign to other tables
|
||||||
|
if index != 12 {
|
||||||
|
test.Log("- map[uint16] any")
|
||||||
|
{ var dest = map[uint16] any { }; arrayCase(&dest) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeAnyTable(test *testing.T) {
|
||||||
|
err := testEncodeDecodeAny(test, map[uint16] any {
|
||||||
|
0xF3B9: uint32(1),
|
||||||
|
0x0102: uint32(2),
|
||||||
|
0x0103: int64(23432),
|
||||||
|
0x0104: int64(-88777),
|
||||||
|
0x0000: []byte("hi!"),
|
||||||
|
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||||
|
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||||
|
}, nil)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeekSlice(test *testing.T) {
|
||||||
|
buffer := bytes.NewBuffer([]byte {
|
||||||
|
2, byte(OTA.WithCN(3)),
|
||||||
|
0, 0, 0, 1, byte(LI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xAA, 0xAA,
|
||||||
|
})
|
||||||
|
decoder := NewDecoder(buffer)
|
||||||
|
|
||||||
|
elem, dimension, err := peekSlice(decoder, OTA.WithCN(0))
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
if elem != LI.WithCN(1) {
|
||||||
|
test.Fatalf("wrong element tag: %v %02X", elem, byte(elem))
|
||||||
|
}
|
||||||
|
if got, correct := dimension, 2; got != correct {
|
||||||
|
test.Fatalf("wrong dimension: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeekSliceOnce(test *testing.T) {
|
||||||
|
buffer := bytes.NewBuffer([]byte {
|
||||||
|
2, byte(OTA.WithCN(3)),
|
||||||
|
0, 0, 0, 1, byte(LI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xAA, 0xAA,
|
||||||
|
})
|
||||||
|
decoder := NewDecoder(buffer)
|
||||||
|
|
||||||
|
test.Log("--- stage 1")
|
||||||
|
elem, populated, n, err := peekSliceOnce(decoder, OTA.WithCN(0), 0)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
if elem != OTA.WithCN(3) {
|
||||||
|
test.Fatal("wrong element tag:", elem)
|
||||||
|
}
|
||||||
|
if !populated {
|
||||||
|
test.Fatal("wrong populated:", populated)
|
||||||
|
}
|
||||||
|
if got, correct := n, 2; got != correct {
|
||||||
|
test.Fatalf("wrong n: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("--- stage 2")
|
||||||
|
elem, populated, n, err = peekSliceOnce(decoder, elem, n)
|
||||||
|
if err != nil { test.Fatal(err) }
|
||||||
|
if elem != LI.WithCN(1) {
|
||||||
|
test.Fatal("wrong element tag:", elem)
|
||||||
|
}
|
||||||
|
if !populated {
|
||||||
|
test.Fatal("wrong populated:", populated)
|
||||||
|
}
|
||||||
|
if got, correct := n, 7; got != correct {
|
||||||
|
test.Fatalf("wrong n: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encAny(value any) ([]byte, Tag, int, error) {
|
||||||
|
tag, err := TagAny(value)
|
||||||
|
if err != nil { return nil, 0, 0, err }
|
||||||
|
buffer := bytes.Buffer { }
|
||||||
|
encoder := NewEncoder(&buffer)
|
||||||
|
n, err := EncodeAny(encoder, value, tag)
|
||||||
|
if err != nil { return nil, 0, n, err }
|
||||||
|
encoder.Flush()
|
||||||
|
return buffer.Bytes(), tag, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decAny(data []byte) (Tag, any, int, error) {
|
||||||
|
destination := map[uint16] any { }
|
||||||
|
tag, err := TagAny(destination)
|
||||||
|
if err != nil { return 0, nil, 0, err }
|
||||||
|
n, err := DecodeAny(NewDecoder(bytes.NewBuffer(data)), &destination, tag)
|
||||||
|
if err != nil { return 0, nil, n, err }
|
||||||
|
return tag, destination, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncodeAny(test *testing.T, value any, correctTag Tag, correctBytes tu.Snake) error {
|
||||||
|
bytes, tag, n, err := encAny(value)
|
||||||
|
if err != nil { return err }
|
||||||
|
test.Log("n: ", n)
|
||||||
|
test.Log("tag: ", tag)
|
||||||
|
test.Log("got: ", tu.HexBytes(bytes))
|
||||||
|
test.Log("correct:", correctBytes)
|
||||||
|
if tag != correctTag {
|
||||||
|
return fmt.Errorf("tag not equal: %v != %v", tag, correctTag)
|
||||||
|
}
|
||||||
|
if ok, n := correctBytes.Check(bytes); !ok {
|
||||||
|
return fmt.Errorf("bytes not equal at index %d", n)
|
||||||
|
}
|
||||||
|
if n != len(bytes) {
|
||||||
|
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncodeDecodeAny(test *testing.T, value, correctValue any) error {
|
||||||
|
if correctValue == nil {
|
||||||
|
correctValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Log("encoding...")
|
||||||
|
bytes, tag, n, err := encAny(value)
|
||||||
|
if err != nil { return err }
|
||||||
|
test.Log("n: ", n)
|
||||||
|
test.Log("tag:", tag)
|
||||||
|
test.Log("got:", tu.HexBytes(bytes))
|
||||||
|
test.Log("decoding...", tag)
|
||||||
|
if n != len(bytes) {
|
||||||
|
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, decoded, n, err := decAny(bytes)
|
||||||
|
if err != nil { return err }
|
||||||
|
test.Log("got: ", tu.Describe(decoded))
|
||||||
|
test.Log("correct:", tu.Describe(correctValue))
|
||||||
|
if !reflect.DeepEqual(decoded, correctValue) {
|
||||||
|
return fmt.Errorf("values not equal")
|
||||||
|
}
|
||||||
|
if n != len(bytes) {
|
||||||
|
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
189
tape/encode.go
Normal file
189
tape/encode.go
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
import "math"
|
||||||
|
import "bufio"
|
||||||
|
|
||||||
|
// Encodable is any type that can write itself to an encoder.
|
||||||
|
type Encodable interface {
|
||||||
|
// Encode sends data to encoder. It returns the amount of bytes written,
|
||||||
|
// and an error if the write stopped early.
|
||||||
|
Encode(encoder *Encoder) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder encodes data to an io.Writer.
|
||||||
|
type Encoder struct {
|
||||||
|
bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder creates a new encoder that writes to writer.
|
||||||
|
func NewEncoder(writer io.Writer) *Encoder {
|
||||||
|
encoder := &Encoder { }
|
||||||
|
encoder.Reset(writer)
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteIntN encodes an N-byte signed integer to the output writer.
|
||||||
|
func (this *Encoder) WriteIntN(value int64, bytes int) (n int, err error) {
|
||||||
|
return this.WriteUintN(uint64(value), bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for Write/ReadUintN, increase buffers 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 the int64 versions as well though
|
||||||
|
// because its ergonomic.
|
||||||
|
|
||||||
|
// WriteUintN encodes an N-byte unsigned integer to the output writer.
|
||||||
|
func (this *Encoder) WriteUintN(value uint64, bytes int) (n int, err error) {
|
||||||
|
// TODO: don't make multiple write calls (without allocating)
|
||||||
|
buffer := [1]byte { }
|
||||||
|
for bytesLeft := bytes; bytesLeft > 0; bytesLeft -- {
|
||||||
|
buffer[0] = byte(value) >> ((bytesLeft - 1) * 8)
|
||||||
|
nn, err := this.Write(buffer[:])
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat16 encodes a 16-bit floating point value to the output writer.
|
||||||
|
func (this *Encoder) WriteFloat16(value float32) (n int, err error) {
|
||||||
|
return this.WriteUint16(f32bitsToF16bits(math.Float32bits(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32 encodes a 32-bit floating point value to the output writer.
|
||||||
|
func (this *Encoder) WriteFloat32(value float32) (n int, err error) {
|
||||||
|
return this.WriteUint32(math.Float32bits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat64 encodes a 64-bit floating point value to the output writer.
|
||||||
|
func (this *Encoder) WriteFloat64(value float64) (n int, err error) {
|
||||||
|
return this.WriteUint64(math.Float64bits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTag encodes a [Tag] to the output writer.
|
||||||
|
func (this *Encoder) WriteTag(value Tag) (n int, err error) {
|
||||||
|
return this.WriteUint8(uint8(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// f32bitsToF16bits returns uint16 (Float16 bits) converted from the specified float32.
|
||||||
|
// Conversion rounds to nearest integer with ties to even.
|
||||||
|
// Taken from https://github.com/x448/float16/blob/v0.8.4/float16
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
func f32bitsToF16bits(u32 uint32) uint16 {
|
||||||
|
// Translated from Rust to Go by Montgomery Edwards⁴⁴⁸ (github.com/x448).
|
||||||
|
// All 4294967296 conversions with this were confirmed to be correct by x448.
|
||||||
|
// Original Rust implementation is by Kathryn Long (github.com/starkat99) with MIT license.
|
||||||
|
|
||||||
|
sign := u32 & 0x80000000
|
||||||
|
exp := u32 & 0x7f800000
|
||||||
|
coef := u32 & 0x007fffff
|
||||||
|
|
||||||
|
if exp == 0x7f800000 {
|
||||||
|
// NaN or Infinity
|
||||||
|
nanBit := uint32(0)
|
||||||
|
if coef != 0 {
|
||||||
|
nanBit = uint32(0x0200)
|
||||||
|
}
|
||||||
|
return uint16((sign >> 16) | uint32(0x7c00) | nanBit | (coef >> 13))
|
||||||
|
}
|
||||||
|
|
||||||
|
halfSign := sign >> 16
|
||||||
|
|
||||||
|
unbiasedExp := int32(exp>>23) - 127
|
||||||
|
halfExp := unbiasedExp + 15
|
||||||
|
|
||||||
|
if halfExp >= 0x1f {
|
||||||
|
return uint16(halfSign | uint32(0x7c00))
|
||||||
|
}
|
||||||
|
|
||||||
|
if halfExp <= 0 {
|
||||||
|
if 14-halfExp > 24 {
|
||||||
|
return uint16(halfSign)
|
||||||
|
}
|
||||||
|
coef := coef | uint32(0x00800000)
|
||||||
|
halfCoef := coef >> uint32(14-halfExp)
|
||||||
|
roundBit := uint32(1) << uint32(13-halfExp)
|
||||||
|
if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 {
|
||||||
|
halfCoef++
|
||||||
|
}
|
||||||
|
return uint16(halfSign | halfCoef)
|
||||||
|
}
|
||||||
|
|
||||||
|
uHalfExp := uint32(halfExp) << 10
|
||||||
|
halfCoef := coef >> 13
|
||||||
|
roundBit := uint32(0x00001000)
|
||||||
|
if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 {
|
||||||
|
return uint16((halfSign | uHalfExp | halfCoef) + 1)
|
||||||
|
}
|
||||||
|
return uint16(halfSign | uHalfExp | halfCoef)
|
||||||
|
}
|
||||||
12
tape/error.go
Normal file
12
tape/error.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
// Error enumerates common errors in this package.
|
||||||
|
type Error string; const (
|
||||||
|
ErrTooLong Error = "data structure too long"
|
||||||
|
ErrTooLarge Error = "number too large"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (err Error) Error() string {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
26
tape/limits.go
Normal file
26
tape/limits.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
// MaxStructureLength determines how long a TAPE data structure can be. This
|
||||||
|
// applies to:
|
||||||
|
//
|
||||||
|
// - OTA
|
||||||
|
// - SBA/LBA
|
||||||
|
// - KTV
|
||||||
|
//
|
||||||
|
// By default it is set at 2^20 (about a million).
|
||||||
|
// You shouldn't need to change this. If you do, it should only be set once at
|
||||||
|
// the start of the program.
|
||||||
|
var MaxStructureLength = 1024 * 1024
|
||||||
|
|
||||||
|
// MaxInt is the maximum value an int can hold. This varies depending on the
|
||||||
|
// system.
|
||||||
|
const MaxInt int = int(^uint(0) >> 1)
|
||||||
|
|
||||||
|
// Uint64ToIntSafe casts the input to an int if it can be done without overflow,
|
||||||
|
// or returns an error otherwise.
|
||||||
|
func Uint64ToIntSafe(input uint64) (int, error) {
|
||||||
|
if input > uint64(MaxInt) {
|
||||||
|
return 0, ErrTooLarge
|
||||||
|
}
|
||||||
|
return int(input), nil
|
||||||
|
}
|
||||||
12
tape/measure.go
Normal file
12
tape/measure.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
// IntBytes returns the number of bytes required to hold a given unsigned
|
||||||
|
// integer.
|
||||||
|
func IntBytes(value uint64) int {
|
||||||
|
bytes := 0
|
||||||
|
for value > 0 || bytes == 0 {
|
||||||
|
value >>= 8;
|
||||||
|
bytes ++
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
21
tape/measure_test.go
Normal file
21
tape/measure_test.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIntBytes(test *testing.T) {
|
||||||
|
if correct, got := 1, IntBytes(0); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(1); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(16); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(255); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 2, IntBytes(256); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
54
tape/skim.go
Normal file
54
tape/skim.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Skim uses up data from a decoder to "skim" over one value (and all else
|
||||||
|
// contained within it) without actually putting the data anywhere.
|
||||||
|
func Skim(decoder *Decoder, tag Tag) (n int, err error) {
|
||||||
|
switch tag.WithoutCN() {
|
||||||
|
case SI:
|
||||||
|
// SI: (none)
|
||||||
|
return n, nil
|
||||||
|
case LI, LSI, FP:
|
||||||
|
// LI: <value: IntN>
|
||||||
|
// LSI: <value: IntN>
|
||||||
|
// FP: <value: FloatN>
|
||||||
|
nn, err := decoder.Discard(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case SBA:
|
||||||
|
// SBA: <data: U8>*
|
||||||
|
nn, err := decoder.Discard(tag.CN())
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case LBA:
|
||||||
|
// LBA: <length: UN> <data: U8>*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = decoder.Discard(int(length))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case OTA:
|
||||||
|
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
oneTag, nn, err := decoder.ReadTag()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
for _ = range length {
|
||||||
|
nn, err := Skim(decoder, oneTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
case KTV:
|
||||||
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
for _ = range length {
|
||||||
|
nn, err := decoder.Discard(2)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
itemTag, nn, err := decoder.ReadTag()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = Skim(decoder, itemTag)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return n, fmt.Errorf("unknown TN %d", tag.TN())
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
137
tape/skim_test.go
Normal file
137
tape/skim_test.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSkimInteger(test *testing.T) {
|
||||||
|
data := []byte {
|
||||||
|
0x12, 0x45, 0x23, 0xF9,
|
||||||
|
}
|
||||||
|
mainDataLen := len(data)
|
||||||
|
// extra junk
|
||||||
|
data = append(data, 0x00, 0x01, 0x02, 0x03,)
|
||||||
|
|
||||||
|
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), LI.WithCN(3))
|
||||||
|
if err != nil {
|
||||||
|
test.Fatal(err)
|
||||||
|
}
|
||||||
|
if got, correct := n, mainDataLen; got != correct {
|
||||||
|
test.Fatalf("n not equal: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkimArray(test *testing.T) {
|
||||||
|
data := []byte {
|
||||||
|
2, byte(LI.WithCN(1)),
|
||||||
|
0xBE, 0xE5, 0x77, 0x77,
|
||||||
|
}
|
||||||
|
mainDataLen := len(data)
|
||||||
|
// extra junk
|
||||||
|
data = append(data, 0x00, 0x01, 0x02, 0x03,)
|
||||||
|
|
||||||
|
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), OTA.WithCN(0))
|
||||||
|
if err != nil {
|
||||||
|
test.Fatal(err)
|
||||||
|
}
|
||||||
|
if got, correct := n, mainDataLen; got != correct {
|
||||||
|
test.Fatalf("n not equal: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkimNestedArray(test *testing.T) {
|
||||||
|
data := []byte {
|
||||||
|
2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xF5, 0x56,
|
||||||
|
}
|
||||||
|
mainDataLen := len(data)
|
||||||
|
// extra junk
|
||||||
|
data = append(data, 0x00, 0x01, 0x02, 0x03,)
|
||||||
|
|
||||||
|
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), OTA.WithCN(0))
|
||||||
|
if err != nil {
|
||||||
|
test.Fatal(err)
|
||||||
|
}
|
||||||
|
if got, correct := n, mainDataLen; got != correct {
|
||||||
|
test.Fatalf("n not equal: %d != %d", got, correct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkimTable(test *testing.T) {
|
||||||
|
data := []byte {
|
||||||
|
2,
|
||||||
|
0xF3, 0xB9,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 1,
|
||||||
|
|
||||||
|
0x01, 0x02,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 2,
|
||||||
|
}
|
||||||
|
mainDataLen := len(data)
|
||||||
|
// extra junk
|
||||||
|
data = append(data, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03)
|
||||||
|
|
||||||
|
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), KTV.WithCN(0))
|
||||||
|
if got, correct := n, mainDataLen; got != correct {
|
||||||
|
test.Fatalf("n not equal: %d != %d ... (%d)", got, correct, len(data))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
test.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkimTableComplex(test *testing.T) {
|
||||||
|
data := []byte {
|
||||||
|
7,
|
||||||
|
0xF3, 0xB9,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 1,
|
||||||
|
|
||||||
|
0x01, 0x02,
|
||||||
|
byte(LSI.WithCN(3)),
|
||||||
|
0, 0, 0, 2,
|
||||||
|
|
||||||
|
0, 0,
|
||||||
|
byte(SBA.WithCN(3)),
|
||||||
|
'h', 'i', '!',
|
||||||
|
|
||||||
|
0xFF, 0xFF,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(LI.WithCN(1)),
|
||||||
|
0xBE, 0xE5, 0x77, 0x77,
|
||||||
|
|
||||||
|
0x12, 0x34,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xAA, 0xAA,
|
||||||
|
|
||||||
|
0x23, 0x45,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xF5, 0x56,
|
||||||
|
|
||||||
|
0x34, 0x56,
|
||||||
|
byte(LSI.WithCN(1)),
|
||||||
|
0x39, 0x21,
|
||||||
|
}
|
||||||
|
mainDataLen := len(data)
|
||||||
|
// extra junk
|
||||||
|
data = append(data, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03)
|
||||||
|
|
||||||
|
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), KTV.WithCN(0))
|
||||||
|
if got, correct := n, mainDataLen; got != correct {
|
||||||
|
test.Fatalf("n not equal: %d != %d ... (%d)", got, correct, len(data))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
test.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
72
tape/tag.go
Normal file
72
tape/tag.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// TODO: fix #7
|
||||||
|
|
||||||
|
type Tag byte; const (
|
||||||
|
SI Tag = 0 << 5 // Small integer
|
||||||
|
LI Tag = 1 << 5 // Large unsigned integer
|
||||||
|
LSI Tag = 2 << 5 // Large signed integer
|
||||||
|
FP Tag = 3 << 5 // Floating point
|
||||||
|
SBA Tag = 4 << 5 // Small byte array
|
||||||
|
LBA Tag = 5 << 5 // Large byte array
|
||||||
|
OTA Tag = 6 << 5 // One-tag array
|
||||||
|
KTV Tag = 7 << 5 // Key-tag-value table
|
||||||
|
TNMask Tag = 0xE0 // The entire TN bitfield
|
||||||
|
CNMask Tag = 0x1F // The entire CN bitfield
|
||||||
|
CNLimit Tag = 32 // All valid CNs are < CNLimit
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tag Tag) TN() int {
|
||||||
|
return int(tag >> 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) CN() int {
|
||||||
|
return int(tag & CNMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) WithCN(cn int) Tag {
|
||||||
|
return (tag & TNMask) | Tag(cn % 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) WithoutCN() Tag {
|
||||||
|
return tag.WithCN(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) Is(other Tag) bool {
|
||||||
|
return tag.TN() == other.TN()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) String() string {
|
||||||
|
tn := fmt.Sprint(tag.TN())
|
||||||
|
switch tag.WithoutCN() {
|
||||||
|
case SI: tn = "SI"
|
||||||
|
case LI: tn = "LI"
|
||||||
|
case LSI: tn = "LSI"
|
||||||
|
case FP: tn = "FP"
|
||||||
|
case SBA: tn = "SBA"
|
||||||
|
case LBA: tn = "LBA"
|
||||||
|
case OTA: tn = "OTA"
|
||||||
|
case KTV: tn = "KTV"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", tn, tag.CN())
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferTag returns the appropriate tag for a buffer.
|
||||||
|
func BufferTag(value []byte) Tag {
|
||||||
|
return bufferLenTag(len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringTag returns the appropriate tag for a string.
|
||||||
|
func StringTag(value string) Tag {
|
||||||
|
return bufferLenTag(len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferLenTag(length int) Tag {
|
||||||
|
if length < int(CNLimit) {
|
||||||
|
return SBA.WithCN(length)
|
||||||
|
} else {
|
||||||
|
return LBA.WithCN(IntBytes(uint64(length)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user