Hardwarefehler an Client zurückmelden
This commit is contained in:
parent
a33546fbed
commit
1621ed4330
12 changed files with 247 additions and 22 deletions
|
|
@ -3,6 +3,9 @@
|
||||||
"address": "0.0.0.0",
|
"address": "0.0.0.0",
|
||||||
"port": 5000
|
"port": 5000
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"hardware_error_response": "ERROR - HARDWARE NOT AVAILABLE",
|
||||||
|
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"id": "radio",
|
"id": "radio",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,16 @@ import (
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/server"
|
"git.lang-dieter.de/rs2322tcp/internal/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test configuration
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const testHardwareErrorResponse = "TEST ERROR RESPONSE"
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Virtual serial pair
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
func startVirtualSerialPair(t *testing.T) (string, string, func()) {
|
func startVirtualSerialPair(t *testing.T) (string, string, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|
@ -79,6 +89,10 @@ func startVirtualSerialPair(t *testing.T) (string, string, func()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Read helper
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
func readExactWithTimeout(
|
func readExactWithTimeout(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
reader io.Reader,
|
reader io.Reader,
|
||||||
|
|
@ -105,6 +119,10 @@ func readExactWithTimeout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Integration test
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
func TestClientServerSerialIntegration(t *testing.T) {
|
func TestClientServerSerialIntegration(t *testing.T) {
|
||||||
serialA, serialB, cleanup := startVirtualSerialPair(t)
|
serialA, serialB, cleanup := startVirtualSerialPair(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
@ -130,6 +148,7 @@ func TestClientServerSerialIntegration(t *testing.T) {
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Port: controlPort,
|
Port: controlPort,
|
||||||
},
|
},
|
||||||
|
HardwareErrorResponse: testHardwareErrorResponse,
|
||||||
Devices: []config.DeviceConfig{
|
Devices: []config.DeviceConfig{
|
||||||
{
|
{
|
||||||
ID: "radio",
|
ID: "radio",
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ func TestClientRuntimeSerialIntegration(t *testing.T) {
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Port: controlPort,
|
Port: controlPort,
|
||||||
},
|
},
|
||||||
|
HardwareErrorResponse: testHardwareErrorResponse,
|
||||||
Devices: []config.DeviceConfig{
|
Devices: []config.DeviceConfig{
|
||||||
{
|
{
|
||||||
ID: "radio",
|
ID: "radio",
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ import (
|
||||||
|
|
||||||
// ServerConfig contains the complete server configuration.
|
// ServerConfig contains the complete server configuration.
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
Listen ListenConfig `json:"listen"`
|
Listen ListenConfig `json:"listen"`
|
||||||
Devices []DeviceConfig `json:"devices"`
|
HardwareErrorResponse string `json:"hardware_error_response"`
|
||||||
|
Devices []DeviceConfig `json:"devices"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenConfig contains the network listener configuration.
|
// ListenConfig contains the network listener configuration.
|
||||||
|
|
@ -88,6 +89,9 @@ const (
|
||||||
// DefaultVirtualPortLast is the last virtual USB serial port number
|
// DefaultVirtualPortLast is the last virtual USB serial port number
|
||||||
// used when no virtual port range is specified.
|
// used when no virtual port range is specified.
|
||||||
DefaultVirtualPortLast = 199
|
DefaultVirtualPortLast = 199
|
||||||
|
|
||||||
|
// Fehlermeldung wenn Hardware nicht erreichbar ist
|
||||||
|
DefaultHardwareErrorResponse = "ERROR - HARDWARE NOT AVAILABLE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultVirtualPortRange returns the default virtual port range.
|
// DefaultVirtualPortRange returns the default virtual port range.
|
||||||
|
|
@ -110,6 +114,10 @@ func LoadServer(filename string) (*ServerConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.HardwareErrorResponse == "" {
|
||||||
|
cfg.HardwareErrorResponse = DefaultHardwareErrorResponse
|
||||||
|
}
|
||||||
|
|
||||||
if err := cfg.Validate(); err != nil {
|
if err := cfg.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ func TestLoadServer(t *testing.T) {
|
||||||
t.Fatalf("LoadServer() failed: %v", err)
|
t.Fatalf("LoadServer() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.HardwareErrorResponse != config.DefaultHardwareErrorResponse {
|
||||||
|
t.Errorf(
|
||||||
|
"HardwareErrorResponse = %q, want %q",
|
||||||
|
cfg.HardwareErrorResponse,
|
||||||
|
config.DefaultHardwareErrorResponse,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Listen.Address != "0.0.0.0" {
|
if cfg.Listen.Address != "0.0.0.0" {
|
||||||
t.Errorf("Listen.Address = %q, want %q",
|
t.Errorf("Listen.Address = %q, want %q",
|
||||||
cfg.Listen.Address, "0.0.0.0")
|
cfg.Listen.Address, "0.0.0.0")
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package serial
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
bugserial "go.bug.st/serial"
|
bugserial "go.bug.st/serial"
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ import (
|
||||||
|
|
||||||
// Connection represents an opened serial connection.
|
// Connection represents an opened serial connection.
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
|
mu sync.Mutex
|
||||||
port bugserial.Port
|
port bugserial.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,20 +128,36 @@ func createMode(device config.DeviceConfig) (*bugserial.Mode, error) {
|
||||||
|
|
||||||
// Read reads data from the serial connection.
|
// Read reads data from the serial connection.
|
||||||
func (c *Connection) Read(p []byte) (int, error) {
|
func (c *Connection) Read(p []byte) (int, error) {
|
||||||
if c == nil || c.port == nil {
|
if c == nil {
|
||||||
return 0, io.ErrClosedPipe
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.port.Read(p)
|
c.mu.Lock()
|
||||||
|
port := c.port
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes data to the serial connection.
|
// Write writes data to the serial connection.
|
||||||
func (c *Connection) Write(p []byte) (int, error) {
|
func (c *Connection) Write(p []byte) (int, error) {
|
||||||
if c == nil || c.port == nil {
|
if c == nil {
|
||||||
return 0, io.ErrClosedPipe
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.port.Write(p)
|
c.mu.Lock()
|
||||||
|
port := c.port
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if port == nil {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
return port.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -148,12 +166,21 @@ func (c *Connection) Write(p []byte) (int, error) {
|
||||||
|
|
||||||
// Close closes the serial connection.
|
// Close closes the serial connection.
|
||||||
func (c *Connection) Close() error {
|
func (c *Connection) Close() error {
|
||||||
if c == nil || c.port == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.port.Close()
|
c.mu.Lock()
|
||||||
|
|
||||||
|
if c.port == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
port := c.port
|
||||||
c.port = nil
|
c.port = nil
|
||||||
|
|
||||||
return err
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
return port.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,7 @@ func (s *ControlServer) runDataListener(
|
||||||
dataConnection, err := NewDataConnection(
|
dataConnection, err := NewDataConnection(
|
||||||
tcpConn,
|
tcpConn,
|
||||||
serialConn,
|
serialConn,
|
||||||
|
s.config.HardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ func testServerConfig() *config.ServerConfig {
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Port: 5000,
|
Port: 5000,
|
||||||
},
|
},
|
||||||
|
HardwareErrorResponse: testHardwareErrorResponse,
|
||||||
Devices: []config.DeviceConfig{
|
Devices: []config.DeviceConfig{
|
||||||
{
|
{
|
||||||
ID: "radio",
|
ID: "radio",
|
||||||
|
|
@ -451,6 +452,7 @@ func TestControlServerDataConnection(t *testing.T) {
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Port: 5000,
|
Port: 5000,
|
||||||
},
|
},
|
||||||
|
HardwareErrorResponse: testHardwareErrorResponse,
|
||||||
Devices: []config.DeviceConfig{
|
Devices: []config.DeviceConfig{
|
||||||
{
|
{
|
||||||
ID: "radio",
|
ID: "radio",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
* Projekt.....: rs2322tcp
|
* Projekt.....: rs2322tcp
|
||||||
* Datei.......: data_connection.go
|
* Datei.......: internal/server/data_connection.go
|
||||||
* Copyright (C) 2026 Dieter Lang
|
* Copyright (C) 2026 Dieter Lang
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
@ -31,12 +31,17 @@ import (
|
||||||
// TCP -> Serial
|
// TCP -> Serial
|
||||||
// TCP <- Serial
|
// TCP <- Serial
|
||||||
//
|
//
|
||||||
|
// If writing TCP data to the serial device fails, the configured hardware
|
||||||
|
// error response is sent back over the TCP connection before the connection
|
||||||
|
// is terminated.
|
||||||
|
//
|
||||||
// The serial side is represented by an io.ReadWriteCloser so that this
|
// The serial side is represented by an io.ReadWriteCloser so that this
|
||||||
// server component does not depend directly on the concrete serial
|
// server component does not depend directly on the concrete serial
|
||||||
// implementation.
|
// implementation.
|
||||||
type DataConnection struct {
|
type DataConnection struct {
|
||||||
tcp net.Conn
|
tcp net.Conn
|
||||||
serial io.ReadWriteCloser
|
serial io.ReadWriteCloser
|
||||||
|
hardwareErrorResponse string
|
||||||
|
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
closeErr error
|
closeErr error
|
||||||
|
|
@ -50,6 +55,7 @@ type DataConnection struct {
|
||||||
func NewDataConnection(
|
func NewDataConnection(
|
||||||
tcp net.Conn,
|
tcp net.Conn,
|
||||||
serial io.ReadWriteCloser,
|
serial io.ReadWriteCloser,
|
||||||
|
hardwareErrorResponse string,
|
||||||
) (*DataConnection, error) {
|
) (*DataConnection, error) {
|
||||||
if tcp == nil {
|
if tcp == nil {
|
||||||
return nil, fmt.Errorf("TCP connection is nil")
|
return nil, fmt.Errorf("TCP connection is nil")
|
||||||
|
|
@ -59,9 +65,14 @@ func NewDataConnection(
|
||||||
return nil, fmt.Errorf("serial connection is nil")
|
return nil, fmt.Errorf("serial connection is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hardwareErrorResponse == "" {
|
||||||
|
return nil, fmt.Errorf("hardware error response is empty")
|
||||||
|
}
|
||||||
|
|
||||||
return &DataConnection{
|
return &DataConnection{
|
||||||
tcp: tcp,
|
tcp: tcp,
|
||||||
serial: serial,
|
serial: serial,
|
||||||
|
hardwareErrorResponse: hardwareErrorResponse,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +98,54 @@ func (c *DataConnection) SerialConn() io.ReadWriteCloser {
|
||||||
return c.serial
|
return c.serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Data transfer
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// copyTCPToSerial transfers data from the TCP connection to the serial
|
||||||
|
// device.
|
||||||
|
//
|
||||||
|
// If writing to the serial device fails, the configured hardware error
|
||||||
|
// response is sent back to the TCP client before the transfer terminates.
|
||||||
|
func (c *DataConnection) copyTCPToSerial() error {
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("data connection is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 32*1024)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := c.tcp.Read(buffer)
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
if _, writeErr := c.serial.Write(buffer[:n]); writeErr != nil {
|
||||||
|
_, _ = io.WriteString(
|
||||||
|
c.tcp,
|
||||||
|
c.hardwareErrorResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copySerialToTCP transfers data from the serial device to the TCP
|
||||||
|
// connection.
|
||||||
|
func (c *DataConnection) copySerialToTCP() error {
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("data connection is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := io.Copy(c.tcp, c.serial)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Run
|
// Run
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -96,6 +155,10 @@ func (c *DataConnection) SerialConn() io.ReadWriteCloser {
|
||||||
// Run blocks until one of the two transfer directions terminates.
|
// Run blocks until one of the two transfer directions terminates.
|
||||||
// The other direction is then stopped and both connections are closed.
|
// The other direction is then stopped and both connections are closed.
|
||||||
//
|
//
|
||||||
|
// If writing data from TCP to the serial device fails, the configured
|
||||||
|
// hardware error response is sent to the TCP client before the connection
|
||||||
|
// is closed.
|
||||||
|
//
|
||||||
// The first non-EOF transfer error is returned.
|
// The first non-EOF transfer error is returned.
|
||||||
func (c *DataConnection) Run() error {
|
func (c *DataConnection) Run() error {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
|
|
@ -111,15 +174,13 @@ func (c *DataConnection) Run() error {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
_, err := io.Copy(c.serial, c.tcp)
|
errCh <- c.copyTCPToSerial()
|
||||||
errCh <- err
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
_, err := io.Copy(c.tcp, c.serial)
|
errCh <- c.copySerialToTCP()
|
||||||
errCh <- err
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := <-errCh
|
err := <-errCh
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ package server_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -31,6 +32,8 @@ type testSerialConnection struct {
|
||||||
reader *bytes.Reader
|
reader *bytes.Reader
|
||||||
writer bytes.Buffer
|
writer bytes.Buffer
|
||||||
|
|
||||||
|
writeErr error
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
closed bool
|
closed bool
|
||||||
|
|
||||||
|
|
@ -45,6 +48,14 @@ func newTestSerialConnection(data []byte) *testSerialConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newFailingTestSerialConnection(writeErr error) *testSerialConnection {
|
||||||
|
return &testSerialConnection{
|
||||||
|
reader: bytes.NewReader(nil),
|
||||||
|
writeErr: writeErr,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *testSerialConnection) Read(p []byte) (int, error) {
|
func (s *testSerialConnection) Read(p []byte) (int, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|
||||||
|
|
@ -76,6 +87,10 @@ func (s *testSerialConnection) Write(p []byte) (int, error) {
|
||||||
return 0, io.ErrClosedPipe
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.writeErr != nil {
|
||||||
|
return 0, s.writeErr
|
||||||
|
}
|
||||||
|
|
||||||
return s.writer.Write(p)
|
return s.writer.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,6 +131,7 @@ func TestNewDataConnection(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
tcpServer,
|
tcpServer,
|
||||||
serial,
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||||
|
|
@ -136,6 +152,7 @@ func TestNewDataConnectionRejectsNilTCP(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
nil,
|
nil,
|
||||||
serial,
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -155,6 +172,7 @@ func TestNewDataConnectionRejectsNilSerial(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
tcpServer,
|
tcpServer,
|
||||||
nil,
|
nil,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -179,6 +197,7 @@ func TestDataConnectionTCPToSerial(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
tcpServer,
|
tcpServer,
|
||||||
serial,
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||||
|
|
@ -223,6 +242,60 @@ func TestDataConnectionTCPToSerial(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TCP -> Serial error
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func TestDataConnectionTCPToSerialWriteError(t *testing.T) {
|
||||||
|
tcpServer, tcpClient := net.Pipe()
|
||||||
|
defer tcpClient.Close()
|
||||||
|
|
||||||
|
writeErr := errors.New("hardware write failed")
|
||||||
|
|
||||||
|
serial := newFailingTestSerialConnection(writeErr)
|
||||||
|
|
||||||
|
connection, err := server.NewDataConnection(
|
||||||
|
tcpServer,
|
||||||
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
done <- connection.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
testData := []byte("hello unavailable hardware")
|
||||||
|
|
||||||
|
if _, err := tcpClient.Write(testData); err != nil {
|
||||||
|
t.Fatalf("TCP Write() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse := make([]byte, len(testHardwareErrorResponse))
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(tcpClient, errorResponse); err != nil {
|
||||||
|
t.Fatalf("TCP Read() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(errorResponse) != testHardwareErrorResponse {
|
||||||
|
t.Fatalf(
|
||||||
|
"error response = %q, want %q",
|
||||||
|
string(errorResponse),
|
||||||
|
testHardwareErrorResponse,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("DataConnection.Run() did not terminate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Serial -> TCP
|
// Serial -> TCP
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -238,6 +311,7 @@ func TestDataConnectionSerialToTCP(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
tcpServer,
|
tcpServer,
|
||||||
serial,
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||||
|
|
@ -285,6 +359,7 @@ func TestDataConnectionClose(t *testing.T) {
|
||||||
connection, err := server.NewDataConnection(
|
connection, err := server.NewDataConnection(
|
||||||
tcpServer,
|
tcpServer,
|
||||||
serial,
|
serial,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataConnection() failed: %v", err)
|
t.Fatalf("NewDataConnection() failed: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,9 @@ type SerialFactory func() (io.ReadWriteCloser, error)
|
||||||
// DataHandler accepts TCP data connections and connects them to a serial
|
// DataHandler accepts TCP data connections and connects them to a serial
|
||||||
// device.
|
// device.
|
||||||
type DataHandler struct {
|
type DataHandler struct {
|
||||||
listener *DataListener
|
listener *DataListener
|
||||||
serialFactory SerialFactory
|
serialFactory SerialFactory
|
||||||
|
hardwareErrorResponse string
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -53,6 +54,7 @@ type DataHandler struct {
|
||||||
func NewDataHandler(
|
func NewDataHandler(
|
||||||
listener *DataListener,
|
listener *DataListener,
|
||||||
serialFactory SerialFactory,
|
serialFactory SerialFactory,
|
||||||
|
hardwareErrorResponse string,
|
||||||
) (*DataHandler, error) {
|
) (*DataHandler, error) {
|
||||||
if listener == nil {
|
if listener == nil {
|
||||||
return nil, fmt.Errorf("data listener is nil")
|
return nil, fmt.Errorf("data listener is nil")
|
||||||
|
|
@ -62,9 +64,14 @@ func NewDataHandler(
|
||||||
return nil, fmt.Errorf("serial factory is nil")
|
return nil, fmt.Errorf("serial factory is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hardwareErrorResponse == "" {
|
||||||
|
return nil, fmt.Errorf("hardware error response is empty")
|
||||||
|
}
|
||||||
|
|
||||||
return &DataHandler{
|
return &DataHandler{
|
||||||
listener: listener,
|
listener: listener,
|
||||||
serialFactory: serialFactory,
|
serialFactory: serialFactory,
|
||||||
|
hardwareErrorResponse: hardwareErrorResponse,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,6 +121,7 @@ func (h *DataHandler) handleConnection(tcpConn net.Conn) {
|
||||||
dataConnection, err := NewDataConnection(
|
dataConnection, err := NewDataConnection(
|
||||||
tcpConn,
|
tcpConn,
|
||||||
serialConn,
|
serialConn,
|
||||||
|
h.hardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create data connection: %v", err)
|
log.Printf("create data connection: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ import (
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/server"
|
"git.lang-dieter.de/rs2322tcp/internal/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test constants
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const testHardwareErrorResponse = "TEST ERROR RESPONSE"
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Test serial connection
|
// Test serial connection
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -119,6 +125,7 @@ func TestNewDataHandler(t *testing.T) {
|
||||||
func() (io.ReadWriteCloser, error) {
|
func() (io.ReadWriteCloser, error) {
|
||||||
return serial, nil
|
return serial, nil
|
||||||
},
|
},
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||||
|
|
@ -135,6 +142,7 @@ func TestNewDataHandlerRejectsNilListener(t *testing.T) {
|
||||||
func() (io.ReadWriteCloser, error) {
|
func() (io.ReadWriteCloser, error) {
|
||||||
return newHandlerTestSerial(nil), nil
|
return newHandlerTestSerial(nil), nil
|
||||||
},
|
},
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -156,6 +164,7 @@ func TestNewDataHandlerRejectsNilFactory(t *testing.T) {
|
||||||
handler, err := server.NewDataHandler(
|
handler, err := server.NewDataHandler(
|
||||||
listener,
|
listener,
|
||||||
nil,
|
nil,
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -185,6 +194,7 @@ func TestDataHandlerTCPToSerial(t *testing.T) {
|
||||||
func() (io.ReadWriteCloser, error) {
|
func() (io.ReadWriteCloser, error) {
|
||||||
return serial, nil
|
return serial, nil
|
||||||
},
|
},
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||||
|
|
@ -255,6 +265,7 @@ func TestDataHandlerSerialToTCP(t *testing.T) {
|
||||||
func() (io.ReadWriteCloser, error) {
|
func() (io.ReadWriteCloser, error) {
|
||||||
return serial, nil
|
return serial, nil
|
||||||
},
|
},
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||||
|
|
@ -311,6 +322,7 @@ func TestDataHandlerSerialFactoryError(t *testing.T) {
|
||||||
func() (io.ReadWriteCloser, error) {
|
func() (io.ReadWriteCloser, error) {
|
||||||
return nil, io.ErrClosedPipe
|
return nil, io.ErrClosedPipe
|
||||||
},
|
},
|
||||||
|
testHardwareErrorResponse,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewDataHandler() failed: %v", err)
|
t.Fatalf("NewDataHandler() failed: %v", err)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue