Files
Reticulum-Go/pkg/transport/announce.go
Sudo-Ivan 1d3a969742
Some checks failed
Bearer / scan (push) Successful in 9s
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 42s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 44s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 41s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 39s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 1m8s
Go Build Multi-Platform / build (wasm, js) (push) Successful in 1m6s
TinyGo Build / tinygo-build (tinygo-wasm, tinygo-wasm, reticulum-go.wasm, wasm) (pull_request) Failing after 1m2s
TinyGo Build / tinygo-build (tinygo-build, tinygo-default, reticulum-go-tinygo, ) (pull_request) Failing after 1m4s
Go Revive Lint / lint (push) Successful in 1m4s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 1m24s
Run Gosec / tests (push) Successful in 1m29s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 2m31s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 9m28s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 9m28s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m27s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 9m26s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m29s
Go Build Multi-Platform / Create Release (push) Has been skipped
chore: add SPDX license identifier and copyright notice
2025-12-31 20:44:58 -06:00

173 lines
3.8 KiB
Go

// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package transport
import (
"crypto/sha256"
"encoding/hex"
"sort"
"sync"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/rate"
)
const (
MaxRetries = 3
RetryInterval = 5 * time.Second
MaxQueueSize = 1000
MinPriorityDelta = 0.1
DefaultPropagationRate = 0.02 // 2% of bandwidth for announces
)
type AnnounceEntry struct {
Data []byte
HopCount int
RetryCount int
LastRetry time.Time
SourceIface string
Priority float64
Hash string
}
type AnnounceManager struct {
announces map[string]*AnnounceEntry
announceQueue map[string][]*AnnounceEntry
rateLimiter *rate.Limiter
mutex sync.RWMutex
}
func NewAnnounceManager() *AnnounceManager {
return &AnnounceManager{
announces: make(map[string]*AnnounceEntry),
announceQueue: make(map[string][]*AnnounceEntry),
rateLimiter: rate.NewLimiter(rate.DefaultBurstFreq, 10.0),
mutex: sync.RWMutex{},
}
}
func (am *AnnounceManager) ProcessAnnounce(data []byte, sourceIface string) error {
hash := sha256.Sum256(data)
hashStr := hex.EncodeToString(hash[:])
am.mutex.Lock()
defer am.mutex.Unlock()
if entry, exists := am.announces[hashStr]; exists {
if entry.HopCount <= int(data[0]) {
return nil
}
entry.HopCount = int(data[0])
entry.Data = data
entry.RetryCount = 0
entry.LastRetry = time.Now()
entry.Priority = calculatePriority(data[0], 0)
return nil
}
entry := &AnnounceEntry{
Data: data,
HopCount: int(data[0]),
RetryCount: 0,
LastRetry: time.Now(),
SourceIface: sourceIface,
Priority: calculatePriority(data[0], 0),
Hash: hashStr,
}
am.announces[hashStr] = entry
for iface := range am.announceQueue {
if iface != sourceIface {
am.queueAnnounce(entry, iface)
}
}
return nil
}
func (am *AnnounceManager) queueAnnounce(entry *AnnounceEntry, iface string) {
queue := am.announceQueue[iface]
if len(queue) >= MaxQueueSize {
// Remove lowest priority announce if queue is full
queue = queue[:len(queue)-1]
}
insertIdx := sort.Search(len(queue), func(i int) bool {
return queue[i].Priority < entry.Priority
})
queue = append(queue[:insertIdx], append([]*AnnounceEntry{entry}, queue[insertIdx:]...)...)
am.announceQueue[iface] = queue
}
func (am *AnnounceManager) GetNextAnnounce(iface string) *AnnounceEntry {
am.mutex.Lock()
defer am.mutex.Unlock()
queue := am.announceQueue[iface]
if len(queue) == 0 {
return nil
}
entry := queue[0]
now := time.Now()
if entry.RetryCount >= MaxRetries {
am.announceQueue[iface] = queue[1:]
delete(am.announces, entry.Hash)
return am.GetNextAnnounce(iface)
}
if now.Sub(entry.LastRetry) < RetryInterval {
return nil
}
if !am.rateLimiter.Allow() {
return nil
}
entry.RetryCount++
entry.LastRetry = now
entry.Priority = calculatePriority(byte(entry.HopCount), entry.RetryCount)
am.announceQueue[iface] = queue[1:]
am.queueAnnounce(entry, iface)
return entry
}
func calculatePriority(hopCount byte, retryCount int) float64 {
basePriority := 1.0 / float64(hopCount)
retryPenalty := float64(retryCount) * MinPriorityDelta
return basePriority - retryPenalty
}
func (am *AnnounceManager) CleanupExpired() {
am.mutex.Lock()
defer am.mutex.Unlock()
now := time.Now()
expiredHashes := make([]string, 0)
for hash, entry := range am.announces {
if entry.RetryCount >= MaxRetries || now.Sub(entry.LastRetry) > RetryInterval*MaxRetries {
expiredHashes = append(expiredHashes, hash)
}
}
for _, hash := range expiredHashes {
delete(am.announces, hash)
for iface, queue := range am.announceQueue {
newQueue := make([]*AnnounceEntry, 0, len(queue))
for _, entry := range queue {
if entry.Hash != hash {
newQueue = append(newQueue, entry)
}
}
am.announceQueue[iface] = newQueue
}
}
}