188 lines
4.8 KiB
Go
188 lines
4.8 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.
|
|
*
|
|
* Optional können nach dem Aufbau der Datenverbindung konfigurierte
|
|
* Startbytes an das entfernte Gerät gesendet werden.
|
|
* ============================================================================
|
|
*/
|
|
package client
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
|
"git.lang-dieter.de/rs2322tcp/internal/transport"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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.
|
|
//
|
|
// If StartupBytes are configured, they are sent once immediately after the
|
|
// TCP data connection has been established. Invalid startup bytes do not
|
|
// abort the connection setup. A warning is printed and the connection
|
|
// continues normally.
|
|
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,
|
|
)
|
|
}
|
|
|
|
if err := sendStartupBytes(conn, virtualPortConfig); err != nil {
|
|
fmt.Printf(
|
|
"warning: startup bytes for %q were not sent: %v\n",
|
|
virtualPortConfig.RemoteDevice,
|
|
err,
|
|
)
|
|
}
|
|
|
|
return virtualPort, conn, nil
|
|
}
|
|
|
|
// sendStartupBytes sends the optional configured startup byte sequence over
|
|
// the already established TCP data connection.
|
|
//
|
|
// An empty StartupBytes configuration does not send anything and is not an
|
|
// error.
|
|
//
|
|
// The configured value may contain spaces between hexadecimal bytes, for
|
|
// example:
|
|
//
|
|
// 57 00 00 00 00 00 00 00 00 00 00 1F 20
|
|
func sendStartupBytes(
|
|
conn net.Conn,
|
|
virtualPortConfig config.VirtualPortConfig,
|
|
) error {
|
|
startupBytes := strings.TrimSpace(virtualPortConfig.StartupBytes)
|
|
if startupBytes == "" {
|
|
return nil
|
|
}
|
|
|
|
hexText := strings.Join(strings.Fields(startupBytes), "")
|
|
|
|
data, err := hex.DecodeString(hexText)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid hexadecimal byte sequence: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
|
|
written, err := conn.Write(data)
|
|
if err != nil {
|
|
return fmt.Errorf("send startup bytes: %w", err)
|
|
}
|
|
|
|
if written != len(data) {
|
|
return fmt.Errorf(
|
|
"send startup bytes: wrote %d of %d bytes",
|
|
written,
|
|
len(data),
|
|
)
|
|
}
|
|
|
|
return 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
|
|
}
|