Start the Go rewrite: jsjson, awg-ctrl port, differential bench

- internal/jsjson: JSON.stringify-compatible encoder and order-preserving decoder,
  tested against fixtures produced by Node itself
- internal/ctrl: awg-ctrl in Go (params, vpn:// keys, users, peers, sync, 3.1
  upgrade, CAKE shaping, metrics, monitor job, Express-compatible HTTP layer)
- internal/iauth, internal/logx: internal Ed25519 tokens and logging
- tools/diffbench: Docker bench with stubbed awg/ip/tc/nft that runs one scenario
  against the TS and Go implementations for comparison
This commit is contained in:
2026-10-14 13:27:24 +00:00
parent 53d5a08bbd
commit 59e076c1de
28 changed files with 4960 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
package ctrl
import (
"math"
"regexp"
"strconv"
"strings"
"github.com/maeneko/forgetting/internal/jsjson"
)
var reOwner = regexp.MustCompile(`^[\w/.-]{0,64}$`)
// checkPubKeyAny — checkPubKey для значения из JSON (не строка → тот же 400).
func checkPubKeyAny(v any) (string, error) {
k, ok := v.(string)
if !ok {
return "", httpErr(400, "Неверный публичный ключ")
}
return checkPubKey(k)
}
func checkPubKeys(arr []any) ([]string, error) {
out := make([]string, 0, len(arr))
for _, v := range arr {
k, err := checkPubKeyAny(v)
if err != nil {
return nil, err
}
out = append(out, k)
}
return out, nil
}
// jsToNumber — Number(v) для значений из JSON.
func jsToNumber(v any) float64 {
switch x := v.(type) {
case nil:
return 0
case float64:
return x
case bool:
if x {
return 1
}
return 0
case string:
return jsNumber(x)
}
return math.NaN()
}
func (s *Server) knownPubKeys() (map[string]bool, error) {
known := map[string]bool{}
rows, err := s.db.Query("SELECT pub_key FROM users")
if err != nil {
return nil, err
}
for rows.Next() {
var k string
rows.Scan(&k)
known[k] = true
}
rows.Close()
peers, err := s.peersAll()
if err != nil {
return nil, err
}
for _, p := range peers {
known[p.PubKey] = true
}
return known, nil
}
func (s *Server) isKnownPub(k string) (bool, error) {
if p, err := s.peerGet(k); err != nil || p != nil {
return p != nil, err
}
var n int
if err := s.db.QueryRow("SELECT COUNT(*) FROM users WHERE pub_key = ?", k).Scan(&n); err != nil {
return false, err
}
return n > 0, nil
}
// profileAwg — параметры для /api/profile в порядке CONFIG.awgParams (порядок объявления в
// DEFAULT_AWG_PARAMS), без пустых значений; числа — строками.
func profileAwg(p Params) jsjson.Object {
o := jsjson.Object{}
add := func(k, v string) {
if v != "" {
o = append(o, jsjson.KV{Key: k, Val: v})
}
}
itoa := strconv.Itoa
add("Jc", itoa(p.Jc))
add("Jmin", itoa(p.Jmin))
add("Jmax", itoa(p.Jmax))
add("S1", itoa(p.S1))
add("S2", itoa(p.S2))
add("S3", itoa(p.S3))
add("S4", itoa(p.S4))
add("H1", p.H1)
add("H2", p.H2)
add("H3", p.H3)
add("H4", p.H4)
add("I1", p.I1)
add("I2", p.I2)
add("I3", p.I3)
add("I4", p.I4)
add("I5", p.I5)
for _, k := range AWG3Keys {
add(k, p.A3[k])
}
return o
}
var _ = strings.TrimSpace