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
+68 -9
View File
@@ -29,13 +29,21 @@ type Server struct {
tcpLn net.Listener
udpCn *net.UDPConn
mu sync.RWMutex
pvs map[string]*serverPV
subs map[uint32]*serverSub // subID → sub
mu sync.RWMutex
pvs map[string]*serverPV
subs map[uint32]*serverSub // subID → sub
getFault int // fault to inject on the next READ_NOTIFY
done chan struct{}
}
// Fault modes for SetGetFault, used to exercise the client's error paths.
const (
GetFaultNone = iota // serve replies normally
GetFaultDisconnect // drop the connection mid-request
GetFaultCorrupt // reply with an undecodable (too-short) payload
)
type serverPV struct {
spec PVSpec
mu sync.RWMutex
@@ -43,11 +51,11 @@ type serverPV struct {
}
type serverSub struct {
pvName string
subID uint32
cid uint32
sid uint32
conn net.Conn
pvName string
subID uint32
cid uint32
sid uint32
conn net.Conn
dbrType uint16
count uint32
}
@@ -93,6 +101,29 @@ func (s *Server) Close() {
s.udpCn.Close()
}
// SetGetFault arms a fault to be injected on the next READ_NOTIFY request.
// It is reset to GetFaultNone after a single request is faulted.
func (s *Server) SetGetFault(mode int) {
s.mu.Lock()
s.getFault = mode
s.mu.Unlock()
}
// Disconnect pushes a SERVER_DISCONN message to every connected subscriber,
// simulating an orderly server shutdown that forces clients to reconnect.
func (s *Server) Disconnect() {
msg := proto.BuildMessage(proto.Header{Command: proto.CmdServerDisc}, nil)
seen := make(map[net.Conn]bool)
s.mu.RLock()
for _, sub := range s.subs {
if sub.conn != nil && !seen[sub.conn] {
seen[sub.conn] = true
_, _ = sub.conn.Write(msg)
}
}
s.mu.RUnlock()
}
// SetValue updates a PV's value and pushes a monitor event to all subscribers.
func (s *Server) SetValue(pvName string, val any) error {
s.mu.RLock()
@@ -350,6 +381,31 @@ func (s *Server) handleConn(conn net.Conn) {
continue
}
// Fault injection: consume a one-shot armed fault, if any.
s.mu.Lock()
fault := s.getFault
s.getFault = GetFaultNone
s.mu.Unlock()
switch fault {
case GetFaultDisconnect:
// Drop the connection mid-request: the client's in-flight GET
// unblocks with ok=false ("circuit disconnected during GET").
conn.Close()
return
case GetFaultCorrupt:
// Reply with a too-short payload so DecodeTimeValue fails
// ("failed to decode GET reply").
reply := proto.BuildMessage(proto.Header{
Command: proto.CmdReadNotify,
DataType: hdr.DataType,
DataCount: hdr.DataCount,
Parameter1: cid,
Parameter2: ioid,
}, []byte{0, 0, 0, 0})
conn.Write(reply)
continue
}
s.mu.RLock()
pv := s.pvs[pvName]
s.mu.RUnlock()
@@ -591,7 +647,10 @@ func nullStr(b []byte) string {
// Minimal io.Reader for proto.DecodeHeader //
// -------------------------------------------------------------------------- //
type bytesReader struct{ b []byte; i int }
type bytesReader struct {
b []byte
i int
}
func newBytesReader(b []byte) *bytesReader { return &bytesReader{b: b} }