3588AdminBackend/internal/service/discovery.go

168 lines
4.5 KiB
Go

package service
import (
"encoding/json"
"net"
"strings"
"time"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"github.com/google/uuid"
)
const discoveryMagicV1 = "RK3588SYS_DISCOVERY_V1"
type DiscoveryService struct {
cfg *config.Config
registry *RegistryService
}
func NewDiscoveryService(cfg *config.Config, registry *RegistryService) *DiscoveryService {
return &DiscoveryService{
cfg: cfg,
registry: registry,
}
}
func (s *DiscoveryService) SearchDefault() ([]*models.Device, error) {
if s == nil || s.cfg == nil {
return nil, nil
}
timeoutMs := s.cfg.DiscoveryTimeoutMs
if timeoutMs <= 0 {
timeoutMs = 1200
}
return s.Search(timeoutMs)
}
func (s *DiscoveryService) Search(timeoutMs int) ([]*models.Device, error) {
reqID := uuid.NewString()
payload, _ := json.Marshal(map[string]interface{}{
"type": "discover",
"req_id": reqID,
"reply_port": 0,
})
msg := []byte(discoveryMagicV1 + "\n" + string(payload))
// Listen for replies
addr, err := net.ResolveUDPAddr("udp", ":0") // Random port for receiving
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
return nil, err
}
defer conn.Close()
// Send Broadcast to all interfaces
interfaces, err := net.Interfaces()
if err == nil {
for _, iface := range interfaces {
if iface.Flags&net.FlagBroadcast != 0 && iface.Flags&net.FlagUp != 0 {
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil {
// Calculate broadcast address
ip := ipnet.IP.To4()
mask := ipnet.Mask
broadcast := make(net.IP, len(ip))
for i := 0; i < len(ip); i++ {
broadcast[i] = ip[i] | ^mask[i]
}
broadcastAddr := &net.UDPAddr{
IP: broadcast,
Port: s.cfg.DiscoveryPort,
}
_, _ = conn.WriteToUDP(msg, broadcastAddr)
}
}
}
}
}
stop := time.After(time.Duration(timeoutMs) * time.Millisecond)
found := make(map[string]*models.Device)
for {
select {
case <-stop:
// Convert map to slice
list := make([]*models.Device, 0, len(found))
for _, d := range found {
list = append(list, d)
}
return list, nil
default:
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
buf := make([]byte, 2048)
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
continue
}
text := strings.TrimSpace(string(buf[:n]))
lines := strings.SplitN(text, "\n", 3)
if len(lines) < 2 {
continue
}
if strings.TrimSpace(lines[0]) != discoveryMagicV1 {
continue
}
var reply 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"`
GitSha string `json:"git_sha"`
UptimeSec int64 `json:"uptime_sec"`
InstanceName string `json:"instance_name"`
InstanceDisplayName string `json:"instance_display_name"`
ConfigID string `json:"config_id"`
ConfigVersion string `json:"config_version"`
Template string `json:"template"`
Profile string `json:"profile"`
Overlays []string `json:"overlays"`
}
if err := json.Unmarshal([]byte(strings.TrimSpace(lines[1])), &reply); err != nil {
continue
}
if reply.Type != "discover_reply" || reply.ReqID != reqID || reply.DeviceID == "" {
continue
}
dev := &models.Device{
DeviceID: reply.DeviceID,
DeviceName: reply.DeviceName,
Hostname: reply.Hostname,
IP: reply.IP,
AgentPort: reply.AgentPort,
MediaPort: reply.MediaPort,
Version: reply.Version,
BuildID: reply.BuildID,
GitSha: reply.GitSha,
UptimeSec: reply.UptimeSec,
InstanceName: reply.InstanceName,
InstanceDisplayName: reply.InstanceDisplayName,
}
if dev.IP == "" {
dev.IP = raddr.IP.String()
}
s.registry.UpdateDevice(dev)
found[dev.DeviceID] = dev
}
}
}