rs2322tcp/internal/server/data_handler.go
2026-08-11 15:19:36 +02:00

151 lines
4.1 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: data_handler.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Verbindet eingehende TCP-Datenverbindungen eines Data-Listeners mit
* einem vom Server bereitgestellten seriellen Gerätekanal.
* ============================================================================
*/
package server
import (
"fmt"
"io"
"log"
"net"
)
///////////////////////////////////////////////////////////////////////////////
// SerialFactory
///////////////////////////////////////////////////////////////////////////////
// SerialFactory creates a serial connection for one device.
//
// The concrete implementation is provided by the serial package later.
// Keeping the factory as a function type prevents the server package from
// depending directly on the concrete serial implementation.
type SerialFactory func() (io.ReadWriteCloser, error)
///////////////////////////////////////////////////////////////////////////////
// DataHandler
///////////////////////////////////////////////////////////////////////////////
// DataHandler accepts TCP data connections and connects them to a serial
// device.
type DataHandler struct {
listener *DataListener
serialFactory SerialFactory
hardwareErrorResponse string
}
///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
// NewDataHandler creates a new data handler.
//
// The listener provides the TCP endpoint. The serial factory is called for
// every accepted TCP connection.
func NewDataHandler(
listener *DataListener,
serialFactory SerialFactory,
hardwareErrorResponse string,
) (*DataHandler, error) {
if listener == nil {
return nil, fmt.Errorf("data listener is nil")
}
if serialFactory == nil {
return nil, fmt.Errorf("serial factory is nil")
}
if hardwareErrorResponse == "" {
return nil, fmt.Errorf("hardware error response is empty")
}
return &DataHandler{
listener: listener,
serialFactory: serialFactory,
hardwareErrorResponse: hardwareErrorResponse,
}, nil
}
///////////////////////////////////////////////////////////////////////////////
// Serve
///////////////////////////////////////////////////////////////////////////////
// Serve waits for incoming TCP data connections.
//
// Every accepted connection gets its own serial connection and
// DataConnection. The handler runs independently for each client.
//
// Serve terminates when the listener is closed.
func (h *DataHandler) Serve() error {
if h == nil {
return fmt.Errorf("data handler is nil")
}
for {
tcpConn, err := h.listener.Accept()
if err != nil {
return err
}
go h.handleConnection(tcpConn)
}
}
///////////////////////////////////////////////////////////////////////////////
// Connection handling
///////////////////////////////////////////////////////////////////////////////
// handleConnection connects one accepted TCP connection to one serial
// connection.
func (h *DataHandler) handleConnection(tcpConn net.Conn) {
if tcpConn == nil {
return
}
serialConn, err := h.serialFactory()
if err != nil {
log.Printf("create serial connection: %v", err)
_ = tcpConn.Close()
return
}
dataConnection, err := NewDataConnection(
tcpConn,
serialConn,
h.hardwareErrorResponse,
)
if err != nil {
log.Printf("create data connection: %v", err)
_ = serialConn.Close()
_ = tcpConn.Close()
return
}
if err := dataConnection.Run(); err != nil {
log.Printf("data connection terminated: %v", err)
}
}
///////////////////////////////////////////////////////////////////////////////
// Test support
///////////////////////////////////////////////////////////////////////////////
// HandleConnectionForTest handles one TCP connection using the configured
// serial factory.
//
// This method is intentionally provided for package-level tests without
// requiring a real TCP listener.
func (h *DataHandler) HandleConnectionForTest(tcpConn net.Conn) {
h.handleConnection(tcpConn)
}