0.3.4
This commit is contained in:
170
pkg/transport/announce.go
Normal file
170
pkg/transport/announce.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sudo-Ivan/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(DefaultPropagationRate, 1),
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/interfaces"
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
|
||||
"github.com/Sudo-Ivan/reticulum-go/pkg/pathfinder"
|
||||
@@ -616,65 +617,82 @@ func (t *Transport) HandlePacket(data []byte, iface common.NetworkInterface) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterface) {
|
||||
// Validate minimum packet size (1 byte hop count + 32 bytes dest + 16 bytes identity + 4 bytes min app data)
|
||||
if len(data) < 53 {
|
||||
log.Printf("[DEBUG-3] Announce packet too small: %d bytes", len(data))
|
||||
return
|
||||
func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterface) error {
|
||||
// Validate minimum packet size (1 byte hop count + 32 bytes dest hash + 16 bytes min identity + 1 byte min app data)
|
||||
if len(data) < 50 {
|
||||
return fmt.Errorf("announce packet too small: %d bytes", len(data))
|
||||
}
|
||||
|
||||
announceHash := sha256.Sum256(data)
|
||||
log.Printf("[DEBUG-3] Processing announce %x from interface %s",
|
||||
announceHash[:8], iface.GetName())
|
||||
|
||||
t.mutex.Lock()
|
||||
if _, seen := t.seenAnnounces[string(announceHash[:])]; seen {
|
||||
t.mutex.Unlock()
|
||||
log.Printf("[DEBUG-4] Ignoring duplicate announce %x", announceHash[:8])
|
||||
return
|
||||
}
|
||||
t.seenAnnounces[string(announceHash[:])] = true
|
||||
t.mutex.Unlock()
|
||||
|
||||
// Don't forward if max hops reached
|
||||
if data[0] >= MAX_HOPS {
|
||||
log.Printf("[DEBUG-3] Announce exceeded max hops: %d", data[0])
|
||||
return
|
||||
}
|
||||
|
||||
// Parse announce fields
|
||||
// Extract fields
|
||||
hopCount := data[0]
|
||||
destHash := data[1:33]
|
||||
identity := data[33:49]
|
||||
identityBytes := data[33:49]
|
||||
appData := data[49:]
|
||||
|
||||
// Check for duplicate announces
|
||||
announceHash := sha256.Sum256(data)
|
||||
hashStr := string(announceHash[:])
|
||||
|
||||
t.mutex.Lock()
|
||||
if _, seen := t.seenAnnounces[hashStr]; seen {
|
||||
t.mutex.Unlock()
|
||||
log.Printf("[DEBUG-7] Ignoring duplicate announce %x", announceHash[:8])
|
||||
return nil
|
||||
}
|
||||
t.seenAnnounces[hashStr] = true
|
||||
t.mutex.Unlock()
|
||||
|
||||
// Validate announce signature and store destination
|
||||
id := identity.FromPublicKey(identityBytes)
|
||||
if id == nil || !id.ValidateAnnounce(data, destHash, appData) {
|
||||
return fmt.Errorf("invalid announce signature")
|
||||
}
|
||||
|
||||
// Don't forward if max hops reached
|
||||
if hopCount >= MAX_HOPS {
|
||||
log.Printf("[DEBUG-7] Announce exceeded max hops: %d", hopCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add random delay before retransmission (0-2 seconds)
|
||||
delay := time.Duration(rand.Float64() * 2 * float64(time.Second))
|
||||
time.Sleep(delay)
|
||||
|
||||
// Check bandwidth allocation for announces
|
||||
if !t.announceRate.Allow() {
|
||||
log.Printf("[DEBUG-3] Announce rate limit exceeded, dropping")
|
||||
return
|
||||
log.Printf("[DEBUG-7] Announce rate limit exceeded, dropping")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increment hop count for forwarding
|
||||
data[0] = hopCount + 1
|
||||
forwardData := make([]byte, len(data))
|
||||
copy(forwardData, data)
|
||||
forwardData[0] = hopCount + 1
|
||||
|
||||
// Forward to other interfaces
|
||||
var lastErr error
|
||||
for name, outIface := range t.interfaces {
|
||||
if outIface == iface || !outIface.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check interface mode restrictions
|
||||
// if outIface.GetMode() == interfaces.ModeAccessPoint {
|
||||
// log.Printf("[DEBUG-7] Blocking announce broadcast on %s due to AP mode", name)
|
||||
// continue
|
||||
// }
|
||||
|
||||
log.Printf("[DEBUG-7] Forwarding announce on interface %s", name)
|
||||
if err := outIface.Send(data, ""); err != nil {
|
||||
if err := outIface.Send(forwardData, ""); err != nil {
|
||||
log.Printf("[DEBUG-3] Failed to forward announce on %s: %v", name, err)
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// Notify announce handlers
|
||||
t.notifyAnnounceHandlers(destHash, identity, appData)
|
||||
t.notifyAnnounceHandlers(destHash, identityBytes, appData)
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (t *Transport) handleLinkPacket(data []byte, iface common.NetworkInterface) {
|
||||
|
||||
Reference in New Issue
Block a user