This commit is contained in:
Martino Ferrari
2026-06-24 01:39:15 +02:00
parent 11120bedca
commit c0f7e662be
76 changed files with 4368 additions and 210 deletions
+3 -3
View File
@@ -224,10 +224,10 @@ func (cl *Client) sendSearchRequest(seq uint32, pvName string) {
laddr := cl.udp.LocalAddr().(*net.UDPAddr)
binary.Write(&buf, binary.LittleEndian, uint16(laddr.Port))
buf.WriteByte(1) // transportCount = 1
buf.WriteByte(0x01) // transport[0]: TCP
buf.WriteByte(1) // transportCount = 1
buf.WriteByte(0x01) // transport[0]: TCP
binary.Write(&buf, binary.LittleEndian, uint16(1)) // pvCount = 1
binary.Write(&buf, binary.LittleEndian, uint16(1)) // pvCount = 1
binary.Write(&buf, binary.LittleEndian, uint32(seq)) // channelID (reuse seq)
pvdata.WriteString(&buf, pvName)
+6 -7
View File
@@ -38,7 +38,7 @@ type serverChannel struct {
type monitorSub struct {
ioid uint32
sid uint32 // server channel ID — needed for pipeline ACKs and DESTROY
sid uint32 // server channel ID — needed for pipeline ACKs and DESTROY
cid uint32
desc pvdata.FieldDesc // structure descriptor (received on INIT response)
updates chan MonitorEvent
@@ -56,10 +56,10 @@ type MonitorEvent struct {
// conn is a single PVA TCP connection to one server.
type conn struct {
nc net.Conn
br *bufio.Reader // shared reader — used by handshake then readLoop
bw *bufio.Writer
mu sync.Mutex // protects writes + state maps
nc net.Conn
br *bufio.Reader // shared reader — used by handshake then readLoop
bw *bufio.Writer
mu sync.Mutex // protects writes + state maps
// auto-incrementing IDs
nextCID atomic.Uint32
@@ -68,7 +68,7 @@ type conn struct {
// state
chans map[uint32]*serverChannel // keyed by client CID
byPV map[string]*serverChannel // keyed by PV name
monitors map[uint32]*monitorSub // keyed by IOID
monitors map[uint32]*monitorSub // keyed by IOID
done chan struct{}
}
@@ -573,4 +573,3 @@ func asError(s Status) error {
}
return fmt.Errorf("%s", s.Error())
}
+15 -15
View File
@@ -59,21 +59,21 @@ const (
// ---- Control message sub-commands -------------------------------------
const (
CtrlSetMarker byte = 0x00
CtrlAckMarker byte = 0x01
CtrlSetByteOrder byte = 0x02
CtrlEchoRequest byte = 0x03
CtrlEchoResponse byte = 0x04
CtrlSetMarker byte = 0x00
CtrlAckMarker byte = 0x01
CtrlSetByteOrder byte = 0x02
CtrlEchoRequest byte = 0x03
CtrlEchoResponse byte = 0x04
)
// ---- Status codes -----------------------------------------------------
const (
StatusOK byte = 0xFF // special "OK" short encoding
StatusOKFull byte = 0x00 // full OK message (type=OK, msg="")
StatusWarn byte = 0x01
StatusError byte = 0x02
StatusFatal byte = 0x03
StatusOK byte = 0xFF // special "OK" short encoding
StatusOKFull byte = 0x00 // full OK message (type=OK, msg="")
StatusWarn byte = 0x01
StatusError byte = 0x02
StatusFatal byte = 0x03
)
// ---- Request sub-command bits -----------------------------------------
@@ -90,11 +90,11 @@ const (
// PVAHeader is the 8-byte fixed header on every PVA message.
//
// byte 0 : magic 0xCA
// byte 1 : protocol version (0x01)
// byte 2 : flags (see flag* constants)
// byte 3 : command code
// bytes 47 : payload size (uint32, matches byte-order flag)
// byte 0 : magic 0xCA
// byte 1 : protocol version (0x01)
// byte 2 : flags (see flag* constants)
// byte 3 : command code
// bytes 47 : payload size (uint32, matches byte-order flag)
type PVAHeader struct {
Version byte
Flags byte
+175
View File
@@ -0,0 +1,175 @@
package pva
import (
"bytes"
"encoding/binary"
"testing"
)
// ---- Header codec -----------------------------------------------------
func TestHeaderRoundTrip(t *testing.T) {
h := PVAHeader{Version: 1, Flags: flagApp, Command: CmdGet, Size: 1234}
var buf bytes.Buffer
if err := WriteHeader(&buf, h); err != nil {
t.Fatal(err)
}
got, err := ReadHeader(&buf)
if err != nil {
t.Fatal(err)
}
if got.Version != h.Version || got.Command != h.Command || got.Size != h.Size {
t.Errorf("round-trip header: got %+v want %+v", got, h)
}
}
func TestReadHeaderBadMagic(t *testing.T) {
raw := []byte{0xAB, 0x01, 0x00, CmdGet, 0, 0, 0, 0}
if _, err := ReadHeader(bytes.NewReader(raw)); err == nil {
t.Error("bad magic: want error")
}
}
func TestReadHeaderTruncated(t *testing.T) {
if _, err := ReadHeader(bytes.NewReader([]byte{0xCA, 0x01})); err == nil {
t.Error("truncated header: want error")
}
}
func TestHeaderBigEndian(t *testing.T) {
// Hand-assemble a big-endian server header with size 0x00010203.
raw := []byte{magic, 0x01, flagBigEndian | flagFromServer, CmdMonitor, 0x00, 0x01, 0x02, 0x03}
h, err := ReadHeader(bytes.NewReader(raw))
if err != nil {
t.Fatal(err)
}
if h.isLittleEndian() {
t.Error("expected big-endian header")
}
if !h.isFromServer() {
t.Error("expected from-server bit")
}
if h.byteOrder() != binary.BigEndian {
t.Error("byteOrder should be big-endian")
}
if h.Size != 0x00010203 {
t.Errorf("size = %#x, want 0x00010203", h.Size)
}
}
func TestHeaderPredicates(t *testing.T) {
le := PVAHeader{Flags: flagApp}
if !le.isLittleEndian() || le.isFromServer() || le.isControl() {
t.Errorf("app/LE header predicates wrong: %+v", le)
}
ctrl := PVAHeader{Flags: flagControl}
if !ctrl.isControl() {
t.Error("control header should report isControl")
}
}
// ---- BuildMessage -----------------------------------------------------
func TestBuildMessage(t *testing.T) {
payload := []byte{1, 2, 3, 4, 5}
// Pass the server flag too; BuildMessage must clear it (client direction).
msg := BuildMessage(CmdCreateChannel, flagFromServer, payload)
h, err := ReadHeader(bytes.NewReader(msg))
if err != nil {
t.Fatal(err)
}
if h.Command != CmdCreateChannel {
t.Errorf("command = %#x, want %#x", h.Command, CmdCreateChannel)
}
if h.isFromServer() {
t.Error("BuildMessage must clear the from-server flag")
}
if int(h.Size) != len(payload) {
t.Errorf("size = %d, want %d", h.Size, len(payload))
}
if got := msg[headerSize:]; !bytes.Equal(got, payload) {
t.Errorf("payload = %v, want %v", got, payload)
}
}
// ---- Status decoding --------------------------------------------------
func TestReadStatusOKShort(t *testing.T) {
s, err := ReadStatus(bytes.NewReader([]byte{StatusOK}), binary.LittleEndian)
if err != nil {
t.Fatal(err)
}
if !s.OK() || s.Error() != "" {
t.Errorf("short OK status: got %+v", s)
}
}
func TestReadStatusError(t *testing.T) {
var buf bytes.Buffer
buf.WriteByte(StatusError)
buf.WriteByte(0x04) // compact size 4
buf.WriteString("boom")
buf.WriteByte(0x00) // empty stack
s, err := ReadStatus(&buf, binary.LittleEndian)
if err != nil {
t.Fatal(err)
}
if s.OK() {
t.Error("error status should not be OK")
}
if s.Message != "boom" {
t.Errorf("message = %q, want boom", s.Message)
}
if s.Error() == "" {
t.Error("Error() should be non-empty for an error status")
}
}
func TestReadStatusTruncated(t *testing.T) {
if _, err := ReadStatus(bytes.NewReader(nil), binary.LittleEndian); err == nil {
t.Error("empty status: want error")
}
// type byte present but the message string is missing.
if _, err := ReadStatus(bytes.NewReader([]byte{StatusError}), binary.LittleEndian); err == nil {
t.Error("status missing message: want error")
}
}
// ---- readPVAString ----------------------------------------------------
func TestReadPVAString(t *testing.T) {
// Short form.
short := append([]byte{0x03}, []byte("abc")...)
s, err := readPVAString(bytes.NewReader(short), binary.LittleEndian)
if err != nil || s != "abc" {
t.Errorf("short string: got %q err %v", s, err)
}
// Empty.
s, err = readPVAString(bytes.NewReader([]byte{0x00}), binary.LittleEndian)
if err != nil || s != "" {
t.Errorf("empty string: got %q err %v", s, err)
}
// Extended form: 0xFF + int32 length + bytes.
var ext bytes.Buffer
ext.WriteByte(0xFF)
_ = binary.Write(&ext, binary.LittleEndian, int32(5))
ext.WriteString("hello")
s, err = readPVAString(&ext, binary.LittleEndian)
if err != nil || s != "hello" {
t.Errorf("extended string: got %q err %v", s, err)
}
}
// ---- env --------------------------------------------------------------
func TestAddrsFromEnv(t *testing.T) {
t.Setenv("EPICS_PVA_ADDR_LIST", "")
if got := addrsFromEnv(); got != nil {
t.Errorf("empty env: got %v, want nil", got)
}
t.Setenv("EPICS_PVA_ADDR_LIST", " 1.2.3.4 5.6.7.8 ")
got := addrsFromEnv()
if len(got) != 2 || got[0] != "1.2.3.4" || got[1] != "5.6.7.8" {
t.Errorf("addr list: got %v", got)
}
}
+3 -1
View File
@@ -3,7 +3,9 @@ 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.
//
// 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 {
+361
View File
@@ -0,0 +1,361 @@
package pvdata_test
import (
"bytes"
"math"
"reflect"
"testing"
"github.com/uopi/gopva/pvdata"
)
// ---- Scalar value codec: every type code -----------------------------
func TestScalarRoundTrip(t *testing.T) {
cases := []struct {
name string
tc byte
v pvdata.Value
}{
{"bool-true", pvdata.TypeCodeBoolean, true},
{"bool-false", pvdata.TypeCodeBoolean, false},
{"byte", pvdata.TypeCodeByte, int8(-12)},
{"short", pvdata.TypeCodeShort, int16(-3000)},
{"int", pvdata.TypeCodeInt, int32(-100000)},
{"long", pvdata.TypeCodeLong, int64(-1 << 40)},
{"ubyte", pvdata.TypeCodeUByte, uint8(200)},
{"ushort", pvdata.TypeCodeUShort, uint16(60000)},
{"uint", pvdata.TypeCodeUInt, uint32(4000000000)},
{"ulong", pvdata.TypeCodeULong, uint64(1 << 50)},
{"float", pvdata.TypeCodeFloat, float32(1.5)},
{"double", pvdata.TypeCodeDouble, float64(2.5)},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
desc := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: c.tc}
var buf bytes.Buffer
if err := pvdata.WriteValue(&buf, desc, c.v); err != nil {
t.Fatalf("WriteValue: %v", err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatalf("ReadValue: %v", err)
}
if got != c.v {
t.Errorf("got %v (%T) want %v (%T)", got, got, c.v, c.v)
}
})
}
}
// ---- Scalar array codec: every array code ----------------------------
func TestScalarArrayRoundTrip(t *testing.T) {
cases := []struct {
name string
tc byte
v pvdata.Value
}{
{"bool", pvdata.TypeCodeBoolArr, []bool{true, false, true}},
{"byte", pvdata.TypeCodeByteArr, []int8{-1, 2, -3}},
{"short", pvdata.TypeCodeShortArr, []int16{-1, 2, 30000}},
{"int", pvdata.TypeCodeIntArr, []int32{-1, 2, 1 << 30}},
{"long", pvdata.TypeCodeLongArr, []int64{-1, 1 << 40}},
{"ubyte", pvdata.TypeCodeUByteArr, []uint8{1, 2, 255}},
{"ushort", pvdata.TypeCodeUShortArr, []uint16{1, 60000}},
{"uint", pvdata.TypeCodeUIntArr, []uint32{1, 4000000000}},
{"ulong", pvdata.TypeCodeULongArr, []uint64{1, 1 << 50}},
{"float", pvdata.TypeCodeFloatArr, []float32{1.5, -2.25}},
{"double", pvdata.TypeCodeDoubleArr, []float64{1.5, -2.25}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
desc := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: c.tc}
var buf bytes.Buffer
if err := pvdata.WriteValue(&buf, desc, c.v); err != nil {
t.Fatalf("WriteValue: %v", err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatalf("ReadValue: %v", err)
}
if !reflect.DeepEqual(got, c.v) {
t.Errorf("got %v want %v", got, c.v)
}
})
}
}
func TestStringArrayRoundTrip(t *testing.T) {
desc := pvdata.FieldDesc{Kind: pvdata.KindStringArr, TypeCode: pvdata.TypeCodeStringArr}
for _, v := range [][]string{{"a", "", "epics"}, {}} {
var buf bytes.Buffer
if err := pvdata.WriteValue(&buf, desc, v); err != nil {
t.Fatalf("WriteValue: %v", err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatalf("ReadValue: %v", err)
}
gs := got.([]string)
if len(gs) != len(v) {
t.Fatalf("len: got %d want %d", len(gs), len(v))
}
for i := range v {
if gs[i] != v[i] {
t.Errorf("[%d]: got %q want %q", i, gs[i], v[i])
}
}
}
}
// ---- FieldDesc round-trip for every Kind -----------------------------
func TestFieldDescVariants(t *testing.T) {
descs := []pvdata.FieldDesc{
{TypeCode: pvdata.TypeCodeNull},
{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString},
{Kind: pvdata.KindStringArr, TypeCode: pvdata.TypeCodeStringArr},
{Kind: pvdata.KindVariant, TypeCode: pvdata.TypeCodeVariant},
{Kind: pvdata.KindScalarArr, TypeCode: pvdata.TypeCodeDoubleArr},
{Kind: pvdata.KindUnion, TypeCode: pvdata.TypeCodeUnion, TypeID: "u", Fields: []pvdata.Field{
{Name: "a", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
}},
{Kind: pvdata.KindStructArr, TypeCode: pvdata.TypeCodeStructArr, TypeID: "s", Fields: []pvdata.Field{
{Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}},
}},
{Kind: pvdata.KindUnionArr, TypeCode: pvdata.TypeCodeUnionArr, TypeID: "ua", Fields: []pvdata.Field{
{Name: "y", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
}},
}
for _, d := range descs {
var buf bytes.Buffer
if err := pvdata.WriteFieldDesc(&buf, d); err != nil {
t.Fatalf("WriteFieldDesc(%#x): %v", d.TypeCode, err)
}
got, err := pvdata.ReadFieldDesc(&buf)
if err != nil {
t.Fatalf("ReadFieldDesc(%#x): %v", d.TypeCode, err)
}
if got.TypeCode != d.TypeCode {
t.Errorf("TypeCode: got %#x want %#x", got.TypeCode, d.TypeCode)
}
if got.TypeID != d.TypeID {
t.Errorf("TypeID: got %q want %q", got.TypeID, d.TypeID)
}
if len(got.Fields) != len(d.Fields) {
t.Errorf("Fields: got %d want %d", len(got.Fields), len(d.Fields))
}
}
}
// ---- Struct array value (readStructArr) ------------------------------
func TestStructArrRoundTrip(t *testing.T) {
fields := []pvdata.Field{
{Name: "x", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
}
elems := []pvdata.StructValue{
{TypeID: "p", Fields: []pvdata.FieldValue{{Name: "x", Value: int32(1)}}},
{TypeID: "p", Fields: []pvdata.FieldValue{{Name: "x", Value: int32(2)}}},
}
var buf bytes.Buffer
if err := pvdata.WriteSize(&buf, int64(len(elems))); err != nil {
t.Fatal(err)
}
sdesc := pvdata.FieldDesc{Kind: pvdata.KindStruct, TypeCode: pvdata.TypeCodeStruct, TypeID: "p", Fields: fields}
for _, sv := range elems {
if err := pvdata.WriteValue(&buf, sdesc, sv); err != nil {
t.Fatal(err)
}
}
adesc := pvdata.FieldDesc{Kind: pvdata.KindStructArr, TypeCode: pvdata.TypeCodeStructArr, TypeID: "p", Fields: fields}
got, err := pvdata.ReadValue(&buf, adesc)
if err != nil {
t.Fatal(err)
}
arr := got.([]pvdata.StructValue)
if len(arr) != 2 || arr[0].Fields[0].Value.(int32) != 1 || arr[1].Fields[0].Value.(int32) != 2 {
t.Errorf("got %+v", arr)
}
}
// ---- Union value (readUnion / writeUnion) ----------------------------
func unionDesc() pvdata.FieldDesc {
return pvdata.FieldDesc{
Kind: pvdata.KindUnion, TypeCode: pvdata.TypeCodeUnion, TypeID: "u",
Fields: []pvdata.Field{
{Name: "i", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
{Name: "s", Desc: pvdata.FieldDesc{Kind: pvdata.KindString, TypeCode: pvdata.TypeCodeString}},
},
}
}
func TestUnionSelectedValue(t *testing.T) {
desc := unionDesc()
// Manually encode: select field 1 (string) with value "hi".
var buf bytes.Buffer
if err := pvdata.WriteSize(&buf, 1); err != nil {
t.Fatal(err)
}
if err := pvdata.WriteValue(&buf, desc.Fields[1].Desc, "hi"); err != nil {
t.Fatal(err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatal(err)
}
uv := got.(pvdata.UnionValue)
if uv.Selected != 1 || uv.Name != "s" || uv.Value.(string) != "hi" {
t.Errorf("got %+v", uv)
}
}
func TestUnionNullSelector(t *testing.T) {
desc := unionDesc()
// writeUnion only writes the selector; an out-of-range selector → null.
var buf bytes.Buffer
if err := pvdata.WriteValue(&buf, desc, pvdata.UnionValue{Selected: 7}); err != nil {
t.Fatal(err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatal(err)
}
uv := got.(pvdata.UnionValue)
if uv.Selected != 7 || uv.Value != nil {
t.Errorf("got %+v, want null selector", uv)
}
}
func TestUnionArrRoundTrip(t *testing.T) {
desc := pvdata.FieldDesc{
Kind: pvdata.KindUnionArr, TypeCode: pvdata.TypeCodeUnionArr, TypeID: "u",
Fields: []pvdata.Field{
{Name: "i", Desc: pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeInt}},
},
}
var buf bytes.Buffer
if err := pvdata.WriteSize(&buf, 2); err != nil {
t.Fatal(err)
}
for _, val := range []int32{10, 20} {
if err := pvdata.WriteSize(&buf, 0); err != nil { // selector 0
t.Fatal(err)
}
if err := pvdata.WriteValue(&buf, desc.Fields[0].Desc, val); err != nil {
t.Fatal(err)
}
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatal(err)
}
arr := got.([]pvdata.UnionValue)
if len(arr) != 2 || arr[0].Value.(int32) != 10 || arr[1].Value.(int32) != 20 {
t.Errorf("got %+v", arr)
}
}
// ---- Variant value (readVariant + WriteValue variant path) -----------
func TestVariantRoundTrip(t *testing.T) {
inner := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: pvdata.TypeCodeDouble}
vv := pvdata.VariantValue{Desc: inner, Value: float64(9.5)}
desc := pvdata.FieldDesc{Kind: pvdata.KindVariant, TypeCode: pvdata.TypeCodeVariant}
var buf bytes.Buffer
if err := pvdata.WriteValue(&buf, desc, vv); err != nil {
t.Fatal(err)
}
got, err := pvdata.ReadValue(&buf, desc)
if err != nil {
t.Fatal(err)
}
gvv := got.(pvdata.VariantValue)
if gvv.Value.(float64) != 9.5 || gvv.Desc.TypeCode != pvdata.TypeCodeDouble {
t.Errorf("got %+v", gvv)
}
}
// ---- Compact size: extended (>MaxInt32) encodings --------------------
func TestCompactSizeLarge(t *testing.T) {
cases := []int64{math.MaxInt32, math.MaxInt32 + 1, 1 << 40}
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(%d): %v", n, err)
}
if got != n {
t.Errorf("round-trip size %d → %d", n, got)
}
}
}
// ---- Error / truncation paths ----------------------------------------
func TestReadErrors(t *testing.T) {
if _, err := pvdata.ReadSize(bytes.NewReader(nil)); err == nil {
t.Error("ReadSize on empty: want error")
}
if _, err := pvdata.ReadString(bytes.NewReader([]byte{0x05})); err == nil {
t.Error("ReadString truncated: want error")
}
if _, err := pvdata.ReadFieldDesc(bytes.NewReader(nil)); err == nil {
t.Error("ReadFieldDesc on empty: want error")
}
// Unknown scalar type code.
badScalar := pvdata.FieldDesc{Kind: pvdata.KindScalar, TypeCode: 0x99}
if _, err := pvdata.ReadValue(bytes.NewReader([]byte{1, 2, 3, 4}), badScalar); err == nil {
t.Error("ReadValue unknown scalar code: want error")
}
// Unknown array type code (after a valid size byte).
badArr := pvdata.FieldDesc{Kind: pvdata.KindScalarArr, TypeCode: 0x99}
if _, err := pvdata.ReadValue(bytes.NewReader([]byte{0x01}), badArr); err == nil {
t.Error("ReadValue unknown array code: want error")
}
// Unknown kind.
if _, err := pvdata.ReadValue(bytes.NewReader(nil), pvdata.FieldDesc{Kind: pvdata.Kind(99)}); err == nil {
t.Error("ReadValue unknown kind: want error")
}
// WriteValue unsupported kind (struct array has no writer).
if err := pvdata.WriteValue(&bytes.Buffer{}, pvdata.FieldDesc{Kind: pvdata.KindStructArr}, nil); err == nil {
t.Error("WriteValue unsupported kind: want error")
}
}
// ---- BitSet edges ----------------------------------------------------
func TestBitSetEdges(t *testing.T) {
bs := pvdata.NewBitSet()
if bs.Has(0) {
t.Error("fresh NewBitSet should be empty")
}
bs.Set(0)
bs.Set(5)
bs.Set(12)
if got := bs.FieldIndices(); !reflect.DeepEqual(got, []int{0, 5, 12}) {
t.Errorf("FieldIndices: got %v want [0 5 12]", got)
}
if bs.Has(100) { // far out of range → no panic, false
t.Error("Has(100) should be false")
}
// Empty BitSet round-trips as a zero-length payload.
var buf bytes.Buffer
if err := pvdata.WriteBitSet(&buf, pvdata.NewBitSet()); err != nil {
t.Fatal(err)
}
got, err := pvdata.ReadBitSet(&buf)
if err != nil {
t.Fatal(err)
}
if len(got.FieldIndices()) != 0 {
t.Errorf("empty BitSet round-trip: got %v", got.FieldIndices())
}
}
+34 -33
View File
@@ -22,30 +22,30 @@ import (
// 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
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
TypeCodeUIntArr byte = 0x2E
TypeCodeULongArr byte = 0x2F
TypeCodeFloatArr byte = 0x4A
TypeCodeDoubleArr byte = 0x4B
TypeCodeStringArr byte = 0x68
TypeCodeStructArr byte = 0x88
@@ -142,19 +142,19 @@ const (
KindScalarArr // variable-length array of scalars
KindString // pvData string (treated specially)
KindStringArr
KindStruct // structure
KindStruct // structure
KindStructArr
KindUnion // discriminated union
KindUnion // discriminated union
KindUnionArr
KindVariant // any (variant)
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
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).
@@ -293,9 +293,10 @@ func WriteFieldDesc(w io.Writer, fd FieldDesc) error {
// callers type-assert to concrete types listed below.
//
// Scalar types:
// bool, int8, int16, int32, int64,
// uint8, uint16, uint32, uint64,
// float32, float64, string
//
// bool, int8, int16, int32, int64,
// uint8, uint16, uint32, uint64,
// float32, float64, string
//
// Array types: []T for each scalar T above; []string.
// Struct: StructValue
+1 -1
View File
@@ -130,7 +130,7 @@ func TestValueDouble(t *testing.T) {
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 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)