package logger import ( "sort" "sync" "time" ) // Device je sve sto pamtimo o jednom uredjaju. Sve je u memoriji i sve je // ograniceno po velicini - dnevnik ide u fajlove, ne ovde. type Device struct { Key string Name string MAC string IP string // adresa koju uredjaj sam prijavi (heartbeat) SourceIP string // adresa sa koje je paket stvarno stigao FW string Serial string Identified bool // znamo li ko je (zaglavlje/heartbeat) ili samo nagadjamo po IP-u FirstSeen time.Time LastSeen time.Time LastHeartbeat time.Time Heartbeats uint64 LogPackets uint64 LogLines uint64 IntervalSec int // izmeren razmak izmedju dva heartbeata (0 = jos ne znamo) LastLine string // poslednja linija dnevnika, samo za pregled } // Registry cuva uredjaje u memoriji. Broj uredjaja je ogranicen jer paket moze // poslati bilo ko - bez granice bi jedan izvor sa laznim MAC-ovima pojeo RAM. type Registry struct { mu sync.RWMutex devices map[string]*Device maxDevices int defaultHb int // pretpostavljeni interval kad uredjaj nikad nije poslao heartbeat } func NewRegistry(maxDevices, defaultHbSec int) *Registry { if maxDevices <= 0 { maxDevices = 500 } if defaultHbSec <= 0 { defaultHbSec = 60 } return &Registry{ devices: make(map[string]*Device), maxDevices: maxDevices, defaultHb: defaultHbSec, } } // Update ubacuje paket u registar i vraca kljuc uredjaja (i da li je primljen). // Paket koji nije prepoznat se ignorise. func (r *Registry) Update(p Packet, now time.Time) (string, bool) { if p.Kind == KindGarbage { return "", false } key := DeviceKey(p.MAC, p.SourceIP) r.mu.Lock() defer r.mu.Unlock() d, ok := r.devices[key] if !ok { if len(r.devices) >= r.maxDevices { r.evictOldestLocked() } d = &Device{Key: key, FirstSeen: now} r.devices[key] = d } d.LastSeen = now d.SourceIP = p.SourceIP if p.Name != "" { d.Name = p.Name } if p.MAC != "" { d.MAC = p.MAC } if p.FW != "" { d.FW = p.FW } if p.Serial != "" { d.Serial = p.Serial } if p.Identified { d.Identified = true } switch p.Kind { case KindHeartbeat: if p.IP != "" { d.IP = p.IP } // Interval ne dolazi u paketu - meri se izmedju dva javljanja. // Uzima se poslednji razmak, uz clamp da jedan izgubljen paket ili // restart uredjaja ne razvuku prag na sate. if !d.LastHeartbeat.IsZero() { if sec := int(now.Sub(d.LastHeartbeat).Seconds()); sec >= 5 && sec <= 3600 { d.IntervalSec = sec } } d.LastHeartbeat = now d.Heartbeats++ case KindLog: d.LogPackets++ d.LogLines += uint64(len(p.Lines)) if n := len(p.Lines); n > 0 { d.LastLine = p.Lines[n-1] } } return key, true } // evictOldestLocked izbacuje uredjaj koji se najduze nije javio. Poziva se samo // kad je registar pun. func (r *Registry) evictOldestLocked() { var oldestKey string var oldest time.Time for k, d := range r.devices { if oldestKey == "" || d.LastSeen.Before(oldest) { oldestKey, oldest = k, d.LastSeen } } if oldestKey != "" { delete(r.devices, oldestKey) } } // Interval vraca interval po kom se procenjuje da li je uredjaj tih. func (r *Registry) Interval(d *Device) int { if d.IntervalSec > 0 { return d.IntervalSec } return r.defaultHb } // Stale je tacno kad se uredjaj nije javio duze od 3x svog intervala. func (r *Registry) Stale(d *Device, now time.Time) bool { limit := time.Duration(3*r.Interval(d)) * time.Second return now.Sub(d.LastSeen) > limit } // Snapshot vraca kopije uredjaja, sortirane: prvo tihi (da bode oci), pa po // nazivu/kljucu. func (r *Registry) Snapshot(now time.Time) []Device { r.mu.RLock() out := make([]Device, 0, len(r.devices)) for _, d := range r.devices { out = append(out, *d) } r.mu.RUnlock() sort.Slice(out, func(i, j int) bool { si, sj := r.Stale(&out[i], now), r.Stale(&out[j], now) if si != sj { return si } ni, nj := out[i].Name, out[j].Name if ni == "" { ni = out[i].Key } if nj == "" { nj = out[j].Key } if ni != nj { return ni < nj } return out[i].Key < out[j].Key }) return out } // Get vraca kopiju jednog uredjaja. func (r *Registry) Get(key string) (Device, bool) { r.mu.RLock() defer r.mu.RUnlock() d, ok := r.devices[key] if !ok { return Device{}, false } return *d, true } // Count vraca broj uredjaja u registru. func (r *Registry) Count() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.devices) }