130 lines
3.1 KiB
Go
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
|
|
}
|