Initial working fully go release

This commit is contained in:
Martino Ferrari
2026-04-30 23:01:01 +02:00
parent 6e51ffc5e1
commit 90669c5fd6
22 changed files with 2950 additions and 196 deletions
+73
View File
@@ -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
}
+679
View File
@@ -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.
// 0x000xFE → 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: 0x280x2F, 0x4A0x4B, 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
}
}
+220
View File
@@ -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)
}
}
}