mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
269 lines
7.8 KiB
Go
269 lines
7.8 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package common
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// DefaultMaxInMemoryPaths is the soft path-table cap for the live path map.
|
|
// Applies for both disk-backed and in-memory storage. Paths live in RAM
|
|
// either way, so an uncapped table grows without bound on a busy mesh.
|
|
DefaultMaxInMemoryPaths = 100_000
|
|
|
|
// DefaultMaxInMemoryKnownDestinations is the soft known-dest cap.
|
|
DefaultMaxInMemoryKnownDestinations = 100_000
|
|
|
|
// DefaultMaxInMemoryResourceBytes is the soft split-resource staging budget
|
|
// under in-memory storage (256 MiB).
|
|
DefaultMaxInMemoryResourceBytes = 256 << 20
|
|
|
|
// DefaultMaxPacketHashlist is the packet hash loop-filter cap when
|
|
// enable_transport is yes (matches Python Transport.hashlist_maxsize).
|
|
DefaultMaxPacketHashlist = 1_000_000
|
|
|
|
// DefaultMaxPacketHashlistClient is the hashlist cap when transport is off.
|
|
// Client nodes still see mesh traffic but do not need a million-entry filter.
|
|
DefaultMaxPacketHashlistClient = 100_000
|
|
|
|
// DefaultMaxSeenAnnounces caps announce-hash dedupe entries.
|
|
DefaultMaxSeenAnnounces = 100_000
|
|
|
|
// DefaultMaxPacketHandlers is the HandlePacket worker count and queue depth.
|
|
DefaultMaxPacketHandlers = 512
|
|
|
|
// CoreRouterMaxInMemoryPaths is the path-table cap applied by node_profile=core_router
|
|
// when max_in_memory_paths is unset.
|
|
CoreRouterMaxInMemoryPaths = 500_000
|
|
|
|
// EmbeddedMaxPacketHandlers is the handler pool size for node_profile=embedded.
|
|
EmbeddedMaxPacketHandlers = 32
|
|
// EmbeddedMaxInMemoryPaths is the path-table cap for node_profile=embedded.
|
|
EmbeddedMaxInMemoryPaths = 4096
|
|
// EmbeddedMaxInMemoryKnownDestinations is the known-dest cap for embedded.
|
|
EmbeddedMaxInMemoryKnownDestinations = 4096
|
|
// EmbeddedMaxPacketHashlist is the hashlist cap for embedded.
|
|
EmbeddedMaxPacketHashlist = 10_000
|
|
)
|
|
|
|
// ApplyPersistenceEnv overrides persistence-related config from environment
|
|
// variables. Set RETICULUM_IN_MEMORY_PATH_TABLE=1 or
|
|
// RETICULUM_IN_MEMORY_KNOWN_DESTINATIONS=1 to force in-memory tables.
|
|
// RETICULUM_IN_MEMORY_STORAGE=1 forces fully ephemeral storage.
|
|
// RETICULUM_SOFT_MEMORY_LIMIT accepts a byte count or K/M/G suffix.
|
|
// RETICULUM_MAX_PACKET_HASHLIST accepts an integer entry budget.
|
|
func (c *ReticulumConfig) ApplyPersistenceEnv() {
|
|
if c == nil {
|
|
return
|
|
}
|
|
if envBool("RETICULUM_IN_MEMORY_PATH_TABLE") {
|
|
c.InMemoryPathTable = true
|
|
}
|
|
if envBool("RETICULUM_IN_MEMORY_KNOWN_DESTINATIONS") {
|
|
c.InMemoryKnownDestinations = true
|
|
}
|
|
if envBool("RETICULUM_IN_MEMORY_STORAGE") {
|
|
c.InMemoryStorage = true
|
|
}
|
|
if v := strings.TrimSpace(os.Getenv("RETICULUM_SOFT_MEMORY_LIMIT")); v != "" {
|
|
if n, err := ParseByteSize(v); err == nil && n > 0 {
|
|
c.SoftMemoryLimitBytes = n
|
|
}
|
|
}
|
|
if v := strings.TrimSpace(os.Getenv("RETICULUM_MAX_PACKET_HASHLIST")); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
c.MaxPacketHashlist = n
|
|
}
|
|
}
|
|
if v := strings.TrimSpace(os.Getenv("RETICULUM_MAX_IN_MEMORY_PATHS")); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
c.MaxInMemoryPaths = n
|
|
}
|
|
}
|
|
if v := strings.TrimSpace(os.Getenv("RETICULUM_MAX_IN_MEMORY_KNOWN_DESTINATIONS")); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
c.MaxInMemoryKnownDestinations = n
|
|
}
|
|
}
|
|
c.NormalizeInMemoryFlags()
|
|
}
|
|
|
|
// NormalizeInMemoryFlags propagates InMemoryStorage onto the per-table flags.
|
|
func (c *ReticulumConfig) NormalizeInMemoryFlags() {
|
|
if c == nil {
|
|
return
|
|
}
|
|
if c.InMemoryStorage {
|
|
c.InMemoryPathTable = true
|
|
c.InMemoryKnownDestinations = true
|
|
}
|
|
}
|
|
|
|
// UseInMemoryStorage reports whether stack state must stay off disk.
|
|
// True when InMemoryStorage is set, or when neither ConfigPath nor
|
|
// RETICULUM_STORAGE_PATH is available (library and test defaults).
|
|
func (c *ReticulumConfig) UseInMemoryStorage() bool {
|
|
if c == nil {
|
|
return true
|
|
}
|
|
if c.InMemoryStorage {
|
|
return true
|
|
}
|
|
if c.ConfigPath == "" && strings.TrimSpace(os.Getenv("RETICULUM_STORAGE_PATH")) == "" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// EffectiveMaxInMemoryPaths returns the live path-table cap. Disk-backed
|
|
// stacks keep the full table in RAM, so the cap always applies. Zero uses
|
|
// DefaultMaxInMemoryPaths. Negative disables the cap.
|
|
func (c *ReticulumConfig) EffectiveMaxInMemoryPaths() int {
|
|
if c == nil {
|
|
return DefaultMaxInMemoryPaths
|
|
}
|
|
if c.MaxInMemoryPaths < 0 {
|
|
return 0
|
|
}
|
|
if c.MaxInMemoryPaths == 0 {
|
|
return DefaultMaxInMemoryPaths
|
|
}
|
|
return c.MaxInMemoryPaths
|
|
}
|
|
|
|
// EffectiveMaxInMemoryKnownDestinations returns the known-dest cap. Always
|
|
// applies (known destinations are held in RAM). Zero uses
|
|
// DefaultMaxInMemoryKnownDestinations. Negative disables the cap.
|
|
func (c *ReticulumConfig) EffectiveMaxInMemoryKnownDestinations() int {
|
|
if c == nil {
|
|
return DefaultMaxInMemoryKnownDestinations
|
|
}
|
|
if c.MaxInMemoryKnownDestinations < 0 {
|
|
return 0
|
|
}
|
|
if c.MaxInMemoryKnownDestinations == 0 {
|
|
return DefaultMaxInMemoryKnownDestinations
|
|
}
|
|
return c.MaxInMemoryKnownDestinations
|
|
}
|
|
|
|
// EffectiveMaxInMemoryResourceBytes returns the split-resource staging budget
|
|
// when InMemoryStorage is explicitly enabled.
|
|
func (c *ReticulumConfig) EffectiveMaxInMemoryResourceBytes() int64 {
|
|
if c == nil || !c.InMemoryStorage {
|
|
return 0
|
|
}
|
|
if c.MaxInMemoryResourceBytes < 0 {
|
|
return 0
|
|
}
|
|
if c.MaxInMemoryResourceBytes == 0 {
|
|
return DefaultMaxInMemoryResourceBytes
|
|
}
|
|
return c.MaxInMemoryResourceBytes
|
|
}
|
|
|
|
// EffectiveMaxPacketHashlist returns the packet hash loop-filter size.
|
|
// Zero picks DefaultMaxPacketHashlist when transport is enabled, otherwise
|
|
// DefaultMaxPacketHashlistClient. Negative uses DefaultMaxPacketHashlist.
|
|
func (c *ReticulumConfig) EffectiveMaxPacketHashlist() int {
|
|
if c == nil {
|
|
return DefaultMaxPacketHashlistClient
|
|
}
|
|
if c.MaxPacketHashlist > 0 {
|
|
return c.MaxPacketHashlist
|
|
}
|
|
if c.MaxPacketHashlist < 0 {
|
|
return DefaultMaxPacketHashlist
|
|
}
|
|
if c.EnableTransport {
|
|
return DefaultMaxPacketHashlist
|
|
}
|
|
return DefaultMaxPacketHashlistClient
|
|
}
|
|
|
|
// EffectiveMaxPacketHandlers returns the HandlePacket worker count and queue
|
|
// depth. Zero uses DefaultMaxPacketHandlers. Negative also uses the default.
|
|
func (c *ReticulumConfig) EffectiveMaxPacketHandlers() int {
|
|
if c == nil || c.MaxPacketHandlers <= 0 {
|
|
return DefaultMaxPacketHandlers
|
|
}
|
|
return c.MaxPacketHandlers
|
|
}
|
|
|
|
// InboundDataQueueLength returns qlen_in_data when set.
|
|
func (c *ReticulumConfig) InboundDataQueueLength() int {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return c.QLenInboundData
|
|
}
|
|
|
|
// InboundAnnounceQueueLength returns qlen_in_announce when set.
|
|
func (c *ReticulumConfig) InboundAnnounceQueueLength() int {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return c.QLenInboundAnnounce
|
|
}
|
|
|
|
// InboundPRQueueLength returns qlen_in_pr when set.
|
|
func (c *ReticulumConfig) InboundPRQueueLength() int {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return c.QLenInboundPR
|
|
}
|
|
|
|
// InboundILQueueLength returns qlen_in_il when set.
|
|
func (c *ReticulumConfig) InboundILQueueLength() int {
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return c.QLenInboundIL
|
|
}
|
|
|
|
// ParseByteSize parses a decimal byte count with an optional K, M, or G suffix
|
|
// (1024-based). Empty or invalid input returns an error.
|
|
func ParseByteSize(s string) (int64, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return 0, strconv.ErrSyntax
|
|
}
|
|
mult := int64(1)
|
|
switch {
|
|
case strings.HasSuffix(strings.ToLower(s), "k"):
|
|
mult = 1 << 10
|
|
s = strings.TrimSpace(s[:len(s)-1])
|
|
case strings.HasSuffix(strings.ToLower(s), "m"):
|
|
mult = 1 << 20
|
|
s = strings.TrimSpace(s[:len(s)-1])
|
|
case strings.HasSuffix(strings.ToLower(s), "g"):
|
|
mult = 1 << 30
|
|
s = strings.TrimSpace(s[:len(s)-1])
|
|
}
|
|
n, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if n < 0 {
|
|
return 0, strconv.ErrRange
|
|
}
|
|
if mult > 1 && n > (1<<63-1)/mult {
|
|
return 0, strconv.ErrRange
|
|
}
|
|
return n * mult, nil
|
|
}
|
|
|
|
func envBool(name string) bool {
|
|
v := strings.TrimSpace(os.Getenv(name))
|
|
switch strings.ToLower(v) {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|