rs2322tcp/internal/client/connection.go
2026-08-11 11:29:49 +02:00

171 lines
4.5 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/connection.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Aufbau einer Verbindung zwischen einem lokalen virtuellen seriellen Port
* und dem zugehörigen entfernten Gerät.
*
* Die konfigurierte lokale virtuelle Schnittstelle wird dabei exakt
* übernommen. Eine Konfiguration wie
*
* /dev/ttyUSB100 -> radio
*
* führt somit technisch auch zur Erzeugung beziehungsweise Verwendung von
* /dev/ttyUSB100 und nicht einfach zum nächsten freien Port.
* ============================================================================
*/
package client
import (
"fmt"
"net"
"strconv"
"strings"
"git.lang-dieter.de/rs2322tcp/internal/config"
"git.lang-dieter.de/rs2322tcp/internal/transport"
)
///////////////////////////////////////////////////////////////////////////////
// Virtual port configuration
///////////////////////////////////////////////////////////////////////////////
// virtualPortNumber converts a configured /dev/ttyUSBxxx path into its
// numeric virtual port number.
//
// The client configuration stores the complete Linux device path, while
// VirtualPortManager works with the numeric port number. Only the exact
// /dev/ttyUSB<number> format is accepted here.
func virtualPortNumber(path string) (int, error) {
const prefix = "/dev/ttyUSB"
if !strings.HasPrefix(path, prefix) {
return 0, fmt.Errorf(
"invalid virtual port %q: expected /dev/ttyUSB<number>",
path,
)
}
value := strings.TrimPrefix(path, prefix)
if value == "" {
return 0, fmt.Errorf(
"invalid virtual port %q: port number is missing",
path,
)
}
number, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf(
"invalid virtual port %q: invalid port number",
path,
)
}
return number, nil
}
///////////////////////////////////////////////////////////////////////////////
// Virtual port connection
///////////////////////////////////////////////////////////////////////////////
// connectVirtualPort opens the configured local virtual port and the
// corresponding remote TCP data connection and returns both endpoints.
//
// The caller owns the returned virtual port and TCP connection.
//
// The local virtual port is opened using exactly the port number specified
// in virtualPortConfig.Port. This preserves the user's configured mapping
// between local virtual interfaces and remote devices.
func connectVirtualPort(
c *Client,
manager *VirtualPortManager,
virtualPortConfig config.VirtualPortConfig,
devices []transport.RemoteDeviceInfo,
) (*ManagedVirtualPort, net.Conn, error) {
if c == nil || c.conn == nil {
return nil, nil, fmt.Errorf("client is not connected")
}
if manager == nil {
return nil, nil, fmt.Errorf("virtual port manager is nil")
}
device, err := findRemoteDevice(virtualPortConfig, devices)
if err != nil {
return nil, nil, err
}
portNumber, err := virtualPortNumber(virtualPortConfig.Port)
if err != nil {
return nil, nil, err
}
virtualPort, err := manager.OpenSpecific(portNumber)
if err != nil {
return nil, nil, fmt.Errorf(
"open virtual port %q for %q: %w",
virtualPortConfig.Port,
virtualPortConfig.RemoteDevice,
err,
)
}
conn, err := c.OpenDataConnection(device)
if err != nil {
_ = virtualPort.Close()
return nil, nil, fmt.Errorf(
"open data connection for %q: %w",
device.ID,
err,
)
}
return virtualPort, conn, nil
}
///////////////////////////////////////////////////////////////////////////////
// Bridge connection
///////////////////////////////////////////////////////////////////////////////
// connectBridge creates the local virtual port, opens the corresponding
// remote TCP data connection and creates the data bridge.
//
// The caller owns the returned bridge and its endpoints.
func connectBridge(
c *Client,
manager *VirtualPortManager,
virtualPortConfig config.VirtualPortConfig,
devices []transport.RemoteDeviceInfo,
) (*Bridge, *ManagedVirtualPort, net.Conn, error) {
virtualPort, conn, err := connectVirtualPort(
c,
manager,
virtualPortConfig,
devices,
)
if err != nil {
return nil, nil, nil, err
}
bridge, err := NewBridge(virtualPort, conn)
if err != nil {
_ = conn.Close()
_ = virtualPort.Close()
return nil, nil, nil, fmt.Errorf(
"create bridge for %q: %w",
virtualPortConfig.RemoteDevice,
err,
)
}
return bridge, virtualPort, conn, nil
}