109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package proto
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Header is the decoded form of a 16-byte CA message header.
|
|
// All integer fields are stored in host byte order after decoding.
|
|
type Header struct {
|
|
Command uint16
|
|
PayloadSize uint32 // actual payload size (resolved from extended form if needed)
|
|
DataType uint16
|
|
DataCount uint32 // actual element count (resolved from extended form if needed)
|
|
Parameter1 uint32
|
|
Parameter2 uint32
|
|
}
|
|
|
|
// HeaderSize is the wire size of a standard CA header.
|
|
const HeaderSize = 16
|
|
|
|
// Encode writes the header to buf (must be at least HeaderSize bytes).
|
|
// If PayloadSize > 0xFFFE or DataCount > 0xFFFF the extended encoding is used
|
|
// and the caller must prepend an extra 8 bytes; use EncodeExtended instead.
|
|
func (h Header) Encode(buf []byte) {
|
|
binary.BigEndian.PutUint16(buf[0:], h.Command)
|
|
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadSize))
|
|
binary.BigEndian.PutUint16(buf[4:], h.DataType)
|
|
binary.BigEndian.PutUint16(buf[6:], uint16(h.DataCount))
|
|
binary.BigEndian.PutUint32(buf[8:], h.Parameter1)
|
|
binary.BigEndian.PutUint32(buf[12:], h.Parameter2)
|
|
}
|
|
|
|
// Bytes returns the 16-byte wire encoding of h.
|
|
func (h Header) Bytes() []byte {
|
|
buf := make([]byte, HeaderSize)
|
|
h.Encode(buf)
|
|
return buf
|
|
}
|
|
|
|
// DecodeHeader reads exactly one CA header from r, handling the extended
|
|
// encoding (payload_size == 0xFFFF) transparently.
|
|
// Returns the decoded Header and the number of bytes read (16 or 24).
|
|
func DecodeHeader(r io.Reader) (Header, int, error) {
|
|
var raw [HeaderSize]byte
|
|
if _, err := io.ReadFull(r, raw[:]); err != nil {
|
|
return Header{}, 0, fmt.Errorf("ca: read header: %w", err)
|
|
}
|
|
|
|
h := Header{
|
|
Command: binary.BigEndian.Uint16(raw[0:]),
|
|
DataType: binary.BigEndian.Uint16(raw[4:]),
|
|
Parameter1: binary.BigEndian.Uint32(raw[8:]),
|
|
Parameter2: binary.BigEndian.Uint32(raw[12:]),
|
|
}
|
|
|
|
rawPayload := binary.BigEndian.Uint16(raw[2:])
|
|
rawCount := binary.BigEndian.Uint16(raw[6:])
|
|
|
|
// Extended message: payload_size == 0xFFFF means real sizes are in the
|
|
// next 8 bytes (parameter1 = real payload size, parameter2 = real count).
|
|
if rawPayload == 0xFFFF && rawCount == 0x0000 {
|
|
var ext [8]byte
|
|
if _, err := io.ReadFull(r, ext[:]); err != nil {
|
|
return Header{}, 16, fmt.Errorf("ca: read extended header: %w", err)
|
|
}
|
|
h.PayloadSize = binary.BigEndian.Uint32(ext[0:])
|
|
h.DataCount = binary.BigEndian.Uint32(ext[4:])
|
|
// Note: parameter1/2 in the base header are overwritten by the extended values.
|
|
// In practice, the original parameter1/2 are still valid — the extended header
|
|
// only carries sizes, not the original parameters. We already decoded parameter1/2
|
|
// above from the base header.
|
|
return h, 24, nil
|
|
}
|
|
|
|
h.PayloadSize = uint32(rawPayload)
|
|
h.DataCount = uint32(rawCount)
|
|
return h, 16, nil
|
|
}
|
|
|
|
// PadTo8 returns n rounded up to the nearest multiple of 8.
|
|
// CA requires all message payloads to be padded to 8-byte boundaries.
|
|
func PadTo8(n int) int {
|
|
return (n + 7) &^ 7
|
|
}
|
|
|
|
// PadBytes appends zero bytes to b until len(b) is a multiple of 8.
|
|
func PadBytes(b []byte) []byte {
|
|
need := PadTo8(len(b)) - len(b)
|
|
return append(b, make([]byte, need)...)
|
|
}
|
|
|
|
// BuildMessage assembles a complete CA message (header + payload).
|
|
// payload may be nil for zero-length messages.
|
|
func BuildMessage(h Header, payload []byte) []byte {
|
|
h.PayloadSize = uint32(len(payload))
|
|
msg := make([]byte, HeaderSize+len(payload))
|
|
h.Encode(msg)
|
|
copy(msg[HeaderSize:], payload)
|
|
return msg
|
|
}
|
|
|
|
// BuildStringPayload encodes a string as a null-terminated, 8-byte padded payload.
|
|
func BuildStringPayload(s string) []byte {
|
|
b := append([]byte(s), 0) // null terminator
|
|
return PadBytes(b)
|
|
}
|