Initial working fully go release
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package pvdata
|
||||
|
||||
import "io"
|
||||
|
||||
// BitSet is a variable-length set of bit indices, encoded on the wire as:
|
||||
// compact-size(n_bytes) + n_bytes of little-endian bits.
|
||||
// Bit 0 of byte 0 is field index 0, bit 1 of byte 0 is field index 1, etc.
|
||||
// Used in pvData Monitor responses for "changed" and "overrun" masks.
|
||||
type BitSet struct {
|
||||
bits []byte
|
||||
}
|
||||
|
||||
// NewBitSet returns an empty BitSet.
|
||||
func NewBitSet() BitSet { return BitSet{} }
|
||||
|
||||
// Set sets bit i in the BitSet.
|
||||
func (bs *BitSet) Set(i int) {
|
||||
byteIdx := i / 8
|
||||
for len(bs.bits) <= byteIdx {
|
||||
bs.bits = append(bs.bits, 0)
|
||||
}
|
||||
bs.bits[byteIdx] |= 1 << (uint(i) % 8)
|
||||
}
|
||||
|
||||
// Has reports whether bit i is set.
|
||||
func (bs *BitSet) Has(i int) bool {
|
||||
byteIdx := i / 8
|
||||
if byteIdx >= len(bs.bits) {
|
||||
return false
|
||||
}
|
||||
return bs.bits[byteIdx]&(1<<(uint(i)%8)) != 0
|
||||
}
|
||||
|
||||
// ReadBitSet reads a BitSet from r.
|
||||
func ReadBitSet(r io.Reader) (BitSet, error) {
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return BitSet{}, err
|
||||
}
|
||||
if n == 0 {
|
||||
return BitSet{}, nil
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return BitSet{}, err
|
||||
}
|
||||
return BitSet{bits: buf}, nil
|
||||
}
|
||||
|
||||
// WriteBitSet writes bs to w.
|
||||
func WriteBitSet(w io.Writer, bs BitSet) error {
|
||||
if err := WriteSize(w, int64(len(bs.bits))); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bs.bits) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := w.Write(bs.bits)
|
||||
return err
|
||||
}
|
||||
|
||||
// FieldIndices returns all set bit indices in ascending order.
|
||||
func (bs *BitSet) FieldIndices() []int {
|
||||
var out []int
|
||||
for bi, b := range bs.bits {
|
||||
for bit := 0; bit < 8; bit++ {
|
||||
if b&(1<<uint(bit)) != 0 {
|
||||
out = append(out, bi*8+bit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
// Package pvdata implements the EPICS pvData serialisation layer.
|
||||
//
|
||||
// pvData defines a rich, self-describing type system used by PV Access (PVA).
|
||||
// Every value that flows over a PVA connection is encoded according to a
|
||||
// FieldDesc (field description / introspection interface), which is itself
|
||||
// serialised on the wire before the payload.
|
||||
//
|
||||
// Reference: EPICS pvData Specification r1.1
|
||||
// https://epics-pvdata.sourceforge.net/pvData.html
|
||||
package pvdata
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// ---- Type codes -------------------------------------------------------
|
||||
//
|
||||
// pvData uses a one-byte "type code" to identify scalar and array element
|
||||
// types. Values from the spec §3.1.
|
||||
|
||||
const (
|
||||
TypeCodeBoolean byte = 0x00
|
||||
TypeCodeByte byte = 0x20 // int8
|
||||
TypeCodeShort byte = 0x21 // int16
|
||||
TypeCodeInt byte = 0x22 // int32
|
||||
TypeCodeLong byte = 0x23 // int64
|
||||
TypeCodeUByte byte = 0x24 // uint8
|
||||
TypeCodeUShort byte = 0x25 // uint16
|
||||
TypeCodeUInt byte = 0x26 // uint32
|
||||
TypeCodeULong byte = 0x27 // uint64
|
||||
TypeCodeFloat byte = 0x42 // float32
|
||||
TypeCodeDouble byte = 0x43 // float64
|
||||
TypeCodeString byte = 0x60
|
||||
TypeCodeStruct byte = 0x80
|
||||
TypeCodeUnion byte = 0x81
|
||||
TypeCodeBoolArr byte = 0x08
|
||||
TypeCodeByteArr byte = 0x28
|
||||
TypeCodeShortArr byte = 0x29
|
||||
TypeCodeIntArr byte = 0x2A
|
||||
TypeCodeLongArr byte = 0x2B
|
||||
TypeCodeUByteArr byte = 0x2C
|
||||
TypeCodeUShortArr byte = 0x2D
|
||||
TypeCodeUIntArr byte = 0x2E
|
||||
TypeCodeULongArr byte = 0x2F
|
||||
TypeCodeFloatArr byte = 0x4A
|
||||
TypeCodeDoubleArr byte = 0x4B
|
||||
TypeCodeStringArr byte = 0x68
|
||||
TypeCodeStructArr byte = 0x88
|
||||
TypeCodeUnionArr byte = 0x89
|
||||
TypeCodeVariant byte = 0x82 // any
|
||||
TypeCodeNull byte = 0xFF
|
||||
)
|
||||
|
||||
// ---- Compact size encoding --------------------------------------------
|
||||
//
|
||||
// pvData encodes array/string lengths as a "size" using 1, 4, or 8 bytes
|
||||
// (spec §3.2).
|
||||
|
||||
// ReadSize reads a compact-format size from r.
|
||||
// 0x00–0xFE → 1-byte value
|
||||
// 0xFF → read 4-byte int32 (negative = 8-byte int64 follows)
|
||||
func ReadSize(r io.Reader) (int64, error) {
|
||||
var b [1]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if b[0] != 0xFF {
|
||||
return int64(b[0]), nil
|
||||
}
|
||||
var v int32
|
||||
if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v >= 0 {
|
||||
return int64(v), nil
|
||||
}
|
||||
// extended 8-byte size
|
||||
var v8 int64
|
||||
if err := binary.Read(r, binary.LittleEndian, &v8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return v8, nil
|
||||
}
|
||||
|
||||
// WriteSize writes n as a compact-format size to w.
|
||||
func WriteSize(w io.Writer, n int64) error {
|
||||
if n < 0xFF {
|
||||
_, err := w.Write([]byte{byte(n)})
|
||||
return err
|
||||
}
|
||||
if n <= math.MaxInt32 {
|
||||
if _, err := w.Write([]byte{0xFF}); err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, int32(n))
|
||||
}
|
||||
// rare: >2 GiB array
|
||||
if _, err := w.Write([]byte{0xFF}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(w, binary.LittleEndian, int32(-1)); err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, n)
|
||||
}
|
||||
|
||||
// ReadString reads a pvData string (compact size + UTF-8 bytes).
|
||||
func ReadString(r io.Reader) (string, error) {
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n == 0 {
|
||||
return "", nil
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// WriteString writes a pvData string to w.
|
||||
func WriteString(w io.Writer, s string) error {
|
||||
if err := WriteSize(w, int64(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := io.WriteString(w, s)
|
||||
return err
|
||||
}
|
||||
|
||||
// ---- Field description (introspection) --------------------------------
|
||||
|
||||
// Kind classifies a FieldDesc at a high level.
|
||||
type Kind byte
|
||||
|
||||
const (
|
||||
KindScalar Kind = iota // single scalar value
|
||||
KindScalarArr // variable-length array of scalars
|
||||
KindString // pvData string (treated specially)
|
||||
KindStringArr
|
||||
KindStruct // structure
|
||||
KindStructArr
|
||||
KindUnion // discriminated union
|
||||
KindUnionArr
|
||||
KindVariant // any (variant)
|
||||
)
|
||||
|
||||
// FieldDesc describes the type of a single field in a pvData structure.
|
||||
type FieldDesc struct {
|
||||
Kind Kind
|
||||
TypeCode byte // scalar/array type code; 0 for struct/union
|
||||
TypeID string // optional structure/union type ID string
|
||||
Fields []Field // child fields for struct/union
|
||||
}
|
||||
|
||||
// Field is a named FieldDesc (one member of a struct or union).
|
||||
type Field struct {
|
||||
Name string
|
||||
Desc FieldDesc
|
||||
}
|
||||
|
||||
// ---- FieldDesc codec --------------------------------------------------
|
||||
|
||||
// ReadFieldDesc decodes a FieldDesc from r.
|
||||
// This follows the pvData introspection encoding (spec §4).
|
||||
func ReadFieldDesc(r io.Reader) (FieldDesc, error) {
|
||||
var tc [1]byte
|
||||
if _, err := io.ReadFull(r, tc[:]); err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
return readFieldDescFromTypeCode(r, tc[0])
|
||||
}
|
||||
|
||||
func readFieldDescFromTypeCode(r io.Reader, tc byte) (FieldDesc, error) {
|
||||
switch {
|
||||
case tc == TypeCodeNull:
|
||||
return FieldDesc{TypeCode: TypeCodeNull}, nil
|
||||
|
||||
case tc == TypeCodeString:
|
||||
return FieldDesc{Kind: KindString, TypeCode: tc}, nil
|
||||
|
||||
case tc == TypeCodeStringArr:
|
||||
return FieldDesc{Kind: KindStringArr, TypeCode: tc}, nil
|
||||
|
||||
case tc == TypeCodeVariant:
|
||||
return FieldDesc{Kind: KindVariant, TypeCode: tc}, nil
|
||||
|
||||
case tc == TypeCodeStruct || tc == TypeCodeUnion:
|
||||
kind := KindStruct
|
||||
if tc == TypeCodeUnion {
|
||||
kind = KindUnion
|
||||
}
|
||||
typeID, err := ReadString(r)
|
||||
if err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
nfields, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
fields := make([]Field, nfields)
|
||||
for i := range fields {
|
||||
name, err := ReadString(r)
|
||||
if err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
desc, err := ReadFieldDesc(r)
|
||||
if err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
fields[i] = Field{Name: name, Desc: desc}
|
||||
}
|
||||
return FieldDesc{Kind: kind, TypeCode: tc, TypeID: typeID, Fields: fields}, nil
|
||||
|
||||
case tc == TypeCodeStructArr || tc == TypeCodeUnionArr:
|
||||
kind := KindStructArr
|
||||
if tc == TypeCodeUnionArr {
|
||||
kind = KindUnionArr
|
||||
}
|
||||
// element descriptor follows
|
||||
elem, err := ReadFieldDesc(r)
|
||||
if err != nil {
|
||||
return FieldDesc{}, err
|
||||
}
|
||||
return FieldDesc{Kind: kind, TypeCode: tc, TypeID: elem.TypeID, Fields: elem.Fields}, nil
|
||||
|
||||
// scalar array types: 0x28–0x2F, 0x4A–0x4B, 0x08, 0x68
|
||||
case isScalarArrayCode(tc):
|
||||
return FieldDesc{Kind: KindScalarArr, TypeCode: tc}, nil
|
||||
|
||||
// plain scalars and boolean
|
||||
default:
|
||||
return FieldDesc{Kind: KindScalar, TypeCode: tc}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func isScalarArrayCode(tc byte) bool {
|
||||
switch tc {
|
||||
case TypeCodeBoolArr,
|
||||
TypeCodeByteArr, TypeCodeShortArr, TypeCodeIntArr, TypeCodeLongArr,
|
||||
TypeCodeUByteArr, TypeCodeUShortArr, TypeCodeUIntArr, TypeCodeULongArr,
|
||||
TypeCodeFloatArr, TypeCodeDoubleArr,
|
||||
TypeCodeStringArr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WriteFieldDesc encodes fd into w.
|
||||
func WriteFieldDesc(w io.Writer, fd FieldDesc) error {
|
||||
if _, err := w.Write([]byte{fd.TypeCode}); err != nil {
|
||||
return err
|
||||
}
|
||||
switch fd.Kind {
|
||||
case KindStruct, KindUnion:
|
||||
if err := WriteString(w, fd.TypeID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := WriteSize(w, int64(len(fd.Fields))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range fd.Fields {
|
||||
if err := WriteString(w, f.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := WriteFieldDesc(w, f.Desc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case KindStructArr, KindUnionArr:
|
||||
// write element descriptor
|
||||
elemTC := TypeCodeStruct
|
||||
if fd.Kind == KindUnionArr {
|
||||
elemTC = TypeCodeUnion
|
||||
}
|
||||
if err := WriteFieldDesc(w, FieldDesc{
|
||||
Kind: KindStruct, TypeCode: byte(elemTC),
|
||||
TypeID: fd.TypeID, Fields: fd.Fields,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- Value codec -------------------------------------------------------
|
||||
|
||||
// Value is a decoded pvData value. We use interface{} for flexibility;
|
||||
// callers type-assert to concrete types listed below.
|
||||
//
|
||||
// Scalar types:
|
||||
// bool, int8, int16, int32, int64,
|
||||
// uint8, uint16, uint32, uint64,
|
||||
// float32, float64, string
|
||||
//
|
||||
// Array types: []T for each scalar T above; []string.
|
||||
// Struct: StructValue
|
||||
// Union: UnionValue
|
||||
// Variant: VariantValue
|
||||
// Null: nil
|
||||
type Value = interface{}
|
||||
|
||||
// StructValue holds the fields of a decoded pvData structure.
|
||||
type StructValue struct {
|
||||
TypeID string
|
||||
Fields []FieldValue
|
||||
}
|
||||
|
||||
// FieldValue is a named value within a StructValue.
|
||||
type FieldValue struct {
|
||||
Name string
|
||||
Value Value
|
||||
}
|
||||
|
||||
// UnionValue holds a selected field from a discriminated union.
|
||||
type UnionValue struct {
|
||||
Selected int // index into union's Fields slice
|
||||
Name string // field name
|
||||
Value Value
|
||||
}
|
||||
|
||||
// VariantValue wraps an any-typed value together with its runtime descriptor.
|
||||
type VariantValue struct {
|
||||
Desc FieldDesc
|
||||
Value Value
|
||||
}
|
||||
|
||||
// ReadValue decodes a value conforming to desc from r.
|
||||
func ReadValue(r io.Reader, desc FieldDesc) (Value, error) {
|
||||
switch desc.Kind {
|
||||
case KindScalar:
|
||||
return readScalar(r, desc.TypeCode)
|
||||
case KindScalarArr:
|
||||
return readScalarArray(r, desc.TypeCode)
|
||||
case KindString:
|
||||
return ReadString(r)
|
||||
case KindStringArr:
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss := make([]string, n)
|
||||
for i := range ss {
|
||||
ss[i], err = ReadString(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ss, nil
|
||||
case KindStruct:
|
||||
return readStruct(r, desc)
|
||||
case KindStructArr:
|
||||
return readStructArr(r, desc)
|
||||
case KindUnion:
|
||||
return readUnion(r, desc)
|
||||
case KindUnionArr:
|
||||
return readUnionArr(r, desc)
|
||||
case KindVariant:
|
||||
return readVariant(r)
|
||||
default:
|
||||
return nil, fmt.Errorf("pvdata: unknown kind %d", desc.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func readScalar(r io.Reader, tc byte) (Value, error) {
|
||||
switch tc {
|
||||
case TypeCodeBoolean:
|
||||
var b [1]byte
|
||||
_, err := io.ReadFull(r, b[:])
|
||||
return b[0] != 0, err
|
||||
case TypeCodeByte:
|
||||
var v int8
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeShort:
|
||||
var v int16
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeInt:
|
||||
var v int32
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeLong:
|
||||
var v int64
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeUByte:
|
||||
var v uint8
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeUShort:
|
||||
var v uint16
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeUInt:
|
||||
var v uint32
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeULong:
|
||||
var v uint64
|
||||
return v, binary.Read(r, binary.LittleEndian, &v)
|
||||
case TypeCodeFloat:
|
||||
var v uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return math.Float32frombits(v), nil
|
||||
case TypeCodeDouble:
|
||||
var v uint64
|
||||
if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return math.Float64frombits(v), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("pvdata: unknown scalar type code 0x%02X", tc)
|
||||
}
|
||||
}
|
||||
|
||||
func readScalarArray(r io.Reader, tc byte) (Value, error) {
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count := int(n)
|
||||
switch tc {
|
||||
case TypeCodeBoolArr:
|
||||
v := make([]bool, count)
|
||||
for i := range v {
|
||||
b := [1]byte{}
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[i] = b[0] != 0
|
||||
}
|
||||
return v, nil
|
||||
case TypeCodeByteArr:
|
||||
v := make([]int8, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeShortArr:
|
||||
v := make([]int16, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeIntArr:
|
||||
v := make([]int32, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeLongArr:
|
||||
v := make([]int64, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeUByteArr:
|
||||
v := make([]uint8, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeUShortArr:
|
||||
v := make([]uint16, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeUIntArr:
|
||||
v := make([]uint32, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeULongArr:
|
||||
v := make([]uint64, count)
|
||||
return v, binary.Read(r, binary.LittleEndian, v)
|
||||
case TypeCodeFloatArr:
|
||||
bits := make([]uint32, count)
|
||||
if err := binary.Read(r, binary.LittleEndian, bits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := make([]float32, count)
|
||||
for i, b := range bits {
|
||||
v[i] = math.Float32frombits(b)
|
||||
}
|
||||
return v, nil
|
||||
case TypeCodeDoubleArr:
|
||||
bits := make([]uint64, count)
|
||||
if err := binary.Read(r, binary.LittleEndian, bits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := make([]float64, count)
|
||||
for i, b := range bits {
|
||||
v[i] = math.Float64frombits(b)
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("pvdata: unknown array type code 0x%02X", tc)
|
||||
}
|
||||
}
|
||||
|
||||
func readStruct(r io.Reader, desc FieldDesc) (StructValue, error) {
|
||||
sv := StructValue{TypeID: desc.TypeID, Fields: make([]FieldValue, len(desc.Fields))}
|
||||
for i, f := range desc.Fields {
|
||||
v, err := ReadValue(r, f.Desc)
|
||||
if err != nil {
|
||||
return StructValue{}, fmt.Errorf("field %q: %w", f.Name, err)
|
||||
}
|
||||
sv.Fields[i] = FieldValue{Name: f.Name, Value: v}
|
||||
}
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
func readStructArr(r io.Reader, desc FieldDesc) ([]StructValue, error) {
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := make([]StructValue, n)
|
||||
for i := range arr {
|
||||
sv, err := readStruct(r, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr[i] = sv
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func readUnion(r io.Reader, desc FieldDesc) (UnionValue, error) {
|
||||
idx, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return UnionValue{}, err
|
||||
}
|
||||
if idx < 0 || int(idx) >= len(desc.Fields) {
|
||||
return UnionValue{Selected: int(idx)}, nil // null selector
|
||||
}
|
||||
f := desc.Fields[idx]
|
||||
v, err := ReadValue(r, f.Desc)
|
||||
if err != nil {
|
||||
return UnionValue{}, err
|
||||
}
|
||||
return UnionValue{Selected: int(idx), Name: f.Name, Value: v}, nil
|
||||
}
|
||||
|
||||
func readUnionArr(r io.Reader, desc FieldDesc) ([]UnionValue, error) {
|
||||
n, err := ReadSize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := make([]UnionValue, n)
|
||||
for i := range arr {
|
||||
uv, err := readUnion(r, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr[i] = uv
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func readVariant(r io.Reader) (VariantValue, error) {
|
||||
desc, err := ReadFieldDesc(r)
|
||||
if err != nil {
|
||||
return VariantValue{}, err
|
||||
}
|
||||
v, err := ReadValue(r, desc)
|
||||
if err != nil {
|
||||
return VariantValue{}, err
|
||||
}
|
||||
return VariantValue{Desc: desc, Value: v}, nil
|
||||
}
|
||||
|
||||
// WriteValue encodes v (which must match desc) into w.
|
||||
func WriteValue(w io.Writer, desc FieldDesc, v Value) error {
|
||||
switch desc.Kind {
|
||||
case KindScalar:
|
||||
return writeScalar(w, desc.TypeCode, v)
|
||||
case KindScalarArr:
|
||||
return writeScalarArray(w, desc.TypeCode, v)
|
||||
case KindString:
|
||||
return WriteString(w, v.(string))
|
||||
case KindStringArr:
|
||||
ss := v.([]string)
|
||||
if err := WriteSize(w, int64(len(ss))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range ss {
|
||||
if err := WriteString(w, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case KindStruct:
|
||||
return writeStruct(w, desc, v.(StructValue))
|
||||
case KindUnion:
|
||||
return writeUnion(w, v.(UnionValue))
|
||||
case KindVariant:
|
||||
vv := v.(VariantValue)
|
||||
if err := WriteFieldDesc(w, vv.Desc); err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteValue(w, vv.Desc, vv.Value)
|
||||
default:
|
||||
return fmt.Errorf("pvdata: WriteValue unsupported kind %d", desc.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func writeScalar(w io.Writer, tc byte, v Value) error {
|
||||
switch tc {
|
||||
case TypeCodeBoolean:
|
||||
b := byte(0)
|
||||
if v.(bool) {
|
||||
b = 1
|
||||
}
|
||||
_, err := w.Write([]byte{b})
|
||||
return err
|
||||
case TypeCodeFloat:
|
||||
return binary.Write(w, binary.LittleEndian, math.Float32bits(v.(float32)))
|
||||
case TypeCodeDouble:
|
||||
return binary.Write(w, binary.LittleEndian, math.Float64bits(v.(float64)))
|
||||
default:
|
||||
return binary.Write(w, binary.LittleEndian, v)
|
||||
}
|
||||
}
|
||||
|
||||
func writeScalarArray(w io.Writer, tc byte, v Value) error {
|
||||
switch tc {
|
||||
case TypeCodeDoubleArr:
|
||||
arr := v.([]float64)
|
||||
if err := WriteSize(w, int64(len(arr))); err != nil {
|
||||
return err
|
||||
}
|
||||
bits := make([]uint64, len(arr))
|
||||
for i, f := range arr {
|
||||
bits[i] = math.Float64bits(f)
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, bits)
|
||||
case TypeCodeFloatArr:
|
||||
arr := v.([]float32)
|
||||
if err := WriteSize(w, int64(len(arr))); err != nil {
|
||||
return err
|
||||
}
|
||||
bits := make([]uint32, len(arr))
|
||||
for i, f := range arr {
|
||||
bits[i] = math.Float32bits(f)
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, bits)
|
||||
default:
|
||||
// for integer arrays, v is already []intN or []uintN
|
||||
if err := WriteSize(w, int64(arrayLen(v))); err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(w, binary.LittleEndian, v)
|
||||
}
|
||||
}
|
||||
|
||||
func writeStruct(w io.Writer, desc FieldDesc, sv StructValue) error {
|
||||
for i, fv := range sv.Fields {
|
||||
if err := WriteValue(w, desc.Fields[i].Desc, fv.Value); err != nil {
|
||||
return fmt.Errorf("field %q: %w", fv.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUnion(w io.Writer, uv UnionValue) error {
|
||||
if err := WriteSize(w, int64(uv.Selected)); err != nil {
|
||||
return err
|
||||
}
|
||||
// caller must supply desc to encode value; not needed if Selected is null
|
||||
return nil
|
||||
}
|
||||
|
||||
// arrayLen returns reflect-free length for the supported array types.
|
||||
func arrayLen(v Value) int {
|
||||
switch x := v.(type) {
|
||||
case []bool:
|
||||
return len(x)
|
||||
case []int8:
|
||||
return len(x)
|
||||
case []int16:
|
||||
return len(x)
|
||||
case []int32:
|
||||
return len(x)
|
||||
case []int64:
|
||||
return len(x)
|
||||
case []uint8:
|
||||
return len(x)
|
||||
case []uint16:
|
||||
return len(x)
|
||||
case []uint32:
|
||||
return len(x)
|
||||
case []uint64:
|
||||
return len(x)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package pvdata_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/uopi/gopva/pvdata"
|
||||
)
|
||||
|
||||
// ---- Compact size encoding -------------------------------------------
|
||||
|
||||
func TestCompactSize(t *testing.T) {
|
||||
cases := []int64{0, 1, 10, 254, 255, 256, 1000, 65535, 70000, 1<<31 - 1}
|
||||
for _, n := range cases {
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteSize(&buf, n); err != nil {
|
||||
t.Fatalf("WriteSize(%d): %v", n, err)
|
||||
}
|
||||
got, err := pvdata.ReadSize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadSize for %d: %v", n, err)
|
||||
}
|
||||
if got != n {
|
||||
t.Errorf("round-trip size %d → %d", n, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- String encoding ------------------------------------------------
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
cases := []string{"", "hello", "UOPI:TICK", "a string with spaces and unicode: é"}
|
||||
for _, s := range cases {
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteString(&buf, s); err != nil {
|
||||
t.Fatalf("WriteString(%q): %v", s, err)
|
||||
}
|
||||
got, err := pvdata.ReadString(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadString for %q: %v", s, err)
|
||||
}
|
||||
if got != s {
|
||||
t.Errorf("round-trip string %q → %q", s, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- FieldDesc (introspection) round-trip ---------------------------
|
||||
|
||||
func TestFieldDescScalar(t *testing.T) {
|
||||
desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteFieldDesc(&buf, desc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadFieldDesc(&buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.TypeCode != desc.TypeCode || got.Kind != desc.Kind {
|
||||
t.Errorf("scalar round-trip: got %+v, want %+v", got, desc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldDescStruct(t *testing.T) {
|
||||
// Minimal NTScalar-like struct: value(double) + timeStamp(struct{…})
|
||||
desc := pvdata.FieldDesc{
|
||||
Kind: pvdata.KindStruct,
|
||||
TypeCode: pvdata.TypeCodeStruct,
|
||||
TypeID: "epics:nt/NTScalar:1.0",
|
||||
Fields: []pvdata.Field{
|
||||
{Name: "value", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}},
|
||||
{Name: "alarm", Desc: pvdata.FieldDesc{
|
||||
Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "alarm_t",
|
||||
Fields: []pvdata.Field{
|
||||
{Name: "severity", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
|
||||
{Name: "status", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
|
||||
{Name: "message", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}},
|
||||
},
|
||||
}},
|
||||
{Name: "timeStamp", Desc: pvdata.FieldDesc{
|
||||
Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "time_t",
|
||||
Fields: []pvdata.Field{
|
||||
{Name: "secondsPastEpoch", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeLong}},
|
||||
{Name: "nanoseconds", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
|
||||
{Name: "userTag", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteFieldDesc(&buf, desc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadFieldDesc(&buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.TypeID != desc.TypeID {
|
||||
t.Errorf("typeID: got %q want %q", got.TypeID, desc.TypeID)
|
||||
}
|
||||
if len(got.Fields) != len(desc.Fields) {
|
||||
t.Fatalf("field count: got %d want %d", len(got.Fields), len(desc.Fields))
|
||||
}
|
||||
for i, f := range desc.Fields {
|
||||
if got.Fields[i].Name != f.Name {
|
||||
t.Errorf("field[%d] name: got %q want %q", i, got.Fields[i].Name, f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Value round-trip -----------------------------------------------
|
||||
|
||||
func TestValueDouble(t *testing.T) {
|
||||
desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}
|
||||
var v pvdata.Value = float64(3.14159)
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteValue(&buf, desc, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadValue(&buf, desc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.(float64) != v.(float64) {
|
||||
t.Errorf("double round-trip: got %v want %v", got, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueIntArray(t *testing.T) {
|
||||
desc := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: pvdata.TypeCodeIntArr}
|
||||
var v pvdata.Value = []int32{1, 2, 3, -7, 1<<30}
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteValue(&buf, desc, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadValue(&buf, desc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
arr := got.([]int32)
|
||||
src := v.([]int32)
|
||||
if len(arr) != len(src) {
|
||||
t.Fatalf("int32 array length: got %d want %d", len(arr), len(src))
|
||||
}
|
||||
for i := range src {
|
||||
if arr[i] != src[i] {
|
||||
t.Errorf("int32[%d]: got %d want %d", i, arr[i], src[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueStruct(t *testing.T) {
|
||||
desc := pvdata.FieldDesc{
|
||||
Kind: pvdata.KindStruct,
|
||||
TypeCode: pvdata.TypeCodeStruct,
|
||||
TypeID: "test_t",
|
||||
Fields: []pvdata.Field{
|
||||
{Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}},
|
||||
{Name: "n", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
|
||||
{Name: "s", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}},
|
||||
},
|
||||
}
|
||||
sv := pvdata.StructValue{
|
||||
TypeID: "test_t",
|
||||
Fields: []pvdata.FieldValue{
|
||||
{Name: "x", Value: float64(2.718)},
|
||||
{Name: "n", Value: int32(42)},
|
||||
{Name: "s", Value: "hello"},
|
||||
},
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteValue(&buf, desc, sv); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadValue(&buf, desc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gsv := got.(pvdata.StructValue)
|
||||
if gsv.Fields[0].Value.(float64) != sv.Fields[0].Value.(float64) {
|
||||
t.Errorf("x field mismatch")
|
||||
}
|
||||
if gsv.Fields[1].Value.(int32) != sv.Fields[1].Value.(int32) {
|
||||
t.Errorf("n field mismatch")
|
||||
}
|
||||
if gsv.Fields[2].Value.(string) != sv.Fields[2].Value.(string) {
|
||||
t.Errorf("s field mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- BitSet ---------------------------------------------------------
|
||||
|
||||
func TestBitSet(t *testing.T) {
|
||||
var bs pvdata.BitSet
|
||||
bs.Set(0)
|
||||
bs.Set(3)
|
||||
bs.Set(15)
|
||||
bs.Set(16)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pvdata.WriteBitSet(&buf, bs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := pvdata.ReadBitSet(&buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, idx := range []int{0, 3, 15, 16} {
|
||||
if !got.Has(idx) {
|
||||
t.Errorf("BitSet missing index %d after round-trip", idx)
|
||||
}
|
||||
}
|
||||
for _, idx := range []int{1, 2, 4, 14, 17} {
|
||||
if got.Has(idx) {
|
||||
t.Errorf("BitSet has unexpected index %d", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user