safesight-edge/agent/internal/discovery/discovery.go
2026-04-18 12:16:10 +08:00

131 lines
2.9 KiB
Go

package discovery
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"strings"
"time"
"rk3588sys/agent/internal/sysinfo"
)
const Magic = "RK3588SYS_DISCOVERY_V1"
type Responder struct {
Port int
DeviceID string
DeviceName string
Hostname string
AgentPort int
MediaPort int
Version string
BuildID string
BuildType string
GitSHA string
}
type discoverReq struct {
Type string `json:"type"`
ReqID string `json:"req_id"`
ReplyPort int `json:"reply_port"`
}
type discoverReply struct {
Type string `json:"type"`
ReqID string `json:"req_id"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
AgentPort int `json:"agent_port"`
MediaPort int `json:"media_port"`
Version string `json:"version"`
BuildID string `json:"build_id"`
BuildType string `json:"build_type"`
GitSHA string `json:"git_sha"`
UptimeSec int64 `json:"uptime_sec"`
}
func (r *Responder) Run(ctx context.Context) error {
addr := &net.UDPAddr{IP: net.IPv4zero, Port: r.Port}
conn, err := net.ListenUDP("udp4", addr)
if err != nil {
return fmt.Errorf("listen udp :%d: %w", r.Port, err)
}
defer conn.Close()
buf := make([]byte, 2048)
for {
select {
case <-ctx.Done():
return nil
default:
}
_ = conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
n, remote, err := conn.ReadFromUDP(buf)
if err != nil {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
continue
}
return err
}
if remote == nil || remote.IP == nil {
continue
}
msg := string(buf[:n])
line1, line2, ok := split2lines(msg)
if !ok || strings.TrimSpace(line1) != Magic {
continue
}
var req discoverReq
if err := json.Unmarshal([]byte(line2), &req); err != nil {
continue
}
if req.Type != "discover" || strings.TrimSpace(req.ReqID) == "" {
continue
}
remoteIP := remote.IP.String()
ip := sysinfo.PrimaryIPv4()
if v, err := sysinfo.LocalIPTo(remoteIP); err == nil && v != "" {
ip = v
}
reply := discoverReply{
Type: "discover_reply",
ReqID: req.ReqID,
DeviceID: r.DeviceID,
DeviceName: r.DeviceName,
Hostname: r.Hostname,
IP: ip,
AgentPort: r.AgentPort,
MediaPort: r.MediaPort,
Version: r.Version,
BuildID: r.BuildID,
BuildType: r.BuildType,
GitSHA: r.GitSHA,
UptimeSec: sysinfo.UptimeSec(),
}
b, _ := json.Marshal(reply)
out := Magic + "\n" + string(b) + "\n"
_, _ = conn.WriteToUDP([]byte(out), remote)
}
}
func split2lines(s string) (string, string, bool) {
idx := strings.IndexByte(s, '\n')
if idx < 0 {
return "", "", false
}
line1 := strings.TrimRight(s[:idx], "\r")
line2 := strings.TrimSpace(strings.TrimRight(s[idx+1:], "\r"))
if line2 == "" {
return "", "", false
}
return line1, line2, true
}