mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"time"
|
|
|
|
"quad4/reticulum-go/pkg/debug"
|
|
)
|
|
|
|
// OutgoingAnnounceQueue is the optional per-interface delayed TX list used
|
|
// when announce_cap would be exceeded. Matches Python Interface.announce_queue.
|
|
type OutgoingAnnounceQueue interface {
|
|
ShouldQueueAnnounce() bool
|
|
QueueOutgoingAnnounce(raw []byte, dest []byte, hops byte, emitted uint32)
|
|
NoteAnnounceSent(nbytes int)
|
|
AnnounceQueueLen() int
|
|
DropAnnounceQueue() int
|
|
NextOutgoingAnnounce() ([]byte, bool)
|
|
}
|
|
|
|
type queuedAnnounce struct {
|
|
dest [16]byte
|
|
queued time.Time
|
|
hops byte
|
|
emitted uint32
|
|
raw []byte
|
|
}
|
|
|
|
// SetAnnounceCap stores announce_cap from config as a fraction of bitrate.
|
|
// percent 0 keeps the 2 percent default. Values are config percents (1 to 100).
|
|
func (i *BaseInterface) SetAnnounceCap(percent float64) {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
if percent <= 0 {
|
|
i.announceCap = 0
|
|
return
|
|
}
|
|
i.announceCap = percent / 100.0
|
|
}
|
|
|
|
func (i *BaseInterface) announceCapFraction() float64 {
|
|
if i.announceCap > 0 {
|
|
return i.announceCap
|
|
}
|
|
return DefaultAnnounceCapFraction
|
|
}
|
|
|
|
// ShouldQueueAnnounce reports whether a forwarded announce must wait.
|
|
// Local origin (hops 0) is decided by the caller, matching Python Transport.outbound.
|
|
func (i *BaseInterface) ShouldQueueAnnounce() bool {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
if len(i.announceQueue) > 0 {
|
|
return true
|
|
}
|
|
if i.Bitrate <= 0 {
|
|
return true
|
|
}
|
|
return time.Now().Before(i.announceAllowedAt)
|
|
}
|
|
|
|
// QueueOutgoingAnnounce stores a forwarded announce for later TX.
|
|
// Same-destination entries keep the first unless emitted is strictly newer.
|
|
// Drops when the queue is already at MaxQueuedAnnounces.
|
|
func (i *BaseInterface) QueueOutgoingAnnounce(raw []byte, dest []byte, hops byte, emitted uint32) {
|
|
if i == nil || len(raw) == 0 {
|
|
return
|
|
}
|
|
entry := queuedAnnounce{
|
|
queued: time.Now(),
|
|
hops: hops,
|
|
emitted: emitted,
|
|
raw: append([]byte(nil), raw...),
|
|
}
|
|
copy(entry.dest[:], dest)
|
|
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
if len(i.announceQueue) >= MaxQueuedAnnounces {
|
|
return
|
|
}
|
|
for idx := range i.announceQueue {
|
|
if i.announceQueue[idx].dest == entry.dest {
|
|
if emitted > i.announceQueue[idx].emitted {
|
|
i.announceQueue[idx] = entry
|
|
}
|
|
return
|
|
}
|
|
}
|
|
i.announceQueue = append(i.announceQueue, entry)
|
|
if debug.Enabled(debug.DebugVerbose) {
|
|
debug.Log(debug.DebugVerbose, "Queued announce for later transmission",
|
|
"name", i.Name, "queue", len(i.announceQueue), "hops", hops)
|
|
}
|
|
}
|
|
|
|
// NoteAnnounceSent records announce_cap airtime after an immediate send.
|
|
func (i *BaseInterface) NoteAnnounceSent(nbytes int) {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
i.noteAnnounceSentLocked(nbytes)
|
|
}
|
|
|
|
func (i *BaseInterface) noteAnnounceSentLocked(nbytes int) {
|
|
capFrac := i.announceCapFraction()
|
|
if i.Bitrate <= 0 || nbytes <= 0 || capFrac <= 0 {
|
|
return
|
|
}
|
|
txTime := (float64(nbytes) * 8) / float64(i.Bitrate)
|
|
wait := txTime / capFrac
|
|
i.announceAllowedAt = time.Now().Add(time.Duration(wait * float64(time.Second)))
|
|
}
|
|
|
|
// AnnounceQueueLen is the number of announces waiting for announce_cap.
|
|
func (i *BaseInterface) AnnounceQueueLen() int {
|
|
i.Mutex.RLock()
|
|
defer i.Mutex.RUnlock()
|
|
return len(i.announceQueue)
|
|
}
|
|
|
|
// DropAnnounceQueue discards queued outgoing announces and returns how many were dropped.
|
|
func (i *BaseInterface) DropAnnounceQueue() int {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
n := len(i.announceQueue)
|
|
i.announceQueue = nil
|
|
return n
|
|
}
|
|
|
|
// NextOutgoingAnnounce pops the next announce that may be sent now.
|
|
// Prefer fewer hops, then older queue time. Returns false when empty, stale-only, or still capped.
|
|
func (i *BaseInterface) NextOutgoingAnnounce() ([]byte, bool) {
|
|
i.Mutex.Lock()
|
|
defer i.Mutex.Unlock()
|
|
|
|
now := time.Now()
|
|
kept := i.announceQueue[:0]
|
|
for _, e := range i.announceQueue {
|
|
if now.Sub(e.queued) > QueuedAnnounceLife {
|
|
continue
|
|
}
|
|
kept = append(kept, e)
|
|
}
|
|
i.announceQueue = kept
|
|
if len(i.announceQueue) == 0 {
|
|
return nil, false
|
|
}
|
|
if now.Before(i.announceAllowedAt) || i.Bitrate <= 0 {
|
|
return nil, false
|
|
}
|
|
|
|
sel := 0
|
|
for idx := 1; idx < len(i.announceQueue); idx++ {
|
|
a := i.announceQueue[idx]
|
|
b := i.announceQueue[sel]
|
|
if a.hops < b.hops || (a.hops == b.hops && a.queued.Before(b.queued)) {
|
|
sel = idx
|
|
}
|
|
}
|
|
chosen := i.announceQueue[sel]
|
|
i.announceQueue = append(i.announceQueue[:sel], i.announceQueue[sel+1:]...)
|
|
i.noteAnnounceSentLocked(len(chosen.raw))
|
|
return append([]byte(nil), chosen.raw...), true
|
|
}
|