rs2322tcp/internal/client/virtual_port_manager.go

183 lines
4.9 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/virtual_port_manager.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Verwaltung und Auswahl der für rs2322tcp reservierten virtuellen
* seriellen Ports.
*
* Der VirtualPortManager verwaltet den für rs2322tcp vorgesehenen Bereich
* virtueller serieller Schnittstellen.
*
* Die konkrete Darstellung eines virtuellen Ports ist plattformabhängig.
* Unter Linux werden beispielsweise /dev/ttyUSBxxx-Schnittstellen verwendet,
* während unter Windows COMxxx-Schnittstellen verwendet werden.
*
* Die eigentliche Erzeugung beziehungsweise Bereitstellung des seriellen
* Ports erfolgt ebenfalls plattformabhängig und ist nicht Bestandteil dieses
* Managers.
*
* Ports können entweder automatisch über den nächsten freien Port oder
* gezielt über eine vorgegebene Portnummer reserviert werden.
* ============================================================================
*/
package client
import (
"fmt"
"sync"
"git.lang-dieter.de/rs2322tcp/internal/config"
)
///////////////////////////////////////////////////////////////////////////////
// VirtualPortManager
///////////////////////////////////////////////////////////////////////////////
// VirtualPortManager verwaltet den für rs2322tcp reservierten Bereich
// virtueller serieller Schnittstellen.
//
// Der reservierte Portbereich und die Belegungstabelle werden durch einen
// Mutex geschützt, da mehrere Bridges beziehungsweise Goroutinen gleichzeitig
// virtuelle Ports öffnen und freigeben können.
//
// Die konkrete Darstellung des sichtbaren Portnamens wird über
// virtualPortPath() von der jeweiligen Plattform bestimmt.
type VirtualPortManager struct {
mu sync.Mutex
first int
last int
used map[int]bool
}
///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
// NewVirtualPortManager erzeugt einen VirtualPortManager aus dem
// konfigurierten Portbereich.
func NewVirtualPortManager(
cfg config.VirtualPortRangeConfig,
) *VirtualPortManager {
return &VirtualPortManager{
first: cfg.First,
last: cfg.Last,
used: make(map[int]bool),
}
}
///////////////////////////////////////////////////////////////////////////////
// Port path
///////////////////////////////////////////////////////////////////////////////
// PortPath erzeugt den für die externe Software sichtbaren Gerätenamen
// für eine Portnummer.
//
// Die konkrete Darstellung ist plattformabhängig:
//
// Linux /dev/ttyUSB100
// Windows COM100
//
// Die Plattformimplementierung befindet sich in
// virtual_port_path_linux.go beziehungsweise virtual_port_path_windows.go.
func (m *VirtualPortManager) PortPath(number int) string {
if m == nil {
return ""
}
return virtualPortPath(number)
}
///////////////////////////////////////////////////////////////////////////////
// Reservation
///////////////////////////////////////////////////////////////////////////////
// Reserve reserviert den nächsten freien virtuellen Port.
//
// Es wird immer mit dem kleinsten freien Port im konfigurierten Bereich
// begonnen.
//
// Diese Methode bleibt für Fälle erhalten, in denen keine bestimmte
// Portnummer vorgegeben ist.
func (m *VirtualPortManager) Reserve() (int, error) {
if m == nil {
return 0, fmt.Errorf("virtual port manager is nil")
}
m.mu.Lock()
defer m.mu.Unlock()
for number := m.first; number <= m.last; number++ {
if m.used[number] {
continue
}
m.used[number] = true
return number, nil
}
return 0, fmt.Errorf(
"no free virtual port in range %d-%d",
m.first,
m.last,
)
}
// ReserveSpecific reserviert exakt den angegebenen virtuellen Port.
//
// Der Port muss innerhalb des konfigurierten Bereichs liegen und darf
// momentan nicht bereits reserviert sein.
//
// Diese Methode wird für konfigurierte Zuordnungen verwendet, bei denen
// die Portnummer aus client.json verbindlich ist.
func (m *VirtualPortManager) ReserveSpecific(number int) error {
if m == nil {
return fmt.Errorf("virtual port manager is nil")
}
m.mu.Lock()
defer m.mu.Unlock()
if number < m.first || number > m.last {
return fmt.Errorf(
"virtual port %d is outside configured range %d-%d",
number,
m.first,
m.last,
)
}
if m.used[number] {
return fmt.Errorf(
"virtual port %d is already reserved",
number,
)
}
m.used[number] = true
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Release
///////////////////////////////////////////////////////////////////////////////
// Release gibt einen zuvor reservierten Port wieder frei.
func (m *VirtualPortManager) Release(number int) {
if m == nil {
return
}
m.mu.Lock()
defer m.mu.Unlock()
delete(m.used, number)
}