235 lines
5.9 KiB
Go
235 lines
5.9 KiB
Go
package discovery
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"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
|
|
ConfigPath 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"`
|
|
ConfigID string `json:"config_id,omitempty"`
|
|
ConfigVersion string `json:"config_version,omitempty"`
|
|
BusinessName string `json:"business_name,omitempty"`
|
|
Template string `json:"template,omitempty"`
|
|
Profile string `json:"profile,omitempty"`
|
|
Overlays []string `json:"overlays,omitempty"`
|
|
InstanceName string `json:"instance_name,omitempty"`
|
|
InstanceDisplayName string `json:"instance_display_name,omitempty"`
|
|
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(),
|
|
}
|
|
if summary := readConfigMetadataSummary(r.ConfigPath); summary != nil {
|
|
reply.ConfigID = summary.ConfigID
|
|
reply.ConfigVersion = summary.ConfigVersion
|
|
reply.BusinessName = summary.BusinessName
|
|
reply.Template = summary.Template
|
|
reply.Profile = summary.Profile
|
|
reply.Overlays = copyStringSlice(summary.Overlays)
|
|
reply.InstanceName = summary.InstanceName
|
|
reply.InstanceDisplayName = summary.InstanceDisplayName
|
|
}
|
|
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
|
|
}
|
|
|
|
type configMetadataSummary struct {
|
|
ConfigID string
|
|
ConfigVersion string
|
|
BusinessName string
|
|
Template string
|
|
Profile string
|
|
Overlays []string
|
|
InstanceName string
|
|
InstanceDisplayName string
|
|
}
|
|
|
|
func readConfigMetadataSummary(path string) *configMetadataSummary {
|
|
if strings.TrimSpace(path) == "" {
|
|
return nil
|
|
}
|
|
b, err := os.ReadFile(filepath.Clean(path))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var root struct {
|
|
Metadata map[string]any `json:"metadata"`
|
|
}
|
|
if err := json.Unmarshal(b, &root); err != nil {
|
|
return nil
|
|
}
|
|
return metadataSummaryFromMap(root.Metadata)
|
|
}
|
|
|
|
func metadataSummaryFromMap(metadata map[string]any) *configMetadataSummary {
|
|
if len(metadata) == 0 {
|
|
return nil
|
|
}
|
|
summary := &configMetadataSummary{
|
|
ConfigID: stringValue(metadata["config_id"]),
|
|
ConfigVersion: stringValue(metadata["config_version"]),
|
|
BusinessName: stringValue(metadata["business_name"]),
|
|
Template: stringValue(metadata["template"]),
|
|
Profile: stringValue(metadata["profile"]),
|
|
Overlays: stringSliceValue(metadata["overlays"]),
|
|
}
|
|
if names := stringSliceValue(metadata["instance_names"]); len(names) > 0 {
|
|
summary.InstanceName = names[0]
|
|
}
|
|
if names := stringSliceValue(metadata["instance_display_names"]); len(names) > 0 {
|
|
summary.InstanceDisplayName = names[0]
|
|
}
|
|
return summary
|
|
}
|
|
|
|
func stringValue(v any) string {
|
|
s, _ := v.(string)
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
func stringSliceValue(v any) []string {
|
|
switch vv := v.(type) {
|
|
case []string:
|
|
return copyStringSlice(vv)
|
|
case []any:
|
|
out := make([]string, 0, len(vv))
|
|
for _, item := range vv {
|
|
if s, ok := item.(string); ok {
|
|
s = strings.TrimSpace(s)
|
|
if s != "" {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func copyStringSlice(v []string) []string {
|
|
if len(v) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]string, len(v))
|
|
copy(out, v)
|
|
return out
|
|
}
|