Compare commits
4 Commits
52aa07a98f
...
5d0b95d59a
Author | SHA1 | Date | |
---|---|---|---|
5d0b95d59a | |||
756bc79c16 | |||
a59870cc69 | |||
782472aa8f |
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
|
||||||
|
}
|
1
error.go
1
error.go
@ -9,6 +9,7 @@ type Error string; const (
|
|||||||
ErrIntegerOverflow Error = "integer overflow"
|
ErrIntegerOverflow Error = "integer overflow"
|
||||||
ErrMessageMalformed Error = "message is malformed"
|
ErrMessageMalformed Error = "message is malformed"
|
||||||
ErrTablePairMissing Error = "required table pair is missing"
|
ErrTablePairMissing Error = "required table pair is missing"
|
||||||
|
ErrWrongBufferLength Error = "wrong buffer length"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
|
@ -41,8 +41,8 @@ type Message interface {
|
|||||||
// the destination tag must come from the same (or hash-equivalent) PDL type.
|
// the destination tag must come from the same (or hash-equivalent) PDL type.
|
||||||
func canAssign(destination, source tape.Tag) bool {
|
func canAssign(destination, source tape.Tag) bool {
|
||||||
if destination.Is(source) { return true }
|
if destination.Is(source) { return true }
|
||||||
if (destination == tape.SBA || destination == tape.LBA) &&
|
if (destination.Is(tape.SBA) || destination.Is(tape.LBA)) &&
|
||||||
(source == tape.SBA || source == tape.LBA) {
|
(source.Is(tape.SBA) || source.Is(tape.LBA)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
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
|
|
||||||
}
|
|
13
metadapta.go
13
metadapta.go
@ -5,7 +5,6 @@ import "fmt"
|
|||||||
import "net"
|
import "net"
|
||||||
import "sync"
|
import "sync"
|
||||||
import "sync/atomic"
|
import "sync/atomic"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|
||||||
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
||||||
|
|
||||||
// TODO investigate why 30 never reaches the server, causing it to wait for ever
|
// TODO investigate why 30 never reaches the server, causing it to wait for ever
|
||||||
@ -406,9 +405,9 @@ func encodeMessageA(
|
|||||||
return ErrPayloadTooLarge
|
return ErrPayloadTooLarge
|
||||||
}
|
}
|
||||||
buffer := make([]byte, 18 + len(data))
|
buffer := make([]byte, 18 + len(data))
|
||||||
tape.EncodeI64(buffer[:8], trans)
|
encodeI64(buffer[:8], trans)
|
||||||
tape.EncodeI16(buffer[8:10], method)
|
encodeI16(buffer[8:10], method)
|
||||||
tape.EncodeI64(buffer[10:18], uint64(len(data)))
|
encodeI64(buffer[10:18], uint64(len(data)))
|
||||||
copy(buffer[18:], data)
|
copy(buffer[18:], data)
|
||||||
_, err := writer.Write(buffer)
|
_, err := writer.Write(buffer)
|
||||||
return err
|
return err
|
||||||
@ -427,11 +426,11 @@ func decodeMessageA(
|
|||||||
headerBuffer := [18]byte { }
|
headerBuffer := [18]byte { }
|
||||||
_, err = io.ReadFull(reader, headerBuffer[:])
|
_, err = io.ReadFull(reader, headerBuffer[:])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
transID, err = tape.DecodeI64[int64](headerBuffer[:8])
|
transID, err = decodeI64[int64](headerBuffer[:8])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
method, err = tape.DecodeI16[uint16](headerBuffer[8:10])
|
method, err = decodeI16[uint16](headerBuffer[8:10])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
size, err := tape.DecodeI64[uint64](headerBuffer[10:18])
|
size, err := decodeI64[uint64](headerBuffer[10:18])
|
||||||
if err != nil { return 0, 0, false, nil, err }
|
if err != nil { return 0, 0, false, nil, err }
|
||||||
chunked, size = splitCCBSize(size)
|
chunked, size = splitCCBSize(size)
|
||||||
if size > uint64(sizeLimit) {
|
if size > uint64(sizeLimit) {
|
||||||
|
@ -5,7 +5,6 @@ import "net"
|
|||||||
import "bytes"
|
import "bytes"
|
||||||
import "errors"
|
import "errors"
|
||||||
import "context"
|
import "context"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|
||||||
|
|
||||||
// B implements METADAPT-B over a multiplexed stream-oriented transport such as
|
// B implements METADAPT-B over a multiplexed stream-oriented transport such as
|
||||||
// QUIC.
|
// QUIC.
|
||||||
@ -165,8 +164,8 @@ func encodeMessageB(writer io.Writer, sizeLimit int64, method uint16, data []byt
|
|||||||
return ErrPayloadTooLarge
|
return ErrPayloadTooLarge
|
||||||
}
|
}
|
||||||
buffer := make([]byte, 10 + len(data))
|
buffer := make([]byte, 10 + len(data))
|
||||||
tape.EncodeI16(buffer[:2], method)
|
encodeI16(buffer[:2], method)
|
||||||
tape.EncodeI64(buffer[2:10], uint64(len(data)))
|
encodeI64(buffer[2:10], uint64(len(data)))
|
||||||
copy(buffer[10:], data)
|
copy(buffer[10:], data)
|
||||||
_, err := writer.Write(buffer)
|
_, err := writer.Write(buffer)
|
||||||
return err
|
return err
|
||||||
@ -187,9 +186,9 @@ func decodeMessageB(
|
|||||||
if errors.Is(err, io.EOF) { return 0, 0, nil, io.ErrUnexpectedEOF }
|
if errors.Is(err, io.EOF) { return 0, 0, nil, io.ErrUnexpectedEOF }
|
||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
method, err = tape.DecodeI16[uint16](headerBuffer[:2])
|
method, err = decodeI16[uint16](headerBuffer[:2])
|
||||||
if err != nil { return 0, 0, nil, err }
|
if err != nil { return 0, 0, nil, err }
|
||||||
length, err := tape.DecodeI64[uint64](headerBuffer[2:10])
|
length, err := decodeI64[uint64](headerBuffer[2:10])
|
||||||
if err != nil { return 0, 0, nil, err }
|
if err != nil { return 0, 0, nil, err }
|
||||||
if length > uint64(sizeLimit) {
|
if length > uint64(sizeLimit) {
|
||||||
return 0, 0, nil, ErrPayloadTooLarge
|
return 0, 0, nil, ErrPayloadTooLarge
|
||||||
|
Loading…
x
Reference in New Issue
Block a user