172 lines
4 KiB
Go
172 lines
4 KiB
Go
/*
|
|
* ============================================================================
|
|
* Projekt.....: rs2322tcp
|
|
* Datei.......: control.go
|
|
* Copyright (C) 2026 Dieter Lang
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Beschreibung:
|
|
* Client-seitige Control-Verbindung zum rs2322tcp-Server.
|
|
* Baut die TCP-Control-Verbindung auf, führt den Protokoll-Handshake durch
|
|
* und liest die vom Server bereitgestellten Geräteinformationen.
|
|
* ============================================================================
|
|
*/
|
|
package client
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
|
|
"git.lang-dieter.de/rs2322tcp/internal/transport"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Client
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Client represents a connection to an rs2322tcp server.
|
|
type Client struct {
|
|
conn net.Conn
|
|
reader *bufio.Reader
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Constructor
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// New creates a new client and connects to the specified server address.
|
|
//
|
|
// The address must be in the form "host:port".
|
|
func New(address string) (*Client, error) {
|
|
if address == "" {
|
|
return nil, fmt.Errorf("server address is empty")
|
|
}
|
|
|
|
conn, err := net.Dial("tcp", address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"connect to server %q: %w",
|
|
address,
|
|
err,
|
|
)
|
|
}
|
|
|
|
client := &Client{
|
|
conn: conn,
|
|
reader: bufio.NewReader(conn),
|
|
}
|
|
|
|
if err := client.hello(); err != nil {
|
|
_ = conn.Close()
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Properties
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Conn returns the underlying control connection.
|
|
//
|
|
// It returns nil if the client is nil or has already been closed.
|
|
func (c *Client) Conn() net.Conn {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
return c.conn
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Handshake
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
func (c *Client) hello() error {
|
|
if c == nil || c.conn == nil {
|
|
return fmt.Errorf("client is not connected")
|
|
}
|
|
|
|
if err := transport.WriteMessage(
|
|
c.conn,
|
|
transport.NewHello(),
|
|
); err != nil {
|
|
return fmt.Errorf("send hello: %w", err)
|
|
}
|
|
|
|
var response transport.HelloResponseMessage
|
|
|
|
if err := transport.ReadMessage(
|
|
c.reader,
|
|
&response,
|
|
); err != nil {
|
|
return fmt.Errorf("read hello response: %w", err)
|
|
}
|
|
|
|
if response.Type != transport.MessageHelloResponse {
|
|
return fmt.Errorf(
|
|
"unexpected hello response: %q",
|
|
response.Type,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Devices
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// GetDevices requests the configured remote devices from the server.
|
|
//
|
|
// The returned list contains the session-specific dynamic TCP data ports.
|
|
func (c *Client) GetDevices() ([]transport.RemoteDeviceInfo, error) {
|
|
if c == nil || c.conn == nil {
|
|
return nil, fmt.Errorf("client is not connected")
|
|
}
|
|
|
|
if err := transport.WriteMessage(
|
|
c.conn,
|
|
transport.NewGetDevices(),
|
|
); err != nil {
|
|
return nil, fmt.Errorf("send get-devices request: %w", err)
|
|
}
|
|
|
|
var response transport.DeviceListMessage
|
|
|
|
if err := transport.ReadMessage(
|
|
c.reader,
|
|
&response,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("read device list: %w", err)
|
|
}
|
|
|
|
if response.Type != transport.MessageDeviceList {
|
|
return nil, fmt.Errorf(
|
|
"unexpected device list response: %q",
|
|
response.Type,
|
|
)
|
|
}
|
|
|
|
return response.Devices, nil
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Close
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Close closes the client control connection.
|
|
func (c *Client) Close() error {
|
|
if c == nil || c.conn == nil {
|
|
return nil
|
|
}
|
|
|
|
err := c.conn.Close()
|
|
c.conn = nil
|
|
|
|
return err
|
|
}
|