Files
SerialTerminalForWindowsTer…/forwarding.go
T
JiXieShi e0de872740 refactor: extract pkg/charset and internal/event packages
Extract ConvertChunk/FormatHexFrame into pkg/charset (zero external deps).
Extract UIEvent/UIEventKind/UIPanelKind types into internal/event.
Update all references across main package to use qualified imports.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 02:35:30 +08:00

312 lines
5.8 KiB
Go

package main
import (
"fmt"
"net"
"sort"
"sync"
"sync/atomic"
"time"
)
type ForwardStats struct {
ReadBytes uint64
WrittenBytes uint64
LastError string
}
type ForwardTarget struct {
ID int
Mode FoeWardMode
Address string
Enabled bool
Connected bool
CreatedAt time.Time
conn net.Conn
stats ForwardStats
mu sync.Mutex
closeCh chan struct{}
closed bool
}
type ForwardSnapshot struct {
ID int
Mode string
Address string
Enabled bool
Connected bool
ReadBytes uint64
WriteByte uint64
LastError string
}
type ForwardManager struct {
mu sync.RWMutex
targets map[int]*ForwardTarget
nextID int
writeToSerial func([]byte) error
notify func(string, ...any)
onInbound func(int, []byte)
}
func NewForwardManager(writeToSerial func([]byte) error, notify func(string, ...any)) *ForwardManager {
return &ForwardManager{
targets: make(map[int]*ForwardTarget),
nextID: 1,
writeToSerial: writeToSerial,
notify: notify,
}
}
func (m *ForwardManager) SetInboundReporter(fn func(int, []byte)) {
m.mu.Lock()
defer m.mu.Unlock()
m.onInbound = fn
}
func (m *ForwardManager) Add(mode FoeWardMode, address string) (int, error) {
if mode == NOT {
return 0, fmt.Errorf("forward mode cannot be none")
}
t := &ForwardTarget{
Mode: mode,
Address: address,
Enabled: true,
CreatedAt: time.Now(),
closeCh: make(chan struct{}),
}
conn, err := net.Dial(mode.Network(), address)
if err != nil {
t.stats.LastError = err.Error()
return 0, err
}
t.conn = conn
t.Connected = true
m.mu.Lock()
t.ID = m.nextID
m.nextID++
m.targets[t.ID] = t
m.mu.Unlock()
go m.readLoop(t, conn, t.closeCh)
m.notify("[forward] #%d %s %s connected", t.ID, t.Mode.String(), t.Address)
return t.ID, nil
}
func (m *ForwardManager) readLoop(t *ForwardTarget, conn net.Conn, stop <-chan struct{}) {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n > 0 {
atomic.AddUint64(&t.stats.ReadBytes, uint64(n))
chunk := make([]byte, n)
copy(chunk, buf[:n])
if wErr := m.writeToSerial(chunk); wErr != nil {
t.stats.LastError = wErr.Error()
m.notify("[forward] #%d write serial error: %v", t.ID, wErr)
} else if m.onInbound != nil {
m.onInbound(t.ID, chunk)
}
}
if err != nil {
t.mu.Lock()
if t.conn == conn {
t.Connected = false
}
t.stats.LastError = err.Error()
t.mu.Unlock()
m.notify("[forward] #%d disconnected: %v", t.ID, err)
return
}
select {
case <-stop:
return
default:
}
}
}
func (m *ForwardManager) Remove(id int) error {
m.mu.Lock()
t, ok := m.targets[id]
if !ok {
m.mu.Unlock()
return fmt.Errorf("forward #%d not found", id)
}
delete(m.targets, id)
m.mu.Unlock()
t.close()
m.notify("[forward] #%d removed", id)
return nil
}
func (m *ForwardManager) Enable(id int) error {
m.mu.RLock()
t, ok := m.targets[id]
m.mu.RUnlock()
if !ok {
return fmt.Errorf("forward #%d not found", id)
}
t.mu.Lock()
defer t.mu.Unlock()
if t.Enabled && t.Connected {
return nil
}
conn, err := net.Dial(t.Mode.Network(), t.Address)
if err != nil {
t.stats.LastError = err.Error()
return err
}
t.Enabled = true
t.Connected = true
t.conn = conn
t.closeCh = make(chan struct{})
t.closed = false
go m.readLoop(t, conn, t.closeCh)
m.notify("[forward] #%d enabled", id)
return nil
}
func (m *ForwardManager) Update(id int, mode FoeWardMode, address string) error {
if mode == NOT {
return fmt.Errorf("forward mode cannot be none")
}
m.mu.RLock()
t, ok := m.targets[id]
m.mu.RUnlock()
if !ok {
return fmt.Errorf("forward #%d not found", id)
}
t.mu.Lock()
wasEnabled := t.Enabled
t.Mode = mode
t.Address = address
t.mu.Unlock()
// Restart the target to apply new mode/address when enabled.
t.close()
if !wasEnabled {
m.notify("[forward] #%d updated (disabled)", id)
return nil
}
return m.Enable(id)
}
func (m *ForwardManager) Disable(id int) error {
m.mu.RLock()
t, ok := m.targets[id]
m.mu.RUnlock()
if !ok {
return fmt.Errorf("forward #%d not found", id)
}
t.mu.Lock()
t.Enabled = false
t.mu.Unlock()
t.close()
m.notify("[forward] #%d disabled", id)
return nil
}
func (m *ForwardManager) Broadcast(data []byte) {
if len(data) == 0 {
return
}
m.mu.RLock()
items := make([]*ForwardTarget, 0, len(m.targets))
for _, t := range m.targets {
items = append(items, t)
}
m.mu.RUnlock()
for _, t := range items {
if !t.Enabled || !t.Connected || t.conn == nil {
continue
}
n, err := t.conn.Write(data)
if err != nil {
t.stats.LastError = err.Error()
m.notify("[forward] #%d write error: %v", t.ID, err)
continue
}
atomic.AddUint64(&t.stats.WrittenBytes, uint64(n))
}
}
func (m *ForwardManager) List() []ForwardSnapshot {
m.mu.RLock()
items := make([]ForwardSnapshot, 0, len(m.targets))
for _, t := range m.targets {
items = append(items, ForwardSnapshot{
ID: t.ID,
Mode: t.Mode.String(),
Address: t.Address,
Enabled: t.Enabled,
Connected: t.Connected,
ReadBytes: atomic.LoadUint64(&t.stats.ReadBytes),
WriteByte: atomic.LoadUint64(&t.stats.WrittenBytes),
LastError: t.stats.LastError,
})
}
m.mu.RUnlock()
sort.Slice(items, func(i, j int) bool {
return items[i].ID < items[j].ID
})
return items
}
func (m *ForwardManager) Close() {
m.mu.Lock()
items := make([]*ForwardTarget, 0, len(m.targets))
for _, t := range m.targets {
items = append(items, t)
}
m.targets = map[int]*ForwardTarget{}
m.mu.Unlock()
for _, t := range items {
t.close()
}
}
func (t *ForwardTarget) close() {
t.mu.Lock()
if t.closed {
t.mu.Unlock()
return
}
t.closed = true
ch := t.closeCh
conn := t.conn
t.conn = nil
t.Connected = false
t.mu.Unlock()
if ch != nil {
close(ch)
}
if conn != nil {
_ = conn.Close()
}
}