Files
redapricot/client/config.go
T
2026-07-15 14:28:58 +08:00

95 lines
2.0 KiB
Go

package client
import (
"encoding/json"
"fmt"
"os"
"strings"
)
// Protocol constants (mirror of the Java Protocol class; see PROTOCOL.md).
const (
IntentRedapricot = 17
ProtocolVersion = 767 // arbitrary; the hub ignores it
MagicControl = 0x01
MagicWorker = 0x02
CIDLen = 16
CtlSessionReady = 0x00
CtlRegister = 0x01
CtlUnregister = 0x02
CtlRegisterAck = 0x03
CtlControlRequest = 0x04
CtlPing = 0x05
CtlPong = 0x06
MuxSyn = 0x00
MuxData = 0x01
MuxFin = 0x02
MuxRst = 0x03
FrameError = 0x7F
SaturationThreshold = 8
)
// Mapping routes a registered pattern to a real destination.
type Mapping struct {
Pattern string `json:"pattern"`
Destination string `json:"destination"`
ProxyProtocol bool `json:"proxyProtocol"`
}
// Config is the client configuration (PROTOCOL.md §9.2).
type Config struct {
Server string `json:"server"`
PSK string `json:"psk"`
MaxConn int `json:"maxConn"`
PingIntervalMs int `json:"pingIntervalMs"`
Mappings []Mapping `json:"mappings"`
}
// LoadConfig reads and validates a JSON config file.
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var c Config
if err := json.Unmarshal(data, &c); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
if c.Server == "" {
return nil, fmt.Errorf("server is required")
}
if c.PSK == "" {
return nil, fmt.Errorf("psk is required")
}
if c.MaxConn < 1 {
c.MaxConn = 1
}
if c.MaxConn > 8 {
c.MaxConn = 8
}
if c.PingIntervalMs <= 0 {
c.PingIntervalMs = 20000
}
if len(c.Mappings) == 0 {
return nil, fmt.Errorf("at least one mapping is required")
}
return &c, nil
}
// NormalizeAddress matches the hub's normalization: lower-cased, FML-suffix and
// trailing-dot stripped.
func NormalizeAddress(addr string) string {
if i := strings.IndexByte(addr, 0); i >= 0 {
addr = addr[:i]
}
addr = strings.ToLower(addr)
addr = strings.TrimRight(addr, ".")
return addr
}