/* * ============================================================================ * Projekt.....: rs2322tcp * Datei.......: data_connection.go * Copyright (C) 2026 Dieter Lang * * SPDX-License-Identifier: GPL-3.0-or-later * * Beschreibung: * Bidirektionale Datenverbindung zwischen einer TCP-Verbindung und * einem seriellen Gerätekanal. * ============================================================================ */ package server import ( "fmt" "io" "net" "sync" ) /////////////////////////////////////////////////////////////////////////////// // DataConnection /////////////////////////////////////////////////////////////////////////////// // DataConnection connects one TCP connection with one serial device. // // Data is transferred bidirectionally: // // TCP -> Serial // TCP <- Serial // // The serial side is represented by an io.ReadWriteCloser so that this // server component does not depend directly on the concrete serial // implementation. type DataConnection struct { tcp net.Conn serial io.ReadWriteCloser closeOnce sync.Once closeErr error } /////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////// // NewDataConnection creates a new bidirectional data connection. func NewDataConnection( tcp net.Conn, serial io.ReadWriteCloser, ) (*DataConnection, error) { if tcp == nil { return nil, fmt.Errorf("TCP connection is nil") } if serial == nil { return nil, fmt.Errorf("serial connection is nil") } return &DataConnection{ tcp: tcp, serial: serial, }, nil } /////////////////////////////////////////////////////////////////////////////// // Properties /////////////////////////////////////////////////////////////////////////////// // TCPConn returns the TCP connection. func (c *DataConnection) TCPConn() net.Conn { if c == nil { return nil } return c.tcp } // SerialConn returns the serial connection. func (c *DataConnection) SerialConn() io.ReadWriteCloser { if c == nil { return nil } return c.serial } /////////////////////////////////////////////////////////////////////////////// // Run /////////////////////////////////////////////////////////////////////////////// // Run transfers data in both directions. // // Run blocks until one of the two transfer directions terminates. // The other direction is then stopped and both connections are closed. // // The first non-EOF transfer error is returned. func (c *DataConnection) Run() error { if c == nil { return fmt.Errorf("data connection is nil") } var wg sync.WaitGroup errCh := make(chan error, 2) wg.Add(2) go func() { defer wg.Done() _, err := io.Copy(c.serial, c.tcp) errCh <- err }() go func() { defer wg.Done() _, err := io.Copy(c.tcp, c.serial) errCh <- err }() err := <-errCh _ = c.Close() wg.Wait() if err == nil || err == io.EOF { return nil } return err } /////////////////////////////////////////////////////////////////////////////// // Lifecycle /////////////////////////////////////////////////////////////////////////////// // Close closes both sides of the data connection. // // Close is safe to call multiple times. func (c *DataConnection) Close() error { if c == nil { return nil } c.closeOnce.Do(func() { var firstErr error if c.tcp != nil { if err := c.tcp.Close(); err != nil { firstErr = err } } if c.serial != nil { if err := c.serial.Close(); err != nil && firstErr == nil { firstErr = err } } c.closeErr = firstErr }) return c.closeErr }