634b5a7bd0
- model: SimNet protocol fields + NormalizeSimnet; type+port protocol uniqueness - server config: OmnXT runtime config delivery via compatible() simnet case - credentials: derive per-user psk/key_id from subscription (pkg/simnet), no new table - subscription: adapter buildOmnxtSimnetConfigs + base64 buildOmnxtProtocolLinks + OmnXT SimNet application (migration 02161) - UA gating: hide experimental protocols from non first-party clients (download + JSON node-list) - admin: normalize simnet on create/update and on GET responses - tests: 21 simnet unit tests; full suite green
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
// Package simnet holds shared, stateless helpers for the simnet protocol.
|
|
//
|
|
// The per-user simnet credentials are DERIVED from the existing user_subscribe
|
|
// record (id + uuid), not stored in a dedicated table. This mirrors the Pro
|
|
// reference (NPanel-backend internal/data/delivery_node.go deriveSimnetUserPSK
|
|
// and internal/biz/public/subscription/template.go key_id derivation), and is
|
|
// required for wire compatibility with OmnXT / SlagClient:
|
|
//
|
|
// - key_id MUST equal the user_subscribe.id (the node's StaticKeyResolver /
|
|
// assembler identifies the user by this id in the handshake frame header).
|
|
// - psk is the user's subscription UUID normalized to lowercase 32-hex.
|
|
package simnet
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"strings"
|
|
)
|
|
|
|
// serverKeyID is the reserved key id for the server-side PSK (AF derivation).
|
|
const serverKeyID = 0
|
|
|
|
// DeriveKeyID maps a user_subscribe.id to the simnet key_id.
|
|
// It matches the reference: subscribeID % (2^31-1), avoiding 0 which collides
|
|
// with the server key id.
|
|
func DeriveKeyID(subscribeID int64) int {
|
|
keyID := int(subscribeID % (1<<31 - 1))
|
|
if keyID == serverKeyID {
|
|
keyID = 1
|
|
}
|
|
return keyID
|
|
}
|
|
|
|
// DeriveUserPSK normalizes a subscription UUID/password into the per-user PSK.
|
|
// - canonical UUID -> lowercase hex without dashes (32 chars)
|
|
// - already 32-char ASCII hex -> lowercased
|
|
// - otherwise -> hex-encoded bytes of the raw value
|
|
func DeriveUserPSK(value string) string {
|
|
trimmed := strings.TrimSpace(value)
|
|
if isCanonicalUUID(trimmed) {
|
|
return strings.ToLower(strings.ReplaceAll(trimmed, "-", ""))
|
|
}
|
|
if len(trimmed) == 32 && isASCIIHex(trimmed) {
|
|
return strings.ToLower(trimmed)
|
|
}
|
|
return hex.EncodeToString([]byte(trimmed))
|
|
}
|
|
|
|
func isCanonicalUUID(value string) bool {
|
|
if len(value) != 36 {
|
|
return false
|
|
}
|
|
for idx, ch := range value {
|
|
switch idx {
|
|
case 8, 13, 18, 23:
|
|
if ch != '-' {
|
|
return false
|
|
}
|
|
default:
|
|
if !isASCIIHexRune(ch) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isASCIIHex(value string) bool {
|
|
if value == "" {
|
|
return false
|
|
}
|
|
for _, ch := range value {
|
|
if !isASCIIHexRune(ch) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isASCIIHexRune(ch rune) bool {
|
|
return (ch >= '0' && ch <= '9') ||
|
|
(ch >= 'a' && ch <= 'f') ||
|
|
(ch >= 'A' && ch <= 'F')
|
|
}
|
|
|
|
// officialClientKeywords are User-Agent substrings of first-party clients/SDKs
|
|
// allowed to receive experimental protocols. Mirrors the Pro reference
|
|
// (internal/biz/public/subscribe/subscribe.go officialClientKeywords).
|
|
var officialClientKeywords = []string{"omnxt", "slag/", "slaglab"}
|
|
|
|
// experimentalProtocols must be hidden from non-official clients.
|
|
var experimentalProtocols = map[string]struct{}{
|
|
"simnet": {},
|
|
}
|
|
|
|
// ClientSupportsExperimental reports whether the client UA is a first-party
|
|
// client permitted to receive experimental protocols (simnet).
|
|
func ClientSupportsExperimental(userAgent string) bool {
|
|
ua := strings.ToLower(userAgent)
|
|
for _, kw := range officialClientKeywords {
|
|
if strings.Contains(ua, kw) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsExperimentalProtocol reports whether a protocol type is experimental
|
|
// (hidden from non-official clients).
|
|
func IsExperimentalProtocol(protocol string) bool {
|
|
_, ok := experimentalProtocols[strings.ToLower(strings.TrimSpace(protocol))]
|
|
return ok
|
|
}
|