125 lines
2.0 KiB
Go
125 lines
2.0 KiB
Go
|
|
package sacn
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Source struct {
|
||
|
|
CID [16]byte
|
||
|
|
SourceName string
|
||
|
|
IP net.IP
|
||
|
|
Universes []uint16
|
||
|
|
LastSeen time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
type Discovery struct {
|
||
|
|
sources map[string]*Source
|
||
|
|
mu sync.RWMutex
|
||
|
|
onChange func(*Source)
|
||
|
|
done chan struct{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewDiscovery() *Discovery {
|
||
|
|
return &Discovery{
|
||
|
|
sources: map[string]*Source{},
|
||
|
|
done: make(chan struct{}),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) SetOnChange(fn func(*Source)) {
|
||
|
|
d.onChange = fn
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) HandleDiscoveryPacket(src *net.UDPAddr, pkt *DiscoveryPacket) {
|
||
|
|
d.mu.Lock()
|
||
|
|
defer d.mu.Unlock()
|
||
|
|
|
||
|
|
cidStr := FormatCID(pkt.CID)
|
||
|
|
|
||
|
|
source, exists := d.sources[cidStr]
|
||
|
|
if !exists {
|
||
|
|
source = &Source{
|
||
|
|
CID: pkt.CID,
|
||
|
|
}
|
||
|
|
d.sources[cidStr] = source
|
||
|
|
}
|
||
|
|
|
||
|
|
source.SourceName = pkt.SourceName
|
||
|
|
source.IP = src.IP
|
||
|
|
source.Universes = pkt.Universes
|
||
|
|
source.LastSeen = time.Now()
|
||
|
|
|
||
|
|
if d.onChange != nil {
|
||
|
|
d.onChange(source)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) GetSource(cid string) *Source {
|
||
|
|
d.mu.RLock()
|
||
|
|
defer d.mu.RUnlock()
|
||
|
|
return d.sources[cid]
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) GetSourceByIP(ip net.IP) *Source {
|
||
|
|
d.mu.RLock()
|
||
|
|
defer d.mu.RUnlock()
|
||
|
|
|
||
|
|
for _, source := range d.sources {
|
||
|
|
if source.IP != nil && source.IP.Equal(ip) {
|
||
|
|
return source
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) GetAllSources() []*Source {
|
||
|
|
d.mu.RLock()
|
||
|
|
defer d.mu.RUnlock()
|
||
|
|
|
||
|
|
result := make([]*Source, 0, len(d.sources))
|
||
|
|
for _, source := range d.sources {
|
||
|
|
result = append(result, source)
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) Expire() {
|
||
|
|
d.mu.Lock()
|
||
|
|
defer d.mu.Unlock()
|
||
|
|
|
||
|
|
cutoff := time.Now().Add(-60 * time.Second)
|
||
|
|
for cid, source := range d.sources {
|
||
|
|
if source.LastSeen.Before(cutoff) {
|
||
|
|
delete(d.sources, cid)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) StartCleanup() {
|
||
|
|
go d.cleanupLoop()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) Stop() {
|
||
|
|
select {
|
||
|
|
case <-d.done:
|
||
|
|
default:
|
||
|
|
close(d.done)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (d *Discovery) cleanupLoop() {
|
||
|
|
ticker := time.NewTicker(30 * time.Second)
|
||
|
|
defer ticker.Stop()
|
||
|
|
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-d.done:
|
||
|
|
return
|
||
|
|
case <-ticker.C:
|
||
|
|
d.Expire()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|