Compare commits
72 Commits
encode-sig
...
unify-byte
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c2b8a0582 | |||
| 4575fa229b | |||
| cbfb513933 | |||
| f10327356e | |||
| f402b46b1c | |||
| c3d0f33700 | |||
| ba2dc6b53f | |||
| 2e03867c66 | |||
| 7a03d8d6b5 | |||
| b2504cda2d | |||
| f6b12d43fb | |||
| c185f5058f | |||
| 813d219580 | |||
| b44d364f0f | |||
| 405b458702 | |||
| 5778616965 | |||
| f5de450c39 | |||
| aebc6972ad | |||
| ef3f5cf4bb | |||
| 3f51beddb6 | |||
| 56c376cd4e | |||
| 19f02d6137 | |||
| 7df18f7d26 | |||
| 84b96ed8f3 | |||
| 85a66a3e70 | |||
| 81391ef101 | |||
| 92040a1bc4 | |||
| 1bb565c6fe | |||
| c4ab60515b | |||
| 8b0915dff1 | |||
| 785b48085d | |||
| 419c3651bf | |||
| 8dac25035f | |||
| b7bdaba694 | |||
| 45dfdb255e | |||
| 5b1448be3e | |||
| 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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/generate/test
|
||||
/debug
|
||||
|
||||
@@ -4,6 +4,7 @@ import "os"
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/sashakoshka/goparse"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/generate"
|
||||
|
||||
func main() {
|
||||
@@ -18,7 +19,7 @@ func main() {
|
||||
input, err := os.Open(source)
|
||||
handleErr(1, err)
|
||||
defer input.Close()
|
||||
protocol, err := generate.ParseReader(input)
|
||||
protocol, err := generate.ParseReader(source, input)
|
||||
handleErr(1, err)
|
||||
|
||||
absDestination, err := filepath.Abs(destination)
|
||||
@@ -30,14 +31,18 @@ func main() {
|
||||
|
||||
output, err := os.Create(destination)
|
||||
handleErr(1, err)
|
||||
err = protocol.Generate(output, packageName)
|
||||
generator := generate.Generator {
|
||||
Output: output,
|
||||
PackageName: packageName,
|
||||
}
|
||||
_, err = generator.Generate(protocol)
|
||||
handleErr(1, err)
|
||||
fmt.Fprintf(os.Stderr, "%s: OK\n", name)
|
||||
}
|
||||
|
||||
func handleErr(code int, err error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package hopp
|
||||
|
||||
import "io"
|
||||
import "net"
|
||||
// import "time"
|
||||
import "time"
|
||||
|
||||
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.
|
||||
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.
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -39,8 +44,6 @@ type Trans interface {
|
||||
// ID returns the transaction ID. This must not change, and it must be
|
||||
// unique within the connection. This method is safe for concurrent use.
|
||||
ID() int64
|
||||
|
||||
// TODO: add methods for setting send and receive deadlines
|
||||
|
||||
// Send sends a message. This method is not safe for concurrent use.
|
||||
Send(method uint16, data []byte) error
|
||||
@@ -57,4 +60,12 @@ type Trans interface {
|
||||
// previously opened through this function will be discarded. This
|
||||
// method is not safe for concurrent use, and neither is its result.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ PDL allows defining a protocol using HOPP and TAPE.
|
||||
| []\<TYPE\> | OTA | * | Array of any type[^1]
|
||||
| Table | KTV | * | Table with undefined schema
|
||||
| {...} | KTV | * | Table with defined schema
|
||||
| Any | * | * | Value of an undefined type
|
||||
|
||||
[^1]: Excluding SI and SBA. I5 and U5 cannot be used in an array, but String and
|
||||
Buffer are simply forced to use their "long" variant.
|
||||
|
||||
@@ -75,16 +75,16 @@ connection. Thus, the value may range from 0 to 31 if unsigned, and from -16 to
|
||||
17 if signed.
|
||||
|
||||
#### Large Integer (LI)
|
||||
LI encodes an integer of up to 256 bits, which are stored in the payload. The CN
|
||||
determine the length of the payload in bytes. The integer is big-endian. Whether
|
||||
LI encodes an integer of up to 256 bits, which are stored in the payload. The
|
||||
length of the payload (in bytes) is CN + 1. The integer is big-endian. Whether
|
||||
the payload is interpreted as unsigned or as signed two's complement is semantic
|
||||
information and must be agreed upon by both sides of the connection. Thus, the
|
||||
value may range from 0 to 31 if unsigned, and from -16 to 17 if signed.
|
||||
|
||||
#### Floating Point (FP)
|
||||
FP encodes an IEEE 754 floating point number of up to 256 bits, which are stored
|
||||
in the payload. The CN determines the length of the payload in bytes, and it may
|
||||
only be one of these values: 16, 32, 64, 128, or 256.
|
||||
in the payload. The length of the payload (in bytes) is CN + 1. The only
|
||||
supported bit widths for floats are as follows: 16, 32, 64, 128, and 256.
|
||||
|
||||
#### Small Byte Array (SBA)
|
||||
SBA encodes an array of up to 32 bytes, which are stored in the paylod. The
|
||||
@@ -98,15 +98,16 @@ in bytes is determined by the CN.
|
||||
#### One-Tag Array (OTA)
|
||||
OTA encodes an array of up to 2^256 items, which are stored in the payload after
|
||||
the length field and the item tag, where the length field comes first. Each item
|
||||
must be the same length, as they all share the same tag. The length of the data
|
||||
length field in bytes is determined by the CN.
|
||||
must be the same length, as they all share the same tag. The length of the
|
||||
length field (in bytes) is CN + 1.
|
||||
|
||||
#### Key-Tag-Value Table (KTV)
|
||||
KTV encodes a table of up to 2^256 key/value pairs, which are stored in the
|
||||
payload after the length field. The pairs themselves consist of a 16-bit
|
||||
unsigned big-endian key followed by a tag and then the payload. Pair values can
|
||||
be of different types and sizes. The order of the pairs is not significant and
|
||||
should never be treated as such.
|
||||
should never be treated as such. The length of the length field (in bytes) is
|
||||
CN + 1.
|
||||
|
||||
## Transports
|
||||
A transport is a protocol that HOPP connections can run on top of. HOPP
|
||||
|
||||
1
error.go
1
error.go
@@ -9,6 +9,7 @@ type Error string; const (
|
||||
ErrIntegerOverflow Error = "integer overflow"
|
||||
ErrMessageMalformed Error = "message is malformed"
|
||||
ErrTablePairMissing Error = "required table pair is missing"
|
||||
ErrWrongBufferLength Error = "wrong buffer length"
|
||||
)
|
||||
|
||||
// Error implements the error interface.
|
||||
|
||||
@@ -24,7 +24,7 @@ const preamble = `
|
||||
|
||||
const static = `
|
||||
// Table is a KTV table with an undefined schema.
|
||||
type Table map[uint16] any
|
||||
type Table = map[uint16] any
|
||||
|
||||
// Message is any message that can be sent along this protocol.
|
||||
type Message interface {
|
||||
@@ -41,8 +41,8 @@ type Message interface {
|
||||
// the destination tag must come from the same (or hash-equivalent) PDL type.
|
||||
func canAssign(destination, source tape.Tag) bool {
|
||||
if destination.Is(source) { return true }
|
||||
if (destination == tape.SBA || destination == tape.LBA) &&
|
||||
(source == tape.SBA || source == tape.LBA) {
|
||||
if (destination.Is(tape.SBA) || destination.Is(tape.LBA)) &&
|
||||
(source.Is(tape.SBA) || source.Is(tape.LBA)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -235,17 +235,13 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
|
||||
this.resolveMessageName(message.Name))
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("tag := ")
|
||||
tagVar, nn, err := this.generateTag(message.Type, "(*this)")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateTag(message.Type, "(*this)")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.println()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("nn, err := encoder.WriteTag(tag)\n")
|
||||
nn, err = this.iprintf("nn, err := encoder.WriteTag(%s)\n", tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateEncodeValue(message.Type, "(*this)", "tag")
|
||||
nn, err = this.generateEncodeValue(message.Type, "(*this)", tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("return n, nil\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -317,13 +313,22 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
if typ.Signed {
|
||||
prefix = "WriteInt"
|
||||
}
|
||||
nn, err := this.iprintf("nn, err = encoder.%s%d(%s)\n", prefix, typ.Bits, valueSource)
|
||||
nn, err := this.iprintf("nn, err = encoder.%s%d(", prefix, typ.Bits)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateType(typ) // TODO: cast like this for
|
||||
// every type
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.printf("(%s))\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeFloat:
|
||||
// FP: <value: FloatN>
|
||||
nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(%s)\n", typ.Bits, valueSource)
|
||||
nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(", typ.Bits)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateType(typ)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.printf("(%s))\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -334,7 +339,14 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
case TypeBuffer:
|
||||
// SBA: <data: U8>*
|
||||
// LBA: <length: UN> <data: U8>*
|
||||
nn, err := this.iprintf("if %s.Is(tape.LBA) {\n", tagSource)
|
||||
nn, err := this.iprintf("if len(%s) > tape.MaxStructureLength {\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("if %s.Is(tape.LBA) {\n", tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf(
|
||||
@@ -346,15 +358,21 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
|
||||
nn, err = this.iprintf("nn, err = encoder.Write([]byte(%s))\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeArray:
|
||||
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||
nn, err := this.iprintf(
|
||||
"nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN())\n",
|
||||
nn, err := this.iprintf("if len(%s) > tape.MaxStructureLength {\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf(
|
||||
"nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN() + 1)\n",
|
||||
valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
@@ -376,15 +394,11 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
this.push()
|
||||
nn, err = this.iprintf("_ = item\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("tag := ")
|
||||
tagVar, nn, err := this.generateTag(typ.Element, "item")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateTag(typ.Element, "item")
|
||||
nn, err = this.iprintf("if %s.Is(tape.SBA) { continue }\n", tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.println()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("if tag.Is(tape.SBA) { continue }\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("if tag.CN() > itemTag.CN() { itemTag = tag }\n")
|
||||
nn, err = this.iprintf("if %s.CN() > itemTag.CN() { itemTag = %s }\n", tagVar, tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
@@ -393,8 +407,8 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("nn, err = encoder.WriteTag(itemTag)\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("for _, item := range %s {\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
@@ -408,7 +422,14 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeTable:
|
||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||
nn, err := this.iprintf(
|
||||
nn, err := this.iprintf("if len(%s) > tape.MaxStructureLength {\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf(
|
||||
"nn, err = tape.EncodeAny(encoder, %s, %s)\n",
|
||||
valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -416,8 +437,15 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeTableDefined:
|
||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||
nn, err := this.iprintf(
|
||||
"nn, err = encoder.WriteUintN(%d, %s.CN())\n",
|
||||
nn, err := this.iprintf("if %d > tape.MaxStructureLength {\n", len(typ.Fields))
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf(
|
||||
"nn, err = encoder.WriteUintN(%d, %s.CN() + 1)\n",
|
||||
len(typ.Fields), tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
@@ -425,25 +453,19 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
nn, err = this.iprintf("{\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("var tag tape.Tag\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
for key, field := range typ.Fields {
|
||||
nn, err = this.iprintf("nn, err = encoder.WriteUint16(0x%04X)\n", key)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("tag = ")
|
||||
n += nn; if err != nil { return n, err }
|
||||
fieldSource := fmt.Sprintf("%s.%s", valueSource, field.Name)
|
||||
nn, err = this.generateTag(field.Type, fieldSource)
|
||||
tagVar, nn, err := this.generateTag(field.Type, fieldSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.println()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("nn, err = encoder.WriteUint8(uint8(tag))\n")
|
||||
nn, err = this.iprintf("nn, err = encoder.WriteUint8(uint8(%s))\n", tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateEncodeValue(field.Type, fieldSource, "tag")
|
||||
nn, err = this.generateEncodeValue(field.Type, fieldSource, tagVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
this.pop()
|
||||
@@ -455,6 +477,12 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeAny:
|
||||
// WHATEVER: [WHATEVER]
|
||||
nn, err := this.iprintf("nn, err = tape.EncodeAny(encoder, %s, %s)\n", valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", typ))
|
||||
}
|
||||
@@ -482,24 +510,57 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
||||
// LI/LSI: <value: IntN>
|
||||
if typ.Bits <= 5 {
|
||||
// SI stores the value in the tag
|
||||
nn, err := this.iprintf("*%s = uint8(%s.CN())\n", valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if typeName == "" {
|
||||
nn, err := this.iprintf("*%s = uint8(%s.CN())\n", valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
nn, err := this.iprintf("*%s = %s(%s.CN())\n", valueSource, typeName, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
break
|
||||
}
|
||||
prefix := "ReadUint"
|
||||
if typ.Signed {
|
||||
prefix = "ReadInt"
|
||||
}
|
||||
nn, err := this.iprintf("*%s, nn, err = decoder.%s%d()\n", valueSource, prefix, typ.Bits)
|
||||
destinationVar := this.newTemporaryVar("destination")
|
||||
nn, err := this.iprintf("var %s ", destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateType(typ)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.print("\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.%s%d()\n", destinationVar, prefix, typ.Bits)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if typeName == "" {
|
||||
nn, err := this.iprintf("*%s = %s\n", valueSource, destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
nn, err := this.iprintf("*%s = %s(%s)\n", valueSource, typeName, destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
case TypeFloat:
|
||||
// FP: <value: FloatN>
|
||||
nn, err := this.iprintf("*%s, nn, err = decoder.ReadFloat%d()\n", valueSource, typ.Bits)
|
||||
destinationVar := this.newTemporaryVar("destination")
|
||||
nn, err := this.iprintf("var %s ", destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateType(typ)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.print("\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.ReadFloat%d()\n", destinationVar, typ.Bits)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if typeName == "" {
|
||||
nn, err := this.iprintf("*%s = %s\n", valueSource, destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
nn, err := this.iprintf("*%s = %s(%s)\n", valueSource, typeName, destinationVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
case TypeString, TypeBuffer:
|
||||
// SBA: <data: U8>*
|
||||
// LBA: <length: UN> <data: U8>*
|
||||
@@ -526,17 +587,29 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("buffer := make([]byte, int(%s))\n", lengthVar)
|
||||
nn, err = this.iprintf("if %s > uint64(tape.MaxStructureLength) {\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("buffer := make([]byte, %s)\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("nn, err = decoder.Read(buffer)\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if _, ok := typ.(TypeString); ok {
|
||||
nn, err = this.iprintf("*%s = string(buffer)\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if typeName == "" {
|
||||
if _, ok := typ.(TypeString); ok {
|
||||
nn, err = this.iprintf("*%s = string(buffer)\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
nn, err = this.iprintf("*%s = buffer\n", valueSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
} else {
|
||||
nn, err = this.iprintf("*%s = buffer\n", valueSource)
|
||||
nn, err = this.iprintf("*%s = %s(buffer)\n", valueSource, typeName)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
case TypeArray:
|
||||
@@ -546,7 +619,7 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
||||
case TypeTable:
|
||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||
nn, err := this.iprintf(
|
||||
"nn, err = tape.DecodeAny(decoder, %s, %s)\n",
|
||||
"nn, err = tape.DecodeAnyInto(decoder, %s, %s)\n",
|
||||
valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
@@ -561,6 +634,12 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeAny:
|
||||
// WHATEVER: [WHATEVER]
|
||||
nn, err := this.iprintf("*%s, nn, err = tape.DecodeAny(decoder, %s)\n", valueSource, tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", typ))
|
||||
}
|
||||
@@ -623,7 +702,14 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
||||
lengthVar := this.newTemporaryVar("length")
|
||||
nn, err := this.iprintf("var %s uint64\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()))\n", lengthVar)
|
||||
nn, err = this.iprintf("if %s > uint64(tape.MaxStructureLength) {\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -692,7 +778,14 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
||||
lengthVar := this.newTemporaryVar("length")
|
||||
nn, err := this.iprintf("var %s uint64\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()))\n", lengthVar)
|
||||
nn, err = this.iprintf("if %s > uint64(tape.MaxStructureLength) {\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("return n, tape.ErrTooLong\n")
|
||||
this.pop()
|
||||
nn, err = this.iprintf("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateErrorCheck()
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -704,7 +797,7 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
||||
// problems
|
||||
|
||||
// read fields
|
||||
nn, err = this.iprintf("for _ = range int(%s) {\n", lengthVar)
|
||||
nn, err = this.iprintf("for _ = range %s {\n", lengthVar)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
// read field header
|
||||
@@ -829,49 +922,60 @@ func (this *Generator) generateErrorCheck() (n int, err error) {
|
||||
return this.iprintf("n += nn; if err != nil { return n, err }\n")
|
||||
}
|
||||
|
||||
func (this *Generator) generateBareErrorCheck() (n int, err error) {
|
||||
return this.iprintf("if err != nil { return n, err }\n")
|
||||
}
|
||||
|
||||
// generateTag generates the preferred TN and CN for the given type and value.
|
||||
// The generated code is INLINE.
|
||||
func (this *Generator) generateTag(typ Type, source string) (n int, err error) {
|
||||
// The generated code is a BLOCK.
|
||||
func (this *Generator) generateTag(typ Type, source string) (tagVar string, n int, err error) {
|
||||
tagVar = this.newTemporaryVar("tag")
|
||||
switch typ := typ.(type) {
|
||||
case TypeInt:
|
||||
if typ.Bits <= 5 {
|
||||
nn, err := this.printf("tape.SI.WithCN(int(%s))", source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.SI.WithCN(int(%s))\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
} else if typ.Signed {
|
||||
nn, err := this.printf("tape.LSI.WithCN(%d)", bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.LSI.WithCN(%d)\n", tagVar, bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
} else {
|
||||
nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.LI.WithCN(%d)\n", tagVar, bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
}
|
||||
case TypeFloat:
|
||||
nn, err := this.printf("tape.FP.WithCN(%d)", bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.FP.WithCN(%d)\n", tagVar, bitsToCN(typ.Bits))
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeString:
|
||||
nn, err := this.printf("tape.StringTag(%s)", source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.StringTag(string(%s))\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeBuffer:
|
||||
nn, err := this.printf("tape.BufferTag(%s)", source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.BufferTag([]byte(%s))\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeArray:
|
||||
nn, err := this.printf("tape.OTA.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.OTA.WithCN(tape.IntBytes(uint64(len(%s))) - 1)\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeTable:
|
||||
nn, err := this.printf("tape.KTV.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.KTV.WithCN(tape.IntBytes(uint64(len(%s))) - 1)\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeTableDefined:
|
||||
nn, err := this.printf("tape.KTV.WithCN(%d)", tape.IntBytes(uint64(len(typ.Fields))))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf("%s := tape.KTV.WithCN(%d)\n", tagVar, tape.IntBytes(uint64(len(typ.Fields))) - 1)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
case TypeNamed:
|
||||
resolved, err := this.resolveTypeName(typ.Name)
|
||||
if err != nil { return n, err }
|
||||
nn, err := this.generateTag(resolved, source)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if err != nil { return tagVar, n, err }
|
||||
subTagVar, nn, err := this.generateTag(resolved, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
tagVar = subTagVar
|
||||
case TypeAny:
|
||||
nn, err := this.iprintf("%s, err := tape.TagAny(%s)\n", tagVar, source)
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
nn, err = this.generateBareErrorCheck()
|
||||
n += nn; if err != nil { return tagVar, n, err }
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", typ))
|
||||
}
|
||||
|
||||
return n, nil
|
||||
return tagVar, n, nil
|
||||
}
|
||||
|
||||
// generateTN generates the appropriate TN for the given type. The generated
|
||||
@@ -914,6 +1018,8 @@ func (this *Generator) generateTN(typ Type) (n int, err error) {
|
||||
if err != nil { return n, err }
|
||||
nn, err := this.generateTN(resolved)
|
||||
n += nn; if err != nil { return n, err }
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", typ))
|
||||
}
|
||||
|
||||
return n, nil
|
||||
@@ -968,6 +1074,11 @@ func (this *Generator) generateType(typ Type) (n int, err error) {
|
||||
case TypeNamed:
|
||||
nn, err := this.print(typ.Name)
|
||||
n += nn; if err != nil { return n, err }
|
||||
case TypeAny:
|
||||
nn, err := this.print("any")
|
||||
n += nn; if err != nil { return n, err }
|
||||
default:
|
||||
panic(fmt.Errorf("unknown type: %T", typ))
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -997,12 +1108,17 @@ func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, er
|
||||
// by tagSource can be assigned to a Go destination generated from typ. The
|
||||
// generated code is INLINE.
|
||||
func (this *Generator) generateCanAssign(typ Type, tagSource string) (n int, err error) {
|
||||
nn, err := this.printf("canAssign(")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateTN(typ)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.printf(", %s)", tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if _, ok := typ.(TypeAny); ok {
|
||||
nn, err := this.printf("true")
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
nn, err := this.printf("canAssign(")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.generateTN(typ)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.printf(", %s)", tagSource)
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,151 +1,15 @@
|
||||
package generate
|
||||
|
||||
// import "fmt"
|
||||
import "strings"
|
||||
import "testing"
|
||||
import "git.tebibyte.media/sashakoshka/goparse"
|
||||
|
||||
var testGenerateCorrect =
|
||||
`package protocol
|
||||
// TODO: once everything has been ironed out, test that the public API of the
|
||||
// generator is equal to something specific
|
||||
|
||||
/* # Do not edit this package by hand!
|
||||
*
|
||||
* This file was automatically generated by the Holanet PDL compiler. The
|
||||
* source file is located at input.pdl
|
||||
* Please edit that file instead, and re-compile it to this location.
|
||||
*
|
||||
* HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
|
||||
*/
|
||||
var exampleProtocol = defaultProtocol()
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
|
||||
// Table is a KTV table with an undefined schema.
|
||||
type Table map[uint16] any
|
||||
|
||||
// Message is any message that can be sent along this protocol.
|
||||
type Message interface {
|
||||
tape.Encodable
|
||||
tape.Decodable
|
||||
|
||||
// Method returns the method code of the message.
|
||||
Method() uint16
|
||||
}
|
||||
|
||||
// User represents the protocol data type User.
|
||||
type User struct {
|
||||
Name string
|
||||
Bio string
|
||||
Followers uint32
|
||||
}
|
||||
|
||||
// EncodeValue encodes the value of this type without the tag. The value is
|
||||
// encoded according to the parameters specified by the tag, if possible.
|
||||
func (this *User) EncodeValue(encoder *tape.Encoder) (n int, err error) {
|
||||
nn, err := tape.WriteTableHeader(2)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := tape.WriteString(encoder, this.Name)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := encoder.WriteUint16(0x0001)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := tape.WriteString(encoder, this.Bio)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode replaces the data in this User with information from the decoder.
|
||||
func (this *User) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
pull, nn, err := tape.ReadTableHeader(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
|
||||
for {
|
||||
key, tag, end, nn, err := pull()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if end { break }
|
||||
|
||||
switch key {
|
||||
case 0x0000:
|
||||
value, nn, err := tape.ReadString(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.Name = value
|
||||
case 0x0001:
|
||||
value, nn, err := tape.ReadString(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.Bio = value
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MessageConnect represents the protocol message M0000 Connect.
|
||||
type MessageConnect struct {
|
||||
Name string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Method returns the method code, M0000.
|
||||
func (this *MessageConnect) Method() uint16 {
|
||||
return 0x0000
|
||||
}
|
||||
|
||||
// Encode encodes the message to the encoder.
|
||||
func (this *MessageConnect) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
nn, err := tape.WriteTableHeader(2)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := tape.WriteString(encoder, this.Name)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := encoder.WriteUint16(0x0001)
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := tape.WriteString(encoder, this.Password)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode replaces the data in this message with information from the decoder.
|
||||
func (this *MessageConnect) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
pull, nn, err := tape.ReadTableHeader(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
|
||||
for {
|
||||
key, tag, end, nn, err := pull()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if end { break }
|
||||
|
||||
switch key {
|
||||
case 0x0000:
|
||||
value, nn, err := tape.ReadString(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.Name = value
|
||||
case 0x0001:
|
||||
value, nn, err := tape.ReadString(decoder)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.Password = value
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// MessageUserList represents the protocol message M0001 UserList.
|
||||
type MessageUserList struct {
|
||||
Users []User
|
||||
}
|
||||
|
||||
// Method returns the method code, M0001.
|
||||
func (this *MessageUserList) Method() uint16 {
|
||||
return 0x0001
|
||||
}
|
||||
|
||||
// TODO methods
|
||||
`
|
||||
|
||||
func TestGenerate(test *testing.T) {
|
||||
protocol := defaultProtocol()
|
||||
protocol.Messages[0x0000] = Message {
|
||||
func init() {
|
||||
exampleProtocol.Messages[0x0000] = Message {
|
||||
Name: "Connect",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
@@ -154,7 +18,7 @@ func TestGenerate(test *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.Messages[0x0001] = Message {
|
||||
exampleProtocol.Messages[0x0001] = Message {
|
||||
Name: "UserList",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
@@ -162,59 +26,7 @@ func TestGenerate(test *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.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 } },
|
||||
},
|
||||
}
|
||||
|
||||
correct := testGenerateCorrect
|
||||
|
||||
builder := strings.Builder { }
|
||||
generator := Generator { Output: &builder }
|
||||
/* TODO test n: */ _, err := generator.Generate(&protocol)
|
||||
if err != nil { test.Fatal(parse.Format(err)) }
|
||||
got := builder.String()
|
||||
|
||||
test.Log("CORRECT:")
|
||||
test.Log(correct)
|
||||
test.Log("GOT:")
|
||||
test.Log(got)
|
||||
|
||||
if correct != got {
|
||||
test.Error("not equal")
|
||||
for index := range min(len(correct), len(got)) {
|
||||
if correct[index] == got[index] { continue }
|
||||
test.Log("C:", correct[max(0, index - 8):min(len(correct), index + 8)])
|
||||
test.Log("G:", got[max(0, index - 8):min(len(got), index + 8)])
|
||||
break
|
||||
}
|
||||
test.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRun(test *testing.T) {
|
||||
protocol := defaultProtocol()
|
||||
protocol.Messages[0x0000] = Message {
|
||||
Name: "Connect",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.Messages[0x0001] = Message {
|
||||
Name: "UserList",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } },
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.Messages[0x0002] = Message {
|
||||
exampleProtocol.Messages[0x0002] = Message {
|
||||
Name: "Pulse",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
@@ -226,11 +38,11 @@ func TestGenerateRun(test *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.Messages[0x0003] = Message {
|
||||
exampleProtocol.Messages[0x0003] = Message {
|
||||
Name: "NestedArray",
|
||||
Type: TypeArray { Element: TypeArray { Element: TypeInt { Bits: 8 } } },
|
||||
}
|
||||
protocol.Messages[0x0004] = Message {
|
||||
exampleProtocol.Messages[0x0004] = Message {
|
||||
Name: "Integers",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
@@ -250,25 +62,48 @@ func TestGenerateRun(test *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
protocol.Types["User"] = TypeTableDefined {
|
||||
exampleProtocol.Messages[0x0005] = Message {
|
||||
Name: "Dynamic",
|
||||
Type: TypeTableDefined {
|
||||
Fields: map[uint16] Field {
|
||||
0x0000: Field { Name: "AU8", Type: TypeAny { } },
|
||||
0x0001: Field { Name: "AU16", Type: TypeAny { } },
|
||||
0x0002: Field { Name: "AU32", Type: TypeAny { } },
|
||||
0x0003: Field { Name: "AU64", Type: TypeAny { } },
|
||||
0x0004: Field { Name: "AI8", Type: TypeAny { } },
|
||||
0x0005: Field { Name: "AI16", Type: TypeAny { } },
|
||||
0x0006: Field { Name: "AI32", Type: TypeAny { } },
|
||||
0x0007: Field { Name: "AI64", Type: TypeAny { } },
|
||||
0x0008: Field { Name: "AF32", Type: TypeAny { } },
|
||||
0x0009: Field { Name: "AF64", Type: TypeAny { } },
|
||||
0x000A: Field { Name: "AString", Type: TypeAny { } },
|
||||
0x000B: Field { Name: "AArray", Type: TypeAny { } },
|
||||
0x000C: Field { Name: "ATable", Type: TypeAny { } },
|
||||
0x000D: Field { Name: "T0", Type: TypeTable { } },
|
||||
},
|
||||
},
|
||||
}
|
||||
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 } },
|
||||
},
|
||||
}
|
||||
testGenerateRun(test, &protocol, `
|
||||
}
|
||||
|
||||
func TestGenerateRunEncodeDecode(test *testing.T) {
|
||||
testGenerateRun(test, &exampleProtocol, "encode-decode", `
|
||||
// imports
|
||||
`, `
|
||||
// test case
|
||||
log.Println("MessageConnect")
|
||||
messageConnect := MessageConnect {
|
||||
Name: "rarity",
|
||||
Password: "gems",
|
||||
}
|
||||
testEncode(
|
||||
testEncodeDecode(
|
||||
&messageConnect,
|
||||
tu.S(0xE1, 0x02).AddVar(
|
||||
tu.S(0xE0, 0x02).AddVar(
|
||||
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||
[]byte { 0x00, 0x01, 0x84, 'g', 'e', 'm', 's' },
|
||||
))
|
||||
@@ -292,10 +127,10 @@ func TestGenerateRun(test *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
testEncode(
|
||||
testEncodeDecode(
|
||||
&messageUserList,
|
||||
tu.S(0xE1, 0x01, 0x00, 0x00,
|
||||
0xC1, 0x03, 0xE1,
|
||||
tu.S(0xE0, 0x01, 0x00, 0x00,
|
||||
0xC0, 0x03, 0xE0,
|
||||
).Add(0x03).AddVar(
|
||||
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||
[]byte { 0x00, 0x01, 0x87, 'a', 's', 'd', 'j', 'a', 'd', 's' },
|
||||
@@ -314,13 +149,13 @@ func TestGenerateRun(test *testing.T) {
|
||||
messagePulse := MessagePulse {
|
||||
Index: 9,
|
||||
Offset: -0x3521,
|
||||
X: 45.389,
|
||||
X: 45.375,
|
||||
Y: 294.1,
|
||||
Z: 384729384.234892034,
|
||||
}
|
||||
testEncode(
|
||||
testEncodeDecode(
|
||||
&messagePulse,
|
||||
tu.S(0xE1, 0x05).AddVar(
|
||||
tu.S(0xE0, 0x05).AddVar(
|
||||
[]byte { 0x00, 0x00, 0x09 },
|
||||
[]byte { 0x00, 0x01, 0x41, 0xCA, 0xDF },
|
||||
[]byte { 0x00, 0x02, 0x61, 0x51, 0xAC },
|
||||
@@ -339,9 +174,9 @@ func TestGenerateRun(test *testing.T) {
|
||||
uint8s(6),
|
||||
uint8s(35),
|
||||
}
|
||||
testEncode(
|
||||
testEncodeDecode(
|
||||
&messageNestedArray,
|
||||
tu.S(0xC1, 0x02, 0xC1,
|
||||
tu.S(0xC0, 0x02, 0xC0,
|
||||
0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||
35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||
0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
|
||||
@@ -365,9 +200,9 @@ func TestGenerateRun(test *testing.T) {
|
||||
NI32: -0x10E134C9,
|
||||
NI64: -0x639109BC10E134C9,
|
||||
}
|
||||
testEncode(
|
||||
testEncodeDecode(
|
||||
&messageIntegers,
|
||||
tu.S(0xE1, 13).AddVar(
|
||||
tu.S(0xE0, 13).AddVar(
|
||||
[]byte { 0x00, 0x00, 0x13 },
|
||||
[]byte { 0x00, 0x01, 0x20, 0xC9 },
|
||||
[]byte { 0x00, 0x02, 0x21, 0x34, 0xC9 },
|
||||
@@ -382,5 +217,237 @@ func TestGenerateRun(test *testing.T) {
|
||||
[]byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||
[]byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||
))
|
||||
log.Println("MessageDynamic")
|
||||
messageDynamic := MessageDynamic {
|
||||
AU8: uint8(0x23),
|
||||
AU16: uint16(0x3247),
|
||||
AU32: uint32(0x87324523),
|
||||
AU64: uint64(0x3284029034098234),
|
||||
AI8: int8(0x23),
|
||||
AI16: int16(0x3247),
|
||||
AI32: int32(0x57324523),
|
||||
AI64: int64(0x3284029034098234),
|
||||
AF32: float32(2342.2378),
|
||||
AF64: float64(324.8899992),
|
||||
AString: "fox bed",
|
||||
AArray: []int16 { 0x7, 0x6, 0x5, 0x4 },
|
||||
ATable: map[uint16] any {
|
||||
0x0001: int8(0x8),
|
||||
0x0002: float64(4.4),
|
||||
},
|
||||
T0: map[uint16] any {
|
||||
0x0001: float32(489.5),
|
||||
0x0002: "hi",
|
||||
0x0003: uint16(0x3992),
|
||||
},
|
||||
}
|
||||
testEncodeDecode(
|
||||
&messageDynamic,
|
||||
tu.S(0xE0, 14).AddVar(
|
||||
[]byte { 0x00, 0x00, 0x20, 0x23 },
|
||||
[]byte { 0x00, 0x01, 0x21, 0x32, 0x47 },
|
||||
[]byte { 0x00, 0x02, 0x23, 0x87, 0x32, 0x45, 0x23 },
|
||||
[]byte { 0x00, 0x03, 0x27, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34 },
|
||||
[]byte { 0x00, 0x04, 0x40, 0x23 },
|
||||
[]byte { 0x00, 0x05, 0x41, 0x32, 0x47 },
|
||||
[]byte { 0x00, 0x06, 0x43, 0x57, 0x32, 0x45, 0x23 },
|
||||
[]byte { 0x00, 0x07, 0x47, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34 },
|
||||
[]byte { 0x00, 0x08, 0x63, 0x45, 0x12, 0x63, 0xCE },
|
||||
[]byte { 0x00, 0x09, 0x67, 0x40, 0x74, 0x4E, 0x3D, 0x6F, 0xCD, 0x17, 0x75 },
|
||||
[]byte { 0x00, 0x0A, 0x87, 'f', 'o', 'x', ' ', 'b', 'e', 'd' },
|
||||
[]byte { 0x00, 0x0B, 0xC0, 0x04, 0x41,
|
||||
0x00, 0x07,
|
||||
0x00, 0x06,
|
||||
0x00, 0x05,
|
||||
0x00, 0x04 },
|
||||
[]byte { 0x00, 0x0C, 0xE0, 0x02,
|
||||
0x00, 0x01, 0x40, 0x08,
|
||||
0x00, 0x02, 0x67, 0x40, 0x11, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A },
|
||||
[]byte { 0x00, 0x0D, 0xE0, 0x03, // ERR
|
||||
0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00,
|
||||
0x00, 0x02, 0x82, 'h', 'i',
|
||||
0x00, 0x03, 0x21, 0x39, 0x92 },
|
||||
))
|
||||
`)
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import "os/exec"
|
||||
import "testing"
|
||||
import "path/filepath"
|
||||
|
||||
func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCase string) {
|
||||
func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCase string) {
|
||||
// reset data directory
|
||||
dir := "test/generate-run"
|
||||
dir := filepath.Join("test", title)
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil { test.Fatal(err) }
|
||||
err = os.MkdirAll(dir, 0750)
|
||||
@@ -34,6 +34,7 @@ func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCa
|
||||
imports = `
|
||||
import "log"
|
||||
import "bytes"
|
||||
import "reflect"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
` + imports
|
||||
@@ -56,13 +57,70 @@ func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCa
|
||||
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: ", tu.Describe(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: ", tu.Describe(destination))
|
||||
log.Println("correct:", tu.Describe(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", "./generate/test/generate-run")
|
||||
command := exec.Command("go", "run", "./" + filepath.Join("generate", dir))
|
||||
workingDirAbs, err := filepath.Abs("..")
|
||||
if err != nil { test.Fatal(err) }
|
||||
command.Dir = workingDirAbs
|
||||
|
||||
@@ -25,8 +25,8 @@ func defaultProtocol() Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
func ParseReader(reader io.Reader) (*Protocol, error) {
|
||||
lx, err := Lex("test.pdl", reader)
|
||||
func ParseReader(fileName string, reader io.Reader) (*Protocol, error) {
|
||||
lx, err := Lex(fileName, reader)
|
||||
if err != nil { return nil, err }
|
||||
return Parse(lx)
|
||||
}
|
||||
@@ -116,6 +116,7 @@ func (this *parser) parseType() (Type, error) {
|
||||
case "String": return TypeString { }, this.Next()
|
||||
case "Buffer": return TypeBuffer { }, this.Next()
|
||||
case "Table": return TypeTable { }, this.Next()
|
||||
case "Any": return TypeAny { }, this.Next()
|
||||
}
|
||||
return this.parseTypeNamed()
|
||||
case TokenLBracket:
|
||||
|
||||
@@ -31,9 +31,10 @@ func TestParse(test *testing.T) {
|
||||
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||
},
|
||||
}
|
||||
correct.Types["Anything"] = TypeAny { }
|
||||
test.Log("CORRECT:", &correct)
|
||||
|
||||
got, err := ParseReader(strings.NewReader(`
|
||||
got, err := ParseReader("test.pdl", strings.NewReader(`
|
||||
M0000 Connect {
|
||||
0000 Name String,
|
||||
0001 Password String,
|
||||
@@ -48,6 +49,8 @@ func TestParse(test *testing.T) {
|
||||
0001 Bio String,
|
||||
0002 Followers U32,
|
||||
}
|
||||
|
||||
Anything Any
|
||||
`))
|
||||
if err != nil { test.Fatal(parse.Format(err)) }
|
||||
test.Log("GOT: ", got)
|
||||
|
||||
@@ -99,6 +99,12 @@ func (typ TypeNamed) String() string {
|
||||
return typ.Name
|
||||
}
|
||||
|
||||
type TypeAny struct { }
|
||||
|
||||
func (typ TypeAny) String() string {
|
||||
return "Any"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,9 @@ package testutil
|
||||
|
||||
import "fmt"
|
||||
import "slices"
|
||||
import "strings"
|
||||
import "reflect"
|
||||
import "strings"
|
||||
import "unicode"
|
||||
|
||||
// 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
|
||||
@@ -64,6 +65,18 @@ func (sn Snake) Check(data []byte) (ok bool, n int) {
|
||||
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"
|
||||
@@ -107,7 +120,15 @@ type describer struct {
|
||||
}
|
||||
|
||||
func (this *describer) describe(value reflect.Value) {
|
||||
if !value.IsValid() {
|
||||
this.printf("<invalid>")
|
||||
return
|
||||
}
|
||||
value = reflect.ValueOf(value.Interface())
|
||||
if !value.IsValid() {
|
||||
this.printf("<invalid>")
|
||||
return
|
||||
}
|
||||
switch value.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
this.printf("[\n")
|
||||
@@ -125,12 +146,20 @@ func (this *describer) describe(value reflect.Value) {
|
||||
typ := value.Type()
|
||||
for index := range typ.NumField() {
|
||||
indexBuffer := [1]int { index }
|
||||
this.iprintf("%s: ", typ.Field(index).Name)
|
||||
this.describe(value.FieldByIndex(indexBuffer[:]))
|
||||
field := typ.Field(index)
|
||||
this.iprintf("%s: ", field.Name)
|
||||
for _, char := range field.Name {
|
||||
if unicode.IsUpper(char) {
|
||||
this.describe(value.FieldByIndex(indexBuffer[:]))
|
||||
} else {
|
||||
this.printf("<private>")
|
||||
}
|
||||
break
|
||||
}
|
||||
this.iprintf("\n")
|
||||
}
|
||||
this.indent -= 1
|
||||
this.iprintf("}\n")
|
||||
this.iprintf("}")
|
||||
case reflect.Map:
|
||||
this.printf("map {\n")
|
||||
this.indent += 1
|
||||
@@ -143,7 +172,7 @@ func (this *describer) describe(value reflect.Value) {
|
||||
this.iprintf("\n")
|
||||
}
|
||||
this.indent -= 1
|
||||
this.iprintf("}\n")
|
||||
this.iprintf("}")
|
||||
case reflect.Pointer:
|
||||
this.printf("& ")
|
||||
this.describe(value.Elem())
|
||||
|
||||
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
|
||||
|
||||
import "io"
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "net"
|
||||
import "sync"
|
||||
import "time"
|
||||
import "sync/atomic"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
||||
|
||||
// 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) {
|
||||
this.sizeLimit = limit
|
||||
}
|
||||
@@ -213,6 +218,10 @@ type transA struct {
|
||||
currentWriter io.Closer
|
||||
writeBuffer []byte
|
||||
closed atomic.Bool
|
||||
closeErr error
|
||||
|
||||
deadline *time.Timer
|
||||
deadlineLock sync.Mutex
|
||||
}
|
||||
|
||||
func (this *transA) Close() error {
|
||||
@@ -222,6 +231,11 @@ func (this *transA) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *transA) closeWithError(err error) error {
|
||||
this.closeErr = err
|
||||
return this.Close()
|
||||
}
|
||||
|
||||
func (this *transA) closeDontUnlist() (err error) {
|
||||
// MUST be goroutine safe
|
||||
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) {
|
||||
// if the transaction has been closed, return an io.EOF
|
||||
if this.closed.Load() {
|
||||
return 0, nil, io.EOF
|
||||
// if the transaction has been closed, return an appropriate error.
|
||||
if err := this.errIfClosed(); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// drain previous reader if necessary
|
||||
@@ -290,6 +304,54 @@ func (this *transA) ReceiveReader() (uint16, io.Reader, error) {
|
||||
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 {
|
||||
parent *transA
|
||||
leftover []byte
|
||||
@@ -320,11 +382,7 @@ func (this *readerA) pull() (uint16, error) {
|
||||
// close and return error on failure
|
||||
this.eof = true
|
||||
this.parent.Close()
|
||||
if this.parent.parent.err == nil {
|
||||
return 0, fmt.Errorf("could not receive message: %w", io.EOF)
|
||||
} else {
|
||||
return 0, this.parent.parent.err
|
||||
}
|
||||
return 0, fmt.Errorf("could not receive message: %w", this.parent.bestErr())
|
||||
}
|
||||
|
||||
func (this *readerA) Read(buffer []byte) (int, error) {
|
||||
@@ -406,9 +464,9 @@ func encodeMessageA(
|
||||
return ErrPayloadTooLarge
|
||||
}
|
||||
buffer := make([]byte, 18 + len(data))
|
||||
tape.EncodeI64(buffer[:8], trans)
|
||||
tape.EncodeI16(buffer[8:10], method)
|
||||
tape.EncodeI64(buffer[10:18], uint64(len(data)))
|
||||
encodeI64(buffer[:8], trans)
|
||||
encodeI16(buffer[8:10], method)
|
||||
encodeI64(buffer[10:18], uint64(len(data)))
|
||||
copy(buffer[18:], data)
|
||||
_, err := writer.Write(buffer)
|
||||
return err
|
||||
@@ -427,11 +485,11 @@ func decodeMessageA(
|
||||
headerBuffer := [18]byte { }
|
||||
_, err = io.ReadFull(reader, headerBuffer[:])
|
||||
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 }
|
||||
method, err = tape.DecodeI16[uint16](headerBuffer[8:10])
|
||||
method, err = decodeI16[uint16](headerBuffer[8:10])
|
||||
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 }
|
||||
chunked, size = splitCCBSize(size)
|
||||
if size > uint64(sizeLimit) {
|
||||
|
||||
22
metadaptb.go
22
metadaptb.go
@@ -2,10 +2,10 @@ package hopp
|
||||
|
||||
import "io"
|
||||
import "net"
|
||||
import "time"
|
||||
import "bytes"
|
||||
import "errors"
|
||||
import "context"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
|
||||
// B implements METADAPT-B over a multiplexed stream-oriented transport such as
|
||||
// QUIC.
|
||||
@@ -51,6 +51,10 @@ func (this *b) SetSizeLimit(limit int64) {
|
||||
this.sizeLimit = limit
|
||||
}
|
||||
|
||||
func (this *b) SetDeadline(t time.Time) error {
|
||||
return this.underlying.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (this *b) newTrans(underlying Stream) *transB {
|
||||
return &transB {
|
||||
sizeLimit: this.sizeLimit,
|
||||
@@ -125,6 +129,10 @@ func (this *transB) receiveReader() (uint16, int64, io.Reader, error) {
|
||||
return method, size, data, nil
|
||||
}
|
||||
|
||||
func (this *transB) SetDeadline(t time.Time) error {
|
||||
return this.underlying.SetDeadline(t)
|
||||
}
|
||||
|
||||
type writerB struct {
|
||||
parent *transB
|
||||
buffer bytes.Buffer
|
||||
@@ -150,12 +158,16 @@ type MultiConn interface {
|
||||
AcceptStream(context.Context) (Stream, error)
|
||||
// OpenStream opens a new stream.
|
||||
OpenStream() (Stream, error)
|
||||
// See the documentation for [net.Conn.SetDeadline].
|
||||
SetDeadline(time.Time) error
|
||||
}
|
||||
|
||||
// Stream represents a single stream returned by a [MultiConn].
|
||||
type Stream interface {
|
||||
// See documentation for [net.Conn].
|
||||
io.ReadWriteCloser
|
||||
// See the documentation for [net.Conn.SetDeadline].
|
||||
SetDeadline(time.Time) error
|
||||
// ID returns the stream ID
|
||||
ID() int64
|
||||
}
|
||||
@@ -165,8 +177,8 @@ func encodeMessageB(writer io.Writer, sizeLimit int64, method uint16, data []byt
|
||||
return ErrPayloadTooLarge
|
||||
}
|
||||
buffer := make([]byte, 10 + len(data))
|
||||
tape.EncodeI16(buffer[:2], method)
|
||||
tape.EncodeI64(buffer[2:10], uint64(len(data)))
|
||||
encodeI16(buffer[:2], method)
|
||||
encodeI64(buffer[2:10], uint64(len(data)))
|
||||
copy(buffer[10:], data)
|
||||
_, err := writer.Write(buffer)
|
||||
return err
|
||||
@@ -187,9 +199,9 @@ func decodeMessageB(
|
||||
if errors.Is(err, io.EOF) { return 0, 0, nil, io.ErrUnexpectedEOF }
|
||||
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 }
|
||||
length, err := tape.DecodeI64[uint64](headerBuffer[2:10])
|
||||
length, err := decodeI64[uint64](headerBuffer[2:10])
|
||||
if err != nil { return 0, 0, nil, err }
|
||||
if length > uint64(sizeLimit) {
|
||||
return 0, 0, nil, ErrPayloadTooLarge
|
||||
|
||||
386
tape/dynamic.go
386
tape/dynamic.go
@@ -8,12 +8,31 @@ package tape
|
||||
|
||||
// TODO: test all of these smaller functions individually
|
||||
|
||||
// For an explanation as to why this package always treats LBA/SBA as strings,
|
||||
// refer to https://go.dev/blog/strings:
|
||||
//
|
||||
// It’s important to state right up front that a string holds arbitrary
|
||||
// bytes. It is not required to hold Unicode text, UTF-8 text, or any other
|
||||
// predefined format. As far as the content of a string is concerned, it is
|
||||
// exactly equivalent to a slice of bytes.
|
||||
//
|
||||
// Arbitrary byte slices and blobs won't be as common of a use case as text
|
||||
// data, and if you need that anyway you can just cast it to a byte slice.
|
||||
|
||||
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:
|
||||
//
|
||||
@@ -28,19 +47,28 @@ 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: return EncodeAny(encoder, []byte(reflectValue.String()), tag)
|
||||
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.Float32: return encoder.WriteFloat32(float32(reflectValue.Float()))
|
||||
case reflect.Float64: return encoder.WriteFloat64(float64(reflectValue.Float()))
|
||||
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 }
|
||||
@@ -56,8 +84,13 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
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)
|
||||
}
|
||||
@@ -66,9 +99,10 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
return n, fmt.Errorf("cannot encode type %T", value)
|
||||
}
|
||||
|
||||
// DecodeAny decodes data and places it into destination, which must be a
|
||||
// DecodeAnyInto 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) {
|
||||
// The head of the decoder must be at the start of the payload.
|
||||
func DecodeAnyInto(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)
|
||||
@@ -76,6 +110,17 @@ func DecodeAny(decoder *Decoder, destination any, tag Tag) (n int, err error) {
|
||||
return decodeAny(decoder, reflectDestination.Elem(), tag)
|
||||
}
|
||||
|
||||
// DecodeAny is like [DecodeAnyInto], but it automatically creates the
|
||||
// destination from the tag and data. The head of the decoder must be at the
|
||||
// start of the payload.
|
||||
func DecodeAny(decoder *Decoder, tag Tag) (value any, n int, err error) {
|
||||
destination, err := skeletonPointer(decoder, tag)
|
||||
if err != nil { return nil, n, err }
|
||||
nn, err := decodeAny(decoder, destination.Elem(), tag)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return destination.Elem().Interface(), n, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -83,20 +128,35 @@ type unknownSlicePlaceholder struct { }
|
||||
var unknownSlicePlaceholderType = reflect.TypeOf(unknownSlicePlaceholder { })
|
||||
|
||||
// decodeAny is internal to [DecodeAny]. It takes in an addressable
|
||||
// [reflect.Value] as the destination.
|
||||
// [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. The head of the decoder must be at the start of the
|
||||
// payload.
|
||||
func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
|
||||
errWrongDestinationType := func(expected string) error {
|
||||
panic(fmt.Errorf(
|
||||
// return fmt.Errorf(
|
||||
"expected %s destination, not %v",
|
||||
expected, destination))
|
||||
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. The head of the decoder must be at the start of the payload.
|
||||
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)
|
||||
err = setInt(destination, uint64(tag.CN()))
|
||||
if err != nil { return n, err }
|
||||
setUint(destination, uint64(tag.CN()), 1)
|
||||
case LI:
|
||||
// LI: <value: IntN>
|
||||
nn, err := decodeAndSetUint(decoder, destination, tag.CN() + 1)
|
||||
@@ -111,56 +171,104 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
|
||||
n += nn; if err != nil { return n, err }
|
||||
case SBA:
|
||||
// SBA: <data: U8>*
|
||||
buffer := make([]byte, tag.CN())
|
||||
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 }
|
||||
err = setByteArray(destination, buffer)
|
||||
if err != nil { return n, err }
|
||||
setString(destination, string(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 }
|
||||
err = setByteArray(destination, buffer)
|
||||
if err != nil { return n, err }
|
||||
setString(destination, string(buffer))
|
||||
case OTA:
|
||||
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||
oldDestination := destination
|
||||
if isTypeAny(destination.Type()) {
|
||||
// need a skeleton value if we are assigning to any.
|
||||
value, err := skeletonValue(decoder, tag)
|
||||
if err != nil { return n, err }
|
||||
destination = value
|
||||
}
|
||||
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.Kind() != reflect.Slice {
|
||||
return n, errWrongDestinationType("slice")
|
||||
if destination.Cap() < lengthCast {
|
||||
destination.Grow(lengthCast - destination.Cap())
|
||||
}
|
||||
if destination.Cap() < int(length) {
|
||||
destination.Grow(int(length) - 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
|
||||
}
|
||||
destination.SetLen(int(length))
|
||||
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 err != nil { return n, err }
|
||||
n += nn
|
||||
if _, ok := err.(errCantAssign); ok {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
oldDestination.Set(destination)
|
||||
case KTV:
|
||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||
table := destination
|
||||
if table.Type() != reflect.TypeOf(dummyMap) {
|
||||
return n, errWrongDestinationType("map[uint16] any")
|
||||
}
|
||||
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
table.Clear()
|
||||
for _ = range length {
|
||||
if length > uint64(MaxStructureLength) {
|
||||
return 0, ErrTooLong
|
||||
}
|
||||
lengthCast, err := Uint64ToIntSafe(length)
|
||||
if err != nil { return n, err }
|
||||
|
||||
// im fucking so done dude. im so fucking done. this was
|
||||
// supposed to only run when we need it but i guess it runs all
|
||||
// the time, because when we get a map destination (a valid,
|
||||
// allocated one) we break apart on SetMapIndex because of a nil
|
||||
// map. yeah thats right. a fucking nil map panic. on the map we
|
||||
// just allocated. but running this unconditionally (whether or
|
||||
// not we receive an empty any value) actually makes it fucking
|
||||
// work. go figure().
|
||||
//
|
||||
// (the map allocation functionality in skeletonPointer has been
|
||||
// removed)
|
||||
value := reflect.MakeMapWithSize(reflect.TypeOf(dummyMap), lengthCast)
|
||||
destination.Set(value)
|
||||
destination = value
|
||||
|
||||
destination.Clear()
|
||||
for _ = range lengthCast {
|
||||
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)
|
||||
value, err := skeletonPointer(decoder, itemTag)
|
||||
if err != nil { return n, err }
|
||||
nn, err = decodeAny(decoder, value.Elem(), itemTag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
table.SetMapIndex(reflect.ValueOf(key), value.Elem())
|
||||
destination.SetMapIndex(reflect.ValueOf(key), value.Elem())
|
||||
}
|
||||
default:
|
||||
return n, fmt.Errorf("unknown TN %d", tag.TN())
|
||||
@@ -172,35 +280,42 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
|
||||
// underlying type is unsupported. See [EncodeAny] for a list of supported
|
||||
// types.
|
||||
func TagAny(value any) (Tag, error) {
|
||||
// TODO use reflection for all of this to ignore type names
|
||||
return tagAny(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
func tagAny(reflectValue reflect.Value) (Tag, error) {
|
||||
// primitives
|
||||
switch value := value.(type) {
|
||||
case int: return LSI.WithCN(3), nil
|
||||
case int8: return LSI.WithCN(0), nil
|
||||
case int16: return LSI.WithCN(1), nil
|
||||
case int32: return LSI.WithCN(3), nil
|
||||
case int64: return LSI.WithCN(7), nil
|
||||
case uint: return LI.WithCN(3), nil
|
||||
case uint8: return LI.WithCN(0), nil
|
||||
case uint16: return LI.WithCN(1), nil
|
||||
case uint32: return LI.WithCN(3), nil
|
||||
case uint64: return LI.WithCN(7), nil
|
||||
case string: return bufferLenTag(len(value)), nil
|
||||
case []byte: return bufferLenTag(len(value)), nil
|
||||
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.Float32: return FP.WithCN(3), nil
|
||||
case reflect.Float64: return FP.WithCN(7), nil
|
||||
case reflect.String: return bufferLenTag(reflectValue.Len()), nil
|
||||
}
|
||||
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
|
||||
return bufferLenTag(reflectValue.Len()), nil
|
||||
}
|
||||
|
||||
// aggregates
|
||||
reflectType := reflect.TypeOf(value)
|
||||
reflectType := reflectValue.Type()
|
||||
switch reflectType.Kind() {
|
||||
case reflect.Slice: return OTA.WithCN(IntBytes(uint64(reflect.ValueOf(value).Len())) - 1), nil
|
||||
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(reflect.ValueOf(value).Len())) - 1), nil
|
||||
return KTV.WithCN(IntBytes(uint64(reflectValue.Len())) - 1), nil
|
||||
}
|
||||
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
|
||||
return 0, fmt.Errorf("cannot encode map key %v, key must be uint16", reflectType.Key())
|
||||
}
|
||||
return 0, fmt.Errorf("cannot get tag of type %T", value)
|
||||
return 0, fmt.Errorf("cannot get tag of type %v", reflectType)
|
||||
}
|
||||
|
||||
func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
@@ -209,11 +324,10 @@ func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
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()).Interface())
|
||||
oneTag, err := tagAny(reflect.Zero(reflectType.Elem()))
|
||||
if err != nil { return n, err }
|
||||
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||
item := reflectValue.Index(index).Interface()
|
||||
itemTag, err := TagAny(item)
|
||||
itemTag, err := tagAny(reflectValue.Index(index))
|
||||
if err != nil { return n, err }
|
||||
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
|
||||
}
|
||||
@@ -235,11 +349,12 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
n += nn; if err != nil { return n, err }
|
||||
iter := reflectValue.MapRange()
|
||||
for iter.Next() {
|
||||
key := iter.Key().Interface().(uint16)
|
||||
value := iter.Value().Interface()
|
||||
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(value)
|
||||
itemTag, err := tagAny(reflectValue)
|
||||
if err != nil { return n, err }
|
||||
nn, err = encoder.WriteUint8(uint8(itemTag))
|
||||
n += nn; if err != nil { return n, err }
|
||||
@@ -249,53 +364,119 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func canSet(destination reflect.Type, tag Tag) error {
|
||||
// anything can be assigned to `any`
|
||||
if isTypeAny(destination) {
|
||||
return nil
|
||||
}
|
||||
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.String { return nil }
|
||||
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:
|
||||
cantAssign :=
|
||||
destination.Kind() != reflect.Map ||
|
||||
destination.Key().Kind() != reflect.Uint16 ||
|
||||
!isTypeAny(destination.Elem())
|
||||
if cantAssign {
|
||||
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) error {
|
||||
func setInt(destination reflect.Value, value int64, bytes int) {
|
||||
switch {
|
||||
case destination.CanInt():
|
||||
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
|
||||
case destination.CanUint():
|
||||
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
|
||||
case isTypeAny(destination.Type()):
|
||||
switch {
|
||||
case bytes > 4: destination.Set(reflect.ValueOf(int64(value)))
|
||||
case bytes > 2: destination.Set(reflect.ValueOf(int32(value)))
|
||||
case bytes > 1: destination.Set(reflect.ValueOf(int16(value)))
|
||||
default: destination.Set(reflect.ValueOf(int8(value)))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot assign integer to %T", destination.Interface())
|
||||
panic("setInt called on an unsupported type")
|
||||
}
|
||||
}
|
||||
|
||||
// setUint expects a settable destination.
|
||||
func setUint(destination reflect.Value, value uint64, bytes int) {
|
||||
switch {
|
||||
case destination.CanInt():
|
||||
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
|
||||
case destination.CanUint():
|
||||
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
|
||||
case isTypeAny(destination.Type()):
|
||||
switch {
|
||||
case bytes > 4: destination.Set(reflect.ValueOf(uint64(value)))
|
||||
case bytes > 2: destination.Set(reflect.ValueOf(uint32(value)))
|
||||
case bytes > 1: destination.Set(reflect.ValueOf(uint16(value)))
|
||||
default: destination.Set(reflect.ValueOf(uint8(value)))
|
||||
}
|
||||
default:
|
||||
panic("setUint called on an unsupported type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setFloat expects a settable destination.
|
||||
func setFloat(destination reflect.Value, value float64) error {
|
||||
if !destination.CanFloat() {
|
||||
return fmt.Errorf("cannot assign float to %T", destination.Interface())
|
||||
}
|
||||
func setFloat(destination reflect.Value, value float64) {
|
||||
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// setByteArrayexpects a settable destination.
|
||||
func setByteArray(destination reflect.Value, value []byte) error {
|
||||
typ := destination.Type()
|
||||
if typ.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("cannot assign %T to ", value)
|
||||
}
|
||||
if typ.Elem() != reflect.TypeOf(byte(0)) {
|
||||
return fmt.Errorf("cannot convert %T to *[]byte", value)
|
||||
}
|
||||
func setByteArray(destination reflect.Value, value []byte) {
|
||||
destination.Set(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// setString exepctes a settable destination
|
||||
func setString(destination reflect.Value, value string) {
|
||||
destination.Set(reflect.ValueOf(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 }
|
||||
return n, setInt(destination, value)
|
||||
setInt(destination, value, bytes)
|
||||
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 }
|
||||
return n, setInt(destination, value)
|
||||
setUint(destination, value, bytes)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// decodeAndSetInt expects a settable destination.
|
||||
@@ -304,25 +485,38 @@ func decodeAndSetFloat(decoder *Decoder, destination reflect.Value, bytes int) (
|
||||
case 8:
|
||||
value, nn, err := decoder.ReadFloat64()
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, setFloat(destination, float64(value))
|
||||
setFloat(destination, float64(value))
|
||||
return n, nil
|
||||
case 4:
|
||||
value, nn, err := decoder.ReadFloat32()
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, setFloat(destination, float64(value))
|
||||
setFloat(destination, float64(value))
|
||||
return n, nil
|
||||
}
|
||||
return n, fmt.Errorf("cannot decode float%d", bytes * 8)
|
||||
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().
|
||||
// skeletonValue returns an addressable value. It can be set directly. The head
|
||||
// of the decoder must be at the start of the payload when calling.
|
||||
func skeletonValue(decoder *Decoder, tag Tag) (reflect.Value, error) {
|
||||
ptr, err := skeletonPointer(decoder, tag)
|
||||
if err != nil { return reflect.Value { }, err }
|
||||
return ptr.Elem(), nil
|
||||
}
|
||||
|
||||
// skeletonPointer returns a pointer value. In order for it to be set, it must
|
||||
// be dereferenced using Elem(). The head of the decoder must be at the start of
|
||||
// the payload when calling.
|
||||
func skeletonPointer(decoder *Decoder, tag Tag) (reflect.Value, error) {
|
||||
typ, err := typeOf(decoder, tag)
|
||||
if err != nil { return reflect.Value { }, err }
|
||||
return reflect.New(typ), nil
|
||||
value := reflect.New(typ)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// typeOf returns the type of the current tag being decoded. It does not use up
|
||||
// the decoder, it only peeks.
|
||||
// the decoder, it only peeks. The head of the decoder must be at the start of
|
||||
// the payload when calling.
|
||||
func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
|
||||
switch tag.WithoutCN() {
|
||||
case SI:
|
||||
@@ -349,8 +543,8 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
|
||||
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 SBA: return reflect.TypeOf(""), nil
|
||||
case LBA: return reflect.TypeOf(""), nil
|
||||
case OTA:
|
||||
elemTag, dimension, err := peekSlice(decoder, tag)
|
||||
if err != nil { return nil, err }
|
||||
@@ -366,6 +560,12 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
|
||||
return nil, fmt.Errorf("unknown TN %d", tag.TN())
|
||||
}
|
||||
|
||||
// isTypeAny returns whether the given reflect.Type is an interface with no
|
||||
// methods.
|
||||
func isTypeAny(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Interface && typ.NumMethod() == 0
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -1,11 +1,69 @@
|
||||
package tape
|
||||
|
||||
import "fmt"
|
||||
import "bytes"
|
||||
import "testing"
|
||||
import "reflect"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
|
||||
var samplePayloads = [][]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, 0x24, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
|
||||
},
|
||||
/* map[uint16] any */ []byte {
|
||||
byte(KTV.WithCN(0)), 3,
|
||||
0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00,
|
||||
0x00, 0x02, 0x82, 'h', 'i',
|
||||
0x00, 0x03, 0x21, 0x39, 0x92,
|
||||
},
|
||||
}
|
||||
|
||||
var sampleValues = []any {
|
||||
/* int8 */ int8(0x45),
|
||||
/* int16 */ int16(0x4567),
|
||||
/* int32 */ int32(0x456789AB),
|
||||
/* int64 */ int64(0x456789ABCDEF0123),
|
||||
/* uint5 */ uint8(12),
|
||||
/* uint8 */ uint8(0x45),
|
||||
/* uint16 */ uint16(0x4567),
|
||||
/* uint32 */ uint32(0x456789AB),
|
||||
/* uint64 */ uint64(0x456789ABCDEF0123),
|
||||
/* string */ "pupever",
|
||||
/* []byte */ "blarg",
|
||||
/* []string */ []string {
|
||||
"\x45\x67\x89\xAB\xCD\xEF\x01\x23",
|
||||
"\x11\x11\x11\x11\x11",
|
||||
},
|
||||
/* map[uint16] any */ map[uint16] any {
|
||||
0x0223: int16(0x4567),
|
||||
0x0224: uint32(0x456789AB),
|
||||
},
|
||||
/* map[uint16] any */ map[uint16] any {
|
||||
0x0001: float32(489.5),
|
||||
0x0002: "hi",
|
||||
0x0003: uint16(0x3992),
|
||||
},
|
||||
}
|
||||
|
||||
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) }
|
||||
@@ -23,7 +81,10 @@ func TestEncodeAnyTable(test *testing.T) {
|
||||
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||
0x2345: [][]int16 { []int16 { 0x5 }, []int16 { 0x17, -0xAAA } },
|
||||
}, KTV.WithCN(0), tu.S(6).AddVar(
|
||||
0x3456: userDefinedInteger(0x3921),
|
||||
0x1F1F: float32(67.26),
|
||||
0x0F0F: float64(5.3),
|
||||
}, KTV.WithCN(0), tu.S(9).AddVar(
|
||||
[]byte {
|
||||
0xF3, 0xB9,
|
||||
byte(LSI.WithCN(3)),
|
||||
@@ -62,23 +123,132 @@ func TestEncodeAnyTable(test *testing.T) {
|
||||
0, 0x17,
|
||||
0xF5, 0x56,
|
||||
},
|
||||
[]byte {
|
||||
0x34, 0x56,
|
||||
byte(LSI.WithCN(1)),
|
||||
0x39, 0x21,
|
||||
},
|
||||
[]byte {
|
||||
0x1F, 0x1F,
|
||||
byte(FP.WithCN(3)),
|
||||
0x42, 0x86, 0x85, 0x1F,
|
||||
},
|
||||
[]byte {
|
||||
0x0F, 0x0F,
|
||||
byte(FP.WithCN(7)),
|
||||
0x40, 0x15, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
|
||||
},
|
||||
))
|
||||
if err != nil { test.Fatal(err) }
|
||||
}
|
||||
|
||||
func TestDecodeWrongType(test *testing.T) {
|
||||
for index, data := range samplePayloads {
|
||||
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 := DecodeAnyInto(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 := DecodeAnyInto(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 && index != 13 {
|
||||
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!"),
|
||||
0x0000: "hi!",
|
||||
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||
0x1F1F: float32(67.26),
|
||||
0x0F0F: float64(5.3),
|
||||
}, nil)
|
||||
if err != nil { test.Fatal(err) }
|
||||
}
|
||||
|
||||
func TestEncodeDecodeAnyDestination(test *testing.T) {
|
||||
var destination any
|
||||
for index, data := range samplePayloads {
|
||||
tag := Tag(data[0])
|
||||
payload := data[1:]
|
||||
test.Logf("data %2d %v [%s]", index, tag, tu.HexBytes(payload))
|
||||
n, err := DecodeAnyInto(NewDecoder(bytes.NewBuffer(payload)), &destination, tag)
|
||||
if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
|
||||
got := destination
|
||||
correct := sampleValues[index]
|
||||
test.Log("got: ", tu.Describe(got))
|
||||
test.Log("correct:", tu.Describe(correct))
|
||||
if !reflect.DeepEqual(got, correct) {
|
||||
test.Fatalf("values not equal")
|
||||
}
|
||||
if n != len(payload) {
|
||||
test.Fatalf("n not equal: %d != %d", n, len(payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeekSlice(test *testing.T) {
|
||||
buffer := bytes.NewBuffer([]byte {
|
||||
2, byte(OTA.WithCN(3)),
|
||||
@@ -138,70 +308,62 @@ func TestPeekSliceOnce(test *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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 TestTagAny(test *testing.T) {
|
||||
cases := [][2]any {
|
||||
[2]any { LSI.WithCN(3), int(9) },
|
||||
[2]any { LSI.WithCN(0), int8(9) },
|
||||
[2]any { LSI.WithCN(1), int16(9) },
|
||||
[2]any { LSI.WithCN(3), int32(9) },
|
||||
[2]any { LSI.WithCN(7), int64(9) },
|
||||
[2]any { LI.WithCN(3), uint(9) },
|
||||
[2]any { LI.WithCN(0), uint8(9) },
|
||||
[2]any { LI.WithCN(1), uint16(9) },
|
||||
[2]any { LI.WithCN(3), uint32(9) },
|
||||
[2]any { LI.WithCN(7), uint64(9) },
|
||||
[2]any { FP.WithCN(3), float32(9) },
|
||||
[2]any { FP.WithCN(7), float64(9) },
|
||||
[2]any { SBA.WithCN(12), "small string" },
|
||||
[2]any { SBA.WithCN(12), []byte("small string") },
|
||||
[2]any { LBA.WithCN(0), "this is a very long string that is long" },
|
||||
[2]any { LBA.WithCN(0), []byte("this is a very long string that is long") },
|
||||
[2]any { LBA.WithCN(1), lipsum },
|
||||
[2]any { OTA.WithCN(0), []int { 1, 2, 3, 4, 5 } },
|
||||
[2]any { OTA.WithCN(0), []string { "1, 2, 3, 4, 5" } },
|
||||
[2]any { KTV.WithCN(0), map[uint16] any {
|
||||
0: 1,
|
||||
1: "wow",
|
||||
2: 10.238,
|
||||
45: -9,
|
||||
9: map[uint16] any { },
|
||||
}},
|
||||
}
|
||||
for _, cas := range cases {
|
||||
test.Log(cas)
|
||||
got, err := TagAny(cas[1])
|
||||
if err != nil { test.Fatal(err) }
|
||||
if correct := cas[0].(Tag); correct != got {
|
||||
test.Fatalf("wrong tag: %v != %v", got, correct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
if ok, n := correctBytes.Check(bytes); !ok {
|
||||
return fmt.Errorf("bytes not equal: %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
|
||||
func TestDecodeAny(test *testing.T) {
|
||||
for index, payload := range samplePayloads {
|
||||
correctValue := sampleValues[index]
|
||||
data := payload[1:]
|
||||
decoder := NewDecoder(bytes.NewBuffer(data))
|
||||
tag := Tag(payload[0])
|
||||
decoded, n, err := DecodeAny(decoder, tag)
|
||||
test.Log("n: ", n)
|
||||
test.Log("tag: ", tag)
|
||||
test.Log("got: ", tu.Describe(decoded))
|
||||
test.Log("correct:", tu.Describe(correctValue))
|
||||
if err != nil { test.Fatal(err) }
|
||||
if !reflect.DeepEqual(decoded, correctValue) {
|
||||
test.Fatal("values not equal")
|
||||
}
|
||||
if n != len(data) {
|
||||
test.Fatalf("n not equal: %d != %d", n, len(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
75
tape/misc_test.go
Normal file
75
tape/misc_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package tape
|
||||
|
||||
import "fmt"
|
||||
import "bytes"
|
||||
import "testing"
|
||||
import "reflect"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
|
||||
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 := DecodeAnyInto(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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
11
tape/strings.go
Normal file
11
tape/strings.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package tape
|
||||
|
||||
const lipsum = `Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.`
|
||||
13
tape/tag.go
13
tape/tag.go
@@ -18,6 +18,17 @@ type Tag byte; const (
|
||||
CNLimit Tag = 32 // All valid CNs are < CNLimit
|
||||
)
|
||||
|
||||
// what the first nybble of a tag means:
|
||||
//
|
||||
// 0-1 : SI
|
||||
// 2-3 : LI
|
||||
// 4-5 : LSI
|
||||
// 6-7 : FP
|
||||
// 8-9 : SBA
|
||||
// A-B : LBA
|
||||
// C-D : OTA
|
||||
// E-F : KTV
|
||||
|
||||
func (tag Tag) TN() int {
|
||||
return int(tag >> 5)
|
||||
}
|
||||
@@ -67,6 +78,6 @@ func bufferLenTag(length int) Tag {
|
||||
if length < int(CNLimit) {
|
||||
return SBA.WithCN(length)
|
||||
} else {
|
||||
return LBA.WithCN(IntBytes(uint64(length)))
|
||||
return LBA.WithCN(IntBytes(uint64(length)) - 1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user