All checks were successful
TinyGo Build / tinygo-build-all (push) Successful in 10m10s
671 lines
20 KiB
Go
671 lines
20 KiB
Go
// SPDX-License-Identifier: 0BSD
|
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.quad4.io/Networks/Reticulum-Go/internal/config"
|
|
"git.quad4.io/Networks/Reticulum-Go/internal/storage"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/buffer"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/channel"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/destination"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/identity"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/interfaces"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/packet"
|
|
"git.quad4.io/Networks/Reticulum-Go/pkg/transport"
|
|
)
|
|
|
|
var (
|
|
interceptPackets = flag.Bool("intercept-packets", false, "Enable packet interception")
|
|
interceptOutput = flag.String("intercept-output", "packets.log", "Output file for intercepted packets")
|
|
)
|
|
|
|
const (
|
|
ANNOUNCE_RATE_TARGET = 3600 // Default target time between announces (1 hour)
|
|
ANNOUNCE_RATE_GRACE = 3 // Number of grace announces before enforcing rate
|
|
ANNOUNCE_RATE_PENALTY = 7200 // Additional penalty time for rate violations
|
|
MAX_ANNOUNCE_HOPS = 128 // Maximum number of hops for announces
|
|
APP_NAME = "Reticulum-Go Test Node"
|
|
APP_ASPECT = "node" // Always use "node" for node announces
|
|
)
|
|
|
|
type Reticulum struct {
|
|
config *common.ReticulumConfig
|
|
transport *transport.Transport
|
|
interfaces []interfaces.Interface
|
|
channels map[string]*channel.Channel
|
|
buffers map[string]*buffer.Buffer
|
|
pathRequests map[string]*common.PathRequest
|
|
announceHistory map[string]announceRecord
|
|
announceHistoryMu sync.RWMutex
|
|
identity *identity.Identity
|
|
destination *destination.Destination
|
|
storage *storage.Manager
|
|
|
|
// Node-specific information
|
|
maxTransferSize int16 // Max transfer size in KB
|
|
nodeEnabled bool // Whether this node is enabled
|
|
nodeTimestamp int64 // Last node announcement timestamp
|
|
}
|
|
|
|
type announceRecord struct {
|
|
timestamp int64
|
|
appData []byte
|
|
}
|
|
|
|
func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
|
if cfg == nil {
|
|
cfg = config.DefaultConfig()
|
|
}
|
|
|
|
// Set default app name and aspect if not provided
|
|
if cfg.AppName == "" {
|
|
cfg.AppName = APP_NAME
|
|
}
|
|
if cfg.AppAspect == "" {
|
|
cfg.AppAspect = APP_ASPECT // Always use "node" for node announcements
|
|
}
|
|
|
|
if err := initializeDirectories(); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize directories: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Directories initialized")
|
|
|
|
// Initialize storage manager
|
|
storageMgr, err := storage.NewManager()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize storage manager: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Storage manager initialized")
|
|
|
|
t := transport.NewTransport(cfg)
|
|
debug.Log(debug.DEBUG_INFO, "Transport initialized")
|
|
|
|
// Load or create identity
|
|
identityPath := storageMgr.GetIdentityPath()
|
|
|
|
var ident *identity.Identity
|
|
|
|
if _, err := os.Stat(identityPath); err == nil {
|
|
// Identity file exists, load it
|
|
ident, err = identity.FromFile(identityPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load identity: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_ERROR, "Loaded existing identity", common.STR_HASH, fmt.Sprintf(common.STR_FMT_HEX_LOW, ident.Hash()))
|
|
} else {
|
|
// Create new identity
|
|
ident, err = identity.NewIdentity()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create identity: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_ERROR, "Created new identity", common.STR_HASH, fmt.Sprintf(common.STR_FMT_HEX_LOW, ident.Hash()))
|
|
|
|
// Save it to disk
|
|
if err := ident.ToFile(identityPath); err != nil {
|
|
debug.Log(debug.DEBUG_ERROR, "Failed to save identity to file", common.STR_ERROR, err)
|
|
} else {
|
|
debug.Log(debug.DEBUG_INFO, "Identity saved to file", "path", identityPath)
|
|
}
|
|
}
|
|
|
|
// Create destination
|
|
debug.Log(debug.DEBUG_INFO, "Creating destination...")
|
|
dest, err := destination.New(
|
|
ident,
|
|
destination.IN,
|
|
destination.SINGLE,
|
|
"nomadnetwork",
|
|
t,
|
|
"node",
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create destination: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Created destination with hash", common.STR_HASH, fmt.Sprintf(common.STR_FMT_HEX_LOW, dest.GetHash()))
|
|
|
|
// Set node metadata
|
|
nodeTimestamp := time.Now().Unix()
|
|
|
|
r := &Reticulum{
|
|
config: cfg,
|
|
transport: t,
|
|
interfaces: make([]interfaces.Interface, 0),
|
|
channels: make(map[string]*channel.Channel),
|
|
buffers: make(map[string]*buffer.Buffer),
|
|
pathRequests: make(map[string]*common.PathRequest),
|
|
announceHistory: make(map[string]announceRecord),
|
|
identity: ident,
|
|
destination: dest,
|
|
storage: storageMgr,
|
|
|
|
// Node-specific information
|
|
maxTransferSize: common.NUM_500, // Default 500KB
|
|
nodeEnabled: true, // Enabled by default
|
|
nodeTimestamp: nodeTimestamp,
|
|
}
|
|
|
|
// Enable destination features
|
|
dest.AcceptsLinks(true)
|
|
// Enable ratchets and point to a file for persistence.
|
|
// The actual path should probably be configurable.
|
|
ratchetPath := ".git.quad4.io/Networks/Reticulum-Go/storage/ratchets/" + r.identity.GetHexHash()
|
|
dest.EnableRatchets(ratchetPath)
|
|
dest.SetProofStrategy(destination.PROVE_APP)
|
|
|
|
debug.Log(debug.DEBUG_VERBOSE, "Configured destination features")
|
|
|
|
// Initialize interfaces from config
|
|
for name, ifaceConfig := range cfg.Interfaces {
|
|
if !ifaceConfig.Enabled {
|
|
continue
|
|
}
|
|
|
|
var iface interfaces.Interface
|
|
var err error
|
|
|
|
switch ifaceConfig.Type {
|
|
case common.STR_TCP_CLIENT:
|
|
iface, err = interfaces.NewTCPClientInterface(
|
|
name,
|
|
ifaceConfig.TargetHost,
|
|
ifaceConfig.TargetPort,
|
|
ifaceConfig.KISSFraming,
|
|
ifaceConfig.I2PTunneled,
|
|
ifaceConfig.Enabled,
|
|
)
|
|
case "UDPInterface":
|
|
iface, err = interfaces.NewUDPInterface(
|
|
name,
|
|
ifaceConfig.Address,
|
|
ifaceConfig.TargetHost,
|
|
ifaceConfig.Enabled,
|
|
)
|
|
case "AutoInterface":
|
|
iface, err = interfaces.NewAutoInterface(name, ifaceConfig)
|
|
case "WebSocketInterface":
|
|
wsURL := ifaceConfig.Address
|
|
if wsURL == "" {
|
|
wsURL = ifaceConfig.TargetHost
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Creating WebSocket interface", common.STR_NAME, name, "url", wsURL, "enabled", ifaceConfig.Enabled)
|
|
iface, err = interfaces.NewWebSocketInterface(name, wsURL, ifaceConfig.Enabled)
|
|
if err != nil {
|
|
debug.Log(debug.DEBUG_ERROR, "Failed to create WebSocket interface", common.STR_NAME, name, common.STR_ERROR, err)
|
|
} else {
|
|
debug.Log(debug.DEBUG_INFO, "WebSocket interface created successfully", common.STR_NAME, name)
|
|
}
|
|
case "SerialInterface":
|
|
iface, err = interfaces.NewSerialInterface(
|
|
name,
|
|
ifaceConfig.Interface,
|
|
uint32(ifaceConfig.Bitrate), // #nosec G115
|
|
ifaceConfig.Enabled,
|
|
)
|
|
case "RNodeInterface":
|
|
// RNode usually runs over Serial
|
|
serial, sErr := interfaces.NewSerialInterface(
|
|
name+"_serial",
|
|
ifaceConfig.Interface,
|
|
uint32(ifaceConfig.Bitrate), // #nosec G115
|
|
ifaceConfig.Enabled,
|
|
)
|
|
if sErr != nil {
|
|
err = sErr
|
|
} else {
|
|
iface, err = interfaces.NewRNodeInterface(
|
|
name,
|
|
serial,
|
|
ifaceConfig.Frequency,
|
|
ifaceConfig.Bandwidth,
|
|
ifaceConfig.SF,
|
|
ifaceConfig.CR,
|
|
ifaceConfig.TXPower,
|
|
)
|
|
}
|
|
default:
|
|
debug.Log(debug.DEBUG_CRITICAL, "Unknown interface type", common.STR_TYPE, ifaceConfig.Type)
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
if cfg.PanicOnInterfaceErr {
|
|
return nil, fmt.Errorf("failed to create interface %s: %v", name, err)
|
|
}
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error creating interface", common.STR_NAME, name, common.STR_ERROR, err)
|
|
continue
|
|
}
|
|
|
|
// Set packet callback
|
|
iface.SetPacketCallback(func(data []byte, ni common.NetworkInterface) {
|
|
debug.Log(debug.DEBUG_INFO, "Packet callback called for interface", common.STR_NAME, ni.GetName(), "data_len", len(data))
|
|
if r.transport != nil {
|
|
r.transport.HandlePacket(data, ni)
|
|
} else {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Transport is nil in packet callback")
|
|
}
|
|
})
|
|
|
|
debug.Log(debug.DEBUG_ERROR, "Configuring interface", common.STR_NAME, name, common.STR_TYPE, ifaceConfig.Type)
|
|
r.interfaces = append(r.interfaces, iface)
|
|
debug.Log(debug.DEBUG_INFO, "Interface started successfully", common.STR_NAME, name)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (r *Reticulum) handleInterface(iface common.NetworkInterface) {
|
|
debug.Log(debug.DEBUG_INFO, "Setting up interface", common.STR_NAME, iface.GetName(), common.STR_TYPE, fmt.Sprintf("%T", iface))
|
|
|
|
ch := channel.NewChannel(&transportWrapper{r.transport})
|
|
r.channels[iface.GetName()] = ch
|
|
|
|
rw := buffer.CreateBidirectionalBuffer(
|
|
1,
|
|
2,
|
|
ch,
|
|
func(size int) {
|
|
data := make([]byte, size)
|
|
debug.Log(debug.DEBUG_PACKETS, "Interface reading bytes from buffer", common.STR_NAME, iface.GetName(), "size", size)
|
|
iface.ProcessIncoming(data)
|
|
|
|
if len(data) > common.ZERO {
|
|
debug.Log(debug.DEBUG_TRACE, "Interface received packet type", common.STR_NAME, iface.GetName(), common.STR_TYPE, fmt.Sprintf("0x%02x", data[0]))
|
|
r.transport.HandlePacket(data, iface)
|
|
}
|
|
},
|
|
)
|
|
|
|
r.buffers[iface.GetName()] = &buffer.Buffer{
|
|
ReadWriter: rw,
|
|
}
|
|
}
|
|
|
|
func (r *Reticulum) monitorInterfaces() {
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
for _, iface := range r.interfaces {
|
|
if tcpClient, ok := iface.(*interfaces.TCPClientInterface); ok {
|
|
stats := fmt.Sprintf("Interface %s status - Connected: %v, TX: %d bytes (%.2f Kbps), RX: %d bytes (%.2f Kbps)",
|
|
iface.GetName(),
|
|
tcpClient.IsConnected(),
|
|
tcpClient.GetTxBytes(),
|
|
float64(tcpClient.GetTxBytes()*8)/(5*1024),
|
|
tcpClient.GetRxBytes(),
|
|
float64(tcpClient.GetRxBytes()*8)/(5*1024),
|
|
)
|
|
|
|
if runtime.GOOS != "windows" {
|
|
stats = fmt.Sprintf("%s, RTT: %v", stats, tcpClient.GetRTT())
|
|
}
|
|
|
|
debug.Log(debug.DEBUG_VERBOSE, "Interface status", "stats", stats)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
debug.Init()
|
|
debug.Log(debug.DEBUG_CRITICAL, "Initializing Reticulum", "debug_level", debug.GetDebugLevel())
|
|
|
|
cfg, err := config.InitConfig()
|
|
if err != nil {
|
|
debug.GetLogger().Error("Failed to initialize config", common.STR_ERROR, err)
|
|
os.Exit(1)
|
|
}
|
|
debug.Log(debug.DEBUG_ERROR, "Configuration loaded", "path", cfg.ConfigPath)
|
|
|
|
r, err := NewReticulum(cfg)
|
|
if err != nil {
|
|
debug.GetLogger().Error("Failed to create Reticulum instance", common.STR_ERROR, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Start monitoring interfaces
|
|
go r.monitorInterfaces()
|
|
|
|
// Register announce handler
|
|
handler := NewAnnounceHandler(r, []string{"*"})
|
|
r.transport.RegisterAnnounceHandler(handler)
|
|
|
|
// Start Reticulum
|
|
if err := r.Start(); err != nil {
|
|
debug.GetLogger().Error("Failed to start Reticulum", common.STR_ERROR, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sigChan
|
|
|
|
debug.Log(debug.DEBUG_CRITICAL, "Shutting down...")
|
|
if err := r.Stop(); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error during shutdown", common.STR_ERROR, err)
|
|
}
|
|
debug.Log(debug.DEBUG_CRITICAL, "Goodbye!")
|
|
}
|
|
|
|
type transportWrapper struct {
|
|
*transport.Transport
|
|
}
|
|
|
|
func (tw *transportWrapper) GetRTT() float64 {
|
|
return 0.1
|
|
}
|
|
|
|
func (tw *transportWrapper) RTT() float64 {
|
|
return tw.GetRTT()
|
|
}
|
|
|
|
func (tw *transportWrapper) GetStatus() byte {
|
|
return transport.STATUS_ACTIVE
|
|
}
|
|
|
|
func (tw *transportWrapper) Send(data []byte) interface{} {
|
|
p := &packet.Packet{
|
|
PacketType: packet.PacketTypeData,
|
|
Hops: 0,
|
|
Data: data,
|
|
HeaderType: packet.HeaderType1,
|
|
}
|
|
|
|
err := tw.Transport.SendPacket(p)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (tw *transportWrapper) Resend(p interface{}) error {
|
|
if pkt, ok := p.(*packet.Packet); ok {
|
|
return tw.Transport.SendPacket(pkt)
|
|
}
|
|
return fmt.Errorf("invalid packet type")
|
|
}
|
|
|
|
func (tw *transportWrapper) SetPacketTimeout(packet interface{}, callback func(interface{}), timeout time.Duration) {
|
|
time.AfterFunc(timeout, func() {
|
|
callback(packet)
|
|
})
|
|
}
|
|
|
|
func (tw *transportWrapper) SetPacketDelivered(packet interface{}, callback func(interface{})) {
|
|
callback(packet)
|
|
}
|
|
|
|
func (tw *transportWrapper) GetLinkID() []byte {
|
|
return nil
|
|
}
|
|
|
|
func (tw *transportWrapper) HandleInbound(pkt *packet.Packet) error {
|
|
return nil
|
|
}
|
|
|
|
func (tw *transportWrapper) ValidateLinkProof(pkt *packet.Packet, networkIface common.NetworkInterface) error {
|
|
return nil
|
|
}
|
|
|
|
func initializeDirectories() error {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get home directory: %v", err)
|
|
}
|
|
|
|
basePath := filepath.Join(homeDir, ".reticulum-go")
|
|
dirs := []string{
|
|
basePath,
|
|
filepath.Join(basePath, common.STR_STORAGE),
|
|
filepath.Join(basePath, common.STR_STORAGE, "destinations"),
|
|
filepath.Join(basePath, common.STR_STORAGE, "identities"),
|
|
filepath.Join(basePath, common.STR_STORAGE, "ratchets"),
|
|
filepath.Join(basePath, common.STR_STORAGE, "cache"),
|
|
filepath.Join(basePath, common.STR_STORAGE, "cache", "announces"),
|
|
filepath.Join(basePath, common.STR_STORAGE, "resources"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, common.NUM_0700); err != nil { // #nosec G301
|
|
return fmt.Errorf("failed to create directory %s: %v", dir, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Reticulum) Start() error {
|
|
debug.Log(debug.DEBUG_ERROR, "Starting Reticulum...")
|
|
|
|
if err := r.transport.Start(); err != nil {
|
|
return fmt.Errorf("failed to start transport: %v", err)
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Transport started successfully")
|
|
|
|
// Start interfaces
|
|
for _, iface := range r.interfaces {
|
|
debug.Log(debug.DEBUG_ERROR, "Starting interface", "name", iface.GetName())
|
|
if err := iface.Start(); err != nil {
|
|
if r.config.PanicOnInterfaceErr {
|
|
return fmt.Errorf("failed to start interface %s: %v", iface.GetName(), err)
|
|
}
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error starting interface", "name", iface.GetName(), "error", err)
|
|
continue
|
|
}
|
|
|
|
if netIface, ok := iface.(common.NetworkInterface); ok {
|
|
// Register interface with transport
|
|
if err := r.transport.RegisterInterface(iface.GetName(), netIface); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Failed to register interface with transport", "name", iface.GetName(), "error", err)
|
|
} else {
|
|
debug.Log(debug.DEBUG_INFO, "Registered interface with transport", "name", iface.GetName())
|
|
}
|
|
r.handleInterface(netIface)
|
|
}
|
|
debug.Log(debug.DEBUG_INFO, "Interface started successfully", "name", iface.GetName())
|
|
}
|
|
|
|
// Wait for interfaces to initialize
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Send initial announce
|
|
debug.Log(debug.DEBUG_ERROR, "Sending initial announce")
|
|
nodeName := "Reticulum-Go Test Node"
|
|
r.destination.SetDefaultAppData([]byte(nodeName))
|
|
if err := r.destination.Announce(false, nil, nil); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Failed to send initial announce", "error", err)
|
|
}
|
|
|
|
// Start periodic announce goroutine
|
|
go func() {
|
|
// Wait a bit before the first announce
|
|
time.Sleep(5 * time.Second)
|
|
|
|
for {
|
|
debug.Log(debug.DEBUG_INFO, "Announcing destination...")
|
|
err := r.destination.Announce(false, nil, nil)
|
|
if err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Could not send announce", "error", err)
|
|
}
|
|
|
|
time.Sleep(60 * time.Second)
|
|
}
|
|
}()
|
|
|
|
go r.monitorInterfaces()
|
|
|
|
debug.Log(debug.DEBUG_ERROR, "Reticulum started successfully")
|
|
return nil
|
|
}
|
|
|
|
func (r *Reticulum) Stop() error {
|
|
debug.Log(debug.DEBUG_ERROR, "Stopping Reticulum...")
|
|
|
|
for _, buf := range r.buffers {
|
|
if err := buf.Close(); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error closing buffer", "error", err)
|
|
}
|
|
}
|
|
|
|
for _, ch := range r.channels {
|
|
if err := ch.Close(); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error closing channel", "error", err)
|
|
}
|
|
}
|
|
|
|
for _, iface := range r.interfaces {
|
|
if err := iface.Stop(); err != nil {
|
|
debug.Log(debug.DEBUG_CRITICAL, "Error stopping interface", "name", iface.GetName(), "error", err)
|
|
}
|
|
}
|
|
|
|
if err := r.transport.Close(); err != nil {
|
|
return fmt.Errorf("failed to close transport: %v", err)
|
|
}
|
|
|
|
debug.Log(debug.DEBUG_ERROR, "Reticulum stopped successfully")
|
|
return nil
|
|
}
|
|
|
|
type AnnounceHandler struct {
|
|
aspectFilter []string
|
|
reticulum *Reticulum
|
|
}
|
|
|
|
func NewAnnounceHandler(r *Reticulum, aspectFilter []string) *AnnounceHandler {
|
|
return &AnnounceHandler{
|
|
aspectFilter: aspectFilter,
|
|
reticulum: r,
|
|
}
|
|
}
|
|
|
|
func (h *AnnounceHandler) AspectFilter() []string {
|
|
return h.aspectFilter
|
|
}
|
|
|
|
func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appData []byte, hops uint8) error {
|
|
debug.Log(debug.DEBUG_INFO, "Received announce", "hash", fmt.Sprintf("%x", destHash), "hops", hops)
|
|
debug.Log(debug.DEBUG_PACKETS, "Raw announce data", "data", fmt.Sprintf("%x", appData))
|
|
debug.Log(debug.DEBUG_INFO, "MAIN HANDLER: Received announce", "hash", fmt.Sprintf("%x", destHash), "appData_len", len(appData), "hops", hops)
|
|
|
|
var isNode bool
|
|
var nodeEnabled bool
|
|
var nodeTimestamp int64
|
|
var nodeMaxSize int16
|
|
|
|
// Parse msgpack appData from transport announce format
|
|
if len(appData) > common.ZERO {
|
|
// appData is msgpack array [name, customData]
|
|
if appData[0] == common.HEX_0x92 { // array of 2 elements
|
|
// Skip array header and first element (name)
|
|
pos := common.ONE
|
|
if pos < len(appData) && appData[pos] == common.HEX_0xC4 { // bin 8
|
|
nameLen := int(appData[pos+1])
|
|
pos += common.TWO + nameLen
|
|
if pos < len(appData) && appData[pos] == common.HEX_0xC4 { // bin 8
|
|
dataLen := int(appData[pos+1])
|
|
if pos+2+dataLen <= len(appData) {
|
|
customData := appData[pos+2 : pos+2+dataLen]
|
|
nodeName := string(customData)
|
|
debug.Log(debug.DEBUG_INFO, "Parsed node name", "name", nodeName)
|
|
debug.Log(debug.DEBUG_INFO, "Announced node", "name", nodeName)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback: treat as raw node name
|
|
nodeName := string(appData)
|
|
debug.Log(debug.DEBUG_INFO, "Raw node name", "name", nodeName)
|
|
debug.Log(debug.DEBUG_INFO, "Announced node", "name", nodeName)
|
|
}
|
|
} else {
|
|
debug.Log(debug.DEBUG_INFO, "No appData (empty announce)")
|
|
}
|
|
|
|
// Type assert and log identity details
|
|
if identity, ok := id.(*identity.Identity); ok {
|
|
debug.Log(debug.DEBUG_ALL, "Identity details")
|
|
debug.Log(debug.DEBUG_ALL, "Identity hash", "hash", identity.GetHexHash())
|
|
debug.Log(debug.DEBUG_ALL, "Identity public key", "key", fmt.Sprintf("%x", identity.GetPublicKey()))
|
|
|
|
ratchets := identity.GetRatchets()
|
|
debug.Log(debug.DEBUG_ALL, "Active ratchets", "count", len(ratchets))
|
|
|
|
if len(ratchets) > 0 {
|
|
ratchetKey := identity.GetCurrentRatchetKey()
|
|
if ratchetKey != nil {
|
|
ratchetID := identity.GetRatchetID(ratchetKey)
|
|
debug.Log(debug.DEBUG_ALL, "Current ratchet ID", "id", fmt.Sprintf("%x", ratchetID))
|
|
}
|
|
}
|
|
|
|
// Create a better record with more info
|
|
recordType := "peer"
|
|
if isNode {
|
|
recordType = "node"
|
|
debug.Log(debug.DEBUG_INFO, "Storing node in announce history", "enabled", nodeEnabled, "timestamp", nodeTimestamp, "maxsize", fmt.Sprintf("%dKB", nodeMaxSize))
|
|
}
|
|
|
|
h.reticulum.announceHistoryMu.Lock()
|
|
h.reticulum.announceHistory[identity.GetHexHash()] = announceRecord{
|
|
timestamp: time.Now().Unix(),
|
|
appData: appData,
|
|
}
|
|
h.reticulum.announceHistoryMu.Unlock()
|
|
|
|
debug.Log(debug.DEBUG_VERBOSE, "Stored announce in history", "type", recordType, "identity", identity.GetHexHash())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *AnnounceHandler) ReceivePathResponses() bool {
|
|
return true
|
|
}
|
|
|
|
func (r *Reticulum) GetDestination() *destination.Destination {
|
|
return r.destination
|
|
}
|
|
|
|
func (r *Reticulum) createNodeAppData() []byte {
|
|
// Create a msgpack array with 3 elements
|
|
// [Bool, Int32, Int16] for [enable, timestamp, max_transfer_size]
|
|
appData := []byte{common.HEX_0x93} // Array with 3 elements
|
|
|
|
// Element 0: Boolean for enable/disable peer
|
|
if r.nodeEnabled {
|
|
appData = append(appData, common.HEX_0xC3) // true
|
|
} else {
|
|
appData = append(appData, common.HEX_0xC2) // false
|
|
}
|
|
|
|
// Element 1: Int32 timestamp (current time)
|
|
r.nodeTimestamp = time.Now().Unix()
|
|
appData = append(appData, common.HEX_0xD2) // int32 format
|
|
timeBytes := make([]byte, common.FOUR)
|
|
binary.BigEndian.PutUint32(timeBytes, uint32(r.nodeTimestamp)) // #nosec G115
|
|
appData = append(appData, timeBytes...)
|
|
|
|
// Element 2: Int16 max transfer size in KB
|
|
appData = append(appData, common.HEX_0xD1) // int16 format
|
|
sizeBytes := make([]byte, common.TWO)
|
|
binary.BigEndian.PutUint16(sizeBytes, uint16(r.maxTransferSize)) // #nosec G115
|
|
appData = append(appData, sizeBytes...)
|
|
|
|
debug.Log(debug.DEBUG_ALL, "Created node appData", "enable", r.nodeEnabled, "timestamp", r.nodeTimestamp, "maxsize", r.maxTransferSize, "data", fmt.Sprintf("%x", appData))
|
|
return appData
|
|
}
|