Initial go port of epics
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user