rs2322tcp/internal/client/bridge.go
2026-08-10 17:59:35 +02:00

130 lines
3.1 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/bridge.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Bidirektionale Daten-Bridge zwischen der virtuellen seriellen
* Schnittstelle des Clients und der TCP-Data-Verbindung.
*
* Die Bridge transportiert den Byte-Strom in beide Richtungen:
*
* VirtualSerial ─────► TCP
* VirtualSerial ◄───── TCP
*
* Die Bridge kennt weder das Control-Protokoll noch die konkrete
* serielle Hardware. Sie verbindet ausschließlich zwei io.ReadWriter-
* Endpunkte miteinander.
* ============================================================================
*/
package client
import (
"fmt"
"io"
"net"
"sync"
)
///////////////////////////////////////////////////////////////////////////////
// Bridge
///////////////////////////////////////////////////////////////////////////////
// Bridge verbindet eine virtuelle serielle Schnittstelle mit einer
// TCP-Data-Verbindung.
//
// Beide Datenrichtungen werden gleichzeitig bedient. Dadurch bleibt der
// serielle Datenstrom vollständig bidirektional.
type Bridge struct {
serial VirtualSerial
conn net.Conn
}
///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
// NewBridge creates a new bidirectional data bridge.
//
// The bridge does not take ownership of the endpoints until Run is called.
func NewBridge(serial VirtualSerial, conn net.Conn) (*Bridge, error) {
if serial == nil {
return nil, fmt.Errorf("virtual serial is nil")
}
if conn == nil {
return nil, fmt.Errorf("TCP connection is nil")
}
return &Bridge{
serial: serial,
conn: conn,
}, nil
}
///////////////////////////////////////////////////////////////////////////////
// Run
///////////////////////////////////////////////////////////////////////////////
// Run starts the bidirectional byte transfer and blocks until one of the
// directions terminates.
//
// Closing the endpoints after termination releases the opposite blocked
// transfer as well.
func (b *Bridge) Run() error {
if b == nil || b.serial == nil {
return fmt.Errorf("bridge is not initialized")
}
if b.conn == nil {
return fmt.Errorf("TCP connection is nil")
}
type result struct {
name string
err error
}
results := make(chan result, 2)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
_, err := io.Copy(b.conn, b.serial)
results <- result{
name: "serial to TCP",
err: err,
}
}()
go func() {
defer wg.Done()
_, err := io.Copy(b.serial, b.conn)
results <- result{
name: "TCP to serial",
err: err,
}
}()
first := <-results
// Beide Richtungen sollen beendet werden. Das Schließen der Endpunkte
// unterbricht insbesondere einen eventuell noch blockierenden Read.
_ = b.conn.Close()
_ = b.serial.Close()
wg.Wait()
if first.err != nil {
return fmt.Errorf("%s: %w", first.name, first.err)
}
return nil
}