Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae9a35e3bb | ||
|
|
32d32380d8 | ||
|
|
5e40f0bfe8 | ||
| 315b35fc81 | |||
| 54dec6aa89 | |||
| 92c8faec11 | |||
| 2aff4989e5 | |||
| f1d2a31be6 | |||
| f604d1a3c8 | |||
| 26a54436f7 | |||
| 2fd85a1034 | |||
| c8e81cd9f0 | |||
| 2f61ce9bf3 |
28
.github/workflows/gosec.yml
vendored
28
.github/workflows/gosec.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: "Security Scan"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
env:
|
||||
GO111MODULE: on
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Gosec Security Scanner
|
||||
uses: securego/gosec@master
|
||||
with:
|
||||
args: '-no-fail -fmt sarif -out results.sarif ./...'
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -5,12 +5,3 @@ logs/
|
||||
.json
|
||||
|
||||
bin/
|
||||
|
||||
RNS/
|
||||
|
||||
test-network/go-rns-network/reticulum/storage/
|
||||
test-network/go-rns-network/reticulum/interfaces/
|
||||
test-network/go-rns-network/nomadnetwork
|
||||
|
||||
/test-network/go-rns-network/
|
||||
/test-network/go-rns-network-client/
|
||||
21
CONTRIBUTING.md
Normal file
21
CONTRIBUTING.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Contributing
|
||||
|
||||
Be good to each other.
|
||||
|
||||
## Development
|
||||
|
||||
By contributing to this project you agree to the following:
|
||||
|
||||
- All code must be tested using `gosec`.
|
||||
- All code must be formatted with `gofmt`.
|
||||
- All code must be documented.
|
||||
|
||||
## Communication
|
||||
|
||||
Feel free to join our seperate matrix channel for this implementation.
|
||||
|
||||
- [Matrix](https://matrix.to/#/#reticulum-go-dev:matrix.org)
|
||||
|
||||
## Usage of LLMs and other Generative AI tools
|
||||
|
||||
We would prefer if you did not use LLMs and other generative AI tools to write critical parts of the code.
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
Copyright 2024 Sudo-Ivan / Quad4.io
|
||||
Copyright 2024-2025 Sudo-Ivan / Quad4.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
11
Makefile
11
Makefile
@@ -70,7 +70,14 @@ build-netbsd:
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-arm64 $(MAIN_PACKAGE)
|
||||
CGO_ENABLED=0 GOOS=netbsd GOARCH=arm $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-arm $(MAIN_PACKAGE)
|
||||
|
||||
build-all: build-linux build-windows build-darwin build-freebsd build-openbsd build-netbsd
|
||||
build-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-arm $(MAIN_PACKAGE)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-arm64 $(MAIN_PACKAGE)
|
||||
|
||||
build-riscv:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-riscv64 $(MAIN_PACKAGE)
|
||||
|
||||
build-all: build-linux build-windows build-darwin build-freebsd build-openbsd build-netbsd build-arm build-riscv
|
||||
|
||||
run:
|
||||
@./$(BUILD_DIR)/$(BINARY_NAME)
|
||||
@@ -92,6 +99,8 @@ help:
|
||||
@echo " build-freebsd- Build for FreeBSD (amd64, 386, arm64, arm, riscv64)"
|
||||
@echo " build-openbsd- Build for OpenBSD (amd64, 386, arm64, arm, ppc64, riscv64)"
|
||||
@echo " build-netbsd - Build for NetBSD (amd64, 386, arm64, arm)"
|
||||
@echo " build-arm - Build for ARM architectures (arm, arm64)"
|
||||
@echo " build-riscv - Build for RISC-V architecture (riscv64)"
|
||||
@echo " build-all - Build for all platforms and architectures"
|
||||
@echo " run - Run reticulum binary"
|
||||
@echo " install - Install dependencies"
|
||||
175
README.md
175
README.md
@@ -1,8 +1,11 @@
|
||||
# Reticulum-Go
|
||||
|
||||
[Reticulum Network](https://github.com/markqvist/Reticulum) implementation in Go `1.24`.
|
||||
> [!WARNING]
|
||||
> This project is still work in progress. Currently not compatible with the Python version.
|
||||
|
||||
Aiming for full spec compatibility with the Python version 0.9.2.
|
||||
[Reticulum Network](https://github.com/markqvist/Reticulum) implementation in Go `1.24+`.
|
||||
|
||||
Aiming to be fully compatible with the Python version.
|
||||
|
||||
# Testing
|
||||
|
||||
@@ -22,170 +25,4 @@ revive -config revive.toml -formatter friendly ./pkg/* ./cmd/* ./internal/*
|
||||
|
||||
## External Packages
|
||||
|
||||
- `golang.org/x/crypto` - Cryptographic primitives
|
||||
|
||||
## To-Do List
|
||||
|
||||
### Core Components (In Progress)
|
||||
- [x] Basic Configuration System
|
||||
- [x] Basic config structure
|
||||
- [x] Default settings
|
||||
- [x] Config file loading/saving
|
||||
- [x] Path management
|
||||
|
||||
- [x] Constants Definition (Testing required)
|
||||
- [x] Packet constants
|
||||
- [x] MTU constants
|
||||
- [x] Header types
|
||||
- [x] Additional protocol constants
|
||||
|
||||
- [x] Identity Management (Testing required)
|
||||
- [x] Identity creation
|
||||
- [x] Key pair generation
|
||||
- [x] Identity storage/recall
|
||||
- [x] Public key handling
|
||||
- [x] Signature verification
|
||||
- [x] Hash functions
|
||||
|
||||
- [x] Cryptographic Primitives (Testing required)
|
||||
- [x] Ed25519
|
||||
- [x] Curve25519
|
||||
- [x] AES-CBC
|
||||
- [x] SHA-256
|
||||
- [x] HKDF
|
||||
- [x] Secure random number generation
|
||||
- [x] HMAC
|
||||
|
||||
- [x] Packet Handling (In Progress)
|
||||
- [x] Packet creation
|
||||
- [x] Packet validation
|
||||
- [x] Basic proof system
|
||||
- [x] Packet encryption/decryption
|
||||
- [x] Signature verification
|
||||
- [x] Announce packet structure
|
||||
- [ ] Testing of packet encrypt/decrypt/sign/proof
|
||||
- [ ] Cross-client packet compatibility
|
||||
|
||||
- [x] Transport Layer (In Progress)
|
||||
- [x] Path management
|
||||
- [x] Basic packet routing
|
||||
- [x] Announce handling
|
||||
- [x] Link management
|
||||
- [x] Resource cleanup
|
||||
- [x] Network layer integration
|
||||
- [x] Basic announce implementation
|
||||
- [ ] Testing announce from go client to python client
|
||||
- [ ] Testing path finding and caching
|
||||
- [ ] Announce propagation optimization
|
||||
|
||||
- [x] Channel System (Testing Required)
|
||||
- [x] Channel creation and management
|
||||
- [x] Message handling
|
||||
- [x] Channel encryption
|
||||
- [x] Channel authentication
|
||||
- [x] Channel callbacks
|
||||
- [x] Integration with Buffer system
|
||||
- [ ] Testing with real network conditions
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
- [x] Buffer System (Testing Required)
|
||||
- [x] Raw channel reader/writer
|
||||
- [x] Buffered stream implementation
|
||||
- [x] Compression support
|
||||
- [ ] Testing with Channel system
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
- [x] Resolver System (Testing Required)
|
||||
- [x] Name resolution
|
||||
- [x] Cache management
|
||||
- [x] Announce handling
|
||||
- [x] Path resolution
|
||||
- [x] Integration with Transport layer
|
||||
- [ ] Testing with live network
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
### Interface Implementation (In Progress)
|
||||
- [x] UDP Interface
|
||||
- [x] TCP Interface
|
||||
- [x] Auto Interface
|
||||
- [ ] Local Interface (In Progress)
|
||||
- [ ] I2P Interface
|
||||
- [ ] Pipe Interface
|
||||
- [ ] RNode Interface
|
||||
- [ ] RNode Multiinterface
|
||||
- [ ] Serial Interface
|
||||
- [ ] AX25KISS Interface
|
||||
- [ ] Interface Discovery
|
||||
- [ ] Interface Modes
|
||||
- [ ] Full mode
|
||||
- [ ] Gateway mode
|
||||
- [ ] Access point mode
|
||||
- [ ] Roaming mode
|
||||
- [ ] Boundary mode
|
||||
|
||||
- [ ] Hot reloading interfaces
|
||||
|
||||
### Destination System (Testing required)
|
||||
- [x] Destination creation
|
||||
- [x] Destination types (IN/OUT)
|
||||
- [x] Destination aspects
|
||||
- [x] Announce implementation
|
||||
- [x] Ratchet support
|
||||
- [x] Request handlers
|
||||
|
||||
### Link System (Testing required)
|
||||
- [x] Link establishment
|
||||
- [x] Link teardown
|
||||
- [x] Basic packet transfer
|
||||
- [x] Encryption/Decryption
|
||||
- [x] Identity verification
|
||||
- [x] Request/Response handling
|
||||
- [x] Session key management
|
||||
- [x] Link state tracking
|
||||
|
||||
### Resource System (Testing required)
|
||||
- [x] Resource creation
|
||||
- [x] Resource transfer
|
||||
- [x] Compression
|
||||
- [x] Progress tracking
|
||||
- [x] Segmentation
|
||||
- [x] Cleanup routines
|
||||
|
||||
### Compatibility
|
||||
- [ ] RNS Utilities.
|
||||
- [ ] Reticulum config.
|
||||
|
||||
|
||||
### Testing & Validation (Priority)
|
||||
- [ ] Unit tests for all components
|
||||
- [ ] Identity tests
|
||||
- [ ] Packet tests
|
||||
- [ ] Transport tests
|
||||
- [ ] Interface tests
|
||||
- [ ] Announce tests
|
||||
- [ ] Channel tests
|
||||
- [ ] Buffer tests
|
||||
- [ ] Resolver tests
|
||||
- [ ] Link tests
|
||||
- [ ] Resource tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Go client to Go client
|
||||
- [ ] Go client to Python client
|
||||
- [ ] Interface compatibility
|
||||
- [ ] Path finding and resolution
|
||||
- [ ] Channel system end-to-end
|
||||
- [ ] Buffer system performance
|
||||
- [ ] Cross-client compatibility tests
|
||||
- [ ] Performance benchmarks
|
||||
- [ ] Security auditing (When Reticulum is 1.0 / stable)
|
||||
|
||||
### Documentation
|
||||
- [ ] API documentation
|
||||
- [ ] Usage examples
|
||||
|
||||
### Cleanup
|
||||
- [ ] Separate Cryptography from identity.go to their own files
|
||||
- [ ] Move constants to their own files
|
||||
- [ ] Remove default community interfaces in default config creation after testing
|
||||
- [ ] Optimize announce packet creation and caching
|
||||
- [ ] Improve debug logging system
|
||||
- `golang.org/x/crypto` `v0.37.0` - Cryptographic primitives
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Security Policy
|
||||
|
||||
I use [Socket](https://socket.dev/), [Deepsource](https://deepsource.com/) and [gosec](https://github.com/securego/gosec) for this project.
|
||||
We use [Socket](https://socket.dev/), [Deepsource](https://deepsource.com/) and [gosec](https://github.com/securego/gosec) for this project.
|
||||
|
||||
## Strict Verfication of Contributors and Code Quality
|
||||
|
||||
We are strict about the quality of the code and the contributors. Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information.
|
||||
|
||||
## Cryptography Dependencies
|
||||
|
||||
|
||||
167
TODO.md
Normal file
167
TODO.md
Normal file
@@ -0,0 +1,167 @@
|
||||
### Core Components (In Progress)
|
||||
|
||||
Last Updated: 2025-04-18
|
||||
|
||||
- [x] Basic Configuration System
|
||||
- [x] Basic config structure
|
||||
- [x] Default settings
|
||||
- [x] Config file loading/saving
|
||||
- [x] Path management
|
||||
|
||||
- [x] Constants Definition (Testing required)
|
||||
- [x] Packet constants
|
||||
- [x] MTU constants
|
||||
- [x] Header types
|
||||
- [x] Additional protocol constants
|
||||
|
||||
- [x] Identity Management (Testing required)
|
||||
- [x] Identity creation
|
||||
- [x] Key pair generation
|
||||
- [x] Identity storage/recall
|
||||
- [x] Public key handling
|
||||
- [x] Signature verification
|
||||
- [x] Hash functions
|
||||
|
||||
- [x] Cryptographic Primitives (Testing required)
|
||||
- [x] Ed25519
|
||||
- [x] Curve25519
|
||||
- [x] AES-128-CBC
|
||||
- [ ] AES-256-CBC
|
||||
- [x] SHA-256
|
||||
- [x] HKDF
|
||||
- [x] Secure random number generation
|
||||
- [x] HMAC
|
||||
|
||||
- [x] Packet Handling (In Progress)
|
||||
- [x] Packet creation
|
||||
- [x] Packet validation
|
||||
- [x] Basic proof system
|
||||
- [x] Packet encryption/decryption
|
||||
- [x] Signature verification
|
||||
- [x] Announce packet structure
|
||||
- [ ] Testing of packet encrypt/decrypt/sign/proof
|
||||
- [ ] Cross-client packet compatibility
|
||||
|
||||
- [x] Transport Layer (In Progress)
|
||||
- [x] Path management
|
||||
- [x] Basic packet routing
|
||||
- [x] Announce handling
|
||||
- [x] Link management
|
||||
- [x] Resource cleanup
|
||||
- [x] Network layer integration
|
||||
- [x] Basic announce implementation
|
||||
- [ ] Testing announce from go client to python client
|
||||
- [ ] Testing path finding and caching
|
||||
- [ ] Announce propagation optimization
|
||||
|
||||
- [x] Channel System (Testing Required)
|
||||
- [x] Channel creation and management
|
||||
- [x] Message handling
|
||||
- [x] Channel encryption
|
||||
- [x] Channel authentication
|
||||
- [x] Channel callbacks
|
||||
- [x] Integration with Buffer system
|
||||
- [ ] Testing with real network conditions
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
- [x] Buffer System (Testing Required)
|
||||
- [x] Raw channel reader/writer
|
||||
- [x] Buffered stream implementation
|
||||
- [x] Compression support
|
||||
- [ ] Testing with Channel system
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
- [x] Resolver System (Testing Required)
|
||||
- [x] Name resolution
|
||||
- [x] Cache management
|
||||
- [x] Announce handling
|
||||
- [x] Path resolution
|
||||
- [x] Integration with Transport layer
|
||||
- [ ] Testing with live network
|
||||
- [ ] Cross-client compatibility testing
|
||||
|
||||
### Interface Implementation (In Progress)
|
||||
- [x] UDP Interface
|
||||
- [x] TCP Interface
|
||||
- [x] Auto Interface
|
||||
- [ ] Local Interface (In Progress)
|
||||
- [ ] I2P Interface
|
||||
- [ ] Pipe Interface
|
||||
- [ ] RNode Interface
|
||||
- [ ] RNode Multiinterface
|
||||
- [ ] Serial Interface
|
||||
- [ ] AX25KISS Interface
|
||||
- [ ] Interface Discovery
|
||||
- [ ] Interface Modes
|
||||
- [ ] Full mode
|
||||
- [ ] Gateway mode
|
||||
- [ ] Access point mode
|
||||
- [ ] Roaming mode
|
||||
- [ ] Boundary mode
|
||||
|
||||
- [ ] Hot reloading interfaces
|
||||
|
||||
### Destination System (Testing required)
|
||||
- [x] Destination creation
|
||||
- [x] Destination types (IN/OUT)
|
||||
- [x] Destination aspects
|
||||
- [ ] Announce implementation (Fixing)
|
||||
- [x] Ratchet support
|
||||
- [x] Request handlers
|
||||
|
||||
### Link System (Testing required)
|
||||
- [x] Link establishment
|
||||
- [x] Link teardown
|
||||
- [x] Basic packet transfer
|
||||
- [x] Encryption/Decryption
|
||||
- [x] Identity verification
|
||||
- [x] Request/Response handling
|
||||
- [x] Session key management
|
||||
- [x] Link state tracking
|
||||
|
||||
### Resource System (Testing required)
|
||||
- [x] Resource creation
|
||||
- [x] Resource transfer
|
||||
- [x] Compression
|
||||
- [x] Progress tracking
|
||||
- [x] Segmentation
|
||||
- [x] Cleanup routines
|
||||
|
||||
### Compatibility
|
||||
- [ ] RNS Utilities.
|
||||
- [ ] Reticulum config.
|
||||
|
||||
|
||||
### Testing & Validation (Priority)
|
||||
- [ ] Unit tests for all components
|
||||
- [ ] Identity tests
|
||||
- [ ] Packet tests
|
||||
- [ ] Transport tests
|
||||
- [ ] Interface tests
|
||||
- [ ] Announce tests
|
||||
- [ ] Channel tests
|
||||
- [ ] Buffer tests
|
||||
- [ ] Resolver tests
|
||||
- [ ] Link tests
|
||||
- [ ] Resource tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Go client to Go client
|
||||
- [ ] Go client to Python client
|
||||
- [ ] Interface compatibility
|
||||
- [ ] Path finding and resolution
|
||||
- [ ] Channel system end-to-end
|
||||
- [ ] Buffer system performance
|
||||
- [ ] Cross-client compatibility tests
|
||||
- [ ] Performance benchmarks
|
||||
- [ ] Security auditing (When Reticulum is 1.0 / stable)
|
||||
|
||||
### Documentation
|
||||
- [ ] API documentation
|
||||
- [ ] Usage examples
|
||||
|
||||
### Cleanup
|
||||
- [ ] Separate Cryptography from identity.go to their own files
|
||||
- [ ] Move constants to their own files
|
||||
- [ ] Remove default community interfaces in default config creation after testing
|
||||
- [ ] Optimize announce packet creation and caching
|
||||
- [ ] Improve debug logging system
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 315 KiB |
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -48,7 +49,7 @@ const (
|
||||
DEBUG_PACKETS = 6 // Packet-level details
|
||||
DEBUG_ALL = 7 // Everything including identity operations
|
||||
APP_NAME = "Go-Client"
|
||||
APP_ASPECT = "node"
|
||||
APP_ASPECT = "node" // Always use "node" for node announces
|
||||
)
|
||||
|
||||
type Reticulum struct {
|
||||
@@ -62,9 +63,16 @@ type Reticulum struct {
|
||||
announceHistoryMu sync.RWMutex
|
||||
identity *identity.Identity
|
||||
destination *destination.Destination
|
||||
|
||||
// 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) {
|
||||
@@ -74,10 +82,10 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
||||
|
||||
// Set default app name and aspect if not provided
|
||||
if cfg.AppName == "" {
|
||||
cfg.AppName = "Go Client"
|
||||
cfg.AppName = APP_NAME
|
||||
}
|
||||
if cfg.AppAspect == "" {
|
||||
cfg.AppAspect = "node"
|
||||
cfg.AppAspect = APP_ASPECT // Always use "node" for node announcements
|
||||
}
|
||||
|
||||
if err := initializeDirectories(); err != nil {
|
||||
@@ -100,23 +108,16 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
||||
identity,
|
||||
destination.IN,
|
||||
destination.SINGLE,
|
||||
APP_NAME,
|
||||
APP_ASPECT,
|
||||
"reticulum",
|
||||
"node",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create destination: %v", err)
|
||||
}
|
||||
debugLog(DEBUG_INFO, "Created destination with hash: %x", dest.GetHash())
|
||||
|
||||
// Set default app data for announces
|
||||
appData := []byte(fmt.Sprintf(`{"app":"%s","aspect":"%s","version":"0.1.0"}`, APP_NAME, APP_ASPECT))
|
||||
dest.SetDefaultAppData(appData)
|
||||
|
||||
// Enable destination features
|
||||
dest.AcceptsLinks(true)
|
||||
dest.EnableRatchets("") // Empty string for default path
|
||||
dest.SetProofStrategy(destination.PROVE_APP)
|
||||
debugLog(DEBUG_VERBOSE, "Configured destination features")
|
||||
// Set node metadata
|
||||
nodeTimestamp := time.Now().Unix()
|
||||
|
||||
r := &Reticulum{
|
||||
config: cfg,
|
||||
@@ -128,8 +129,19 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
||||
announceHistory: make(map[string]announceRecord),
|
||||
identity: identity,
|
||||
destination: dest,
|
||||
|
||||
// Node-specific information
|
||||
maxTransferSize: 500, // Default 500KB
|
||||
nodeEnabled: true, // Enabled by default
|
||||
nodeTimestamp: nodeTimestamp,
|
||||
}
|
||||
|
||||
// Enable destination features
|
||||
dest.AcceptsLinks(true)
|
||||
dest.EnableRatchets("") // Empty string for default path
|
||||
dest.SetProofStrategy(destination.PROVE_APP)
|
||||
debugLog(DEBUG_VERBOSE, "Configured destination features")
|
||||
|
||||
// Initialize interfaces from config
|
||||
for name, ifaceConfig := range cfg.Interfaces {
|
||||
if !ifaceConfig.Enabled {
|
||||
@@ -276,18 +288,6 @@ func main() {
|
||||
log.Fatalf("Failed to create Reticulum instance: %v", err)
|
||||
}
|
||||
|
||||
// Create announce using r.identity
|
||||
announce, err := announce.NewAnnounce(
|
||||
r.identity,
|
||||
[]byte("HELLO WORLD"),
|
||||
nil,
|
||||
false,
|
||||
r.config,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create announce: %v", err)
|
||||
}
|
||||
|
||||
// Start monitoring interfaces
|
||||
go r.monitorInterfaces()
|
||||
|
||||
@@ -300,36 +300,45 @@ func main() {
|
||||
log.Fatalf("Failed to start Reticulum: %v", err)
|
||||
}
|
||||
|
||||
// Send initial announces after interfaces are ready
|
||||
time.Sleep(2 * time.Second) // Give interfaces time to connect
|
||||
for _, iface := range r.interfaces {
|
||||
if netIface, ok := iface.(common.NetworkInterface); ok {
|
||||
if netIface.IsEnabled() && netIface.IsOnline() {
|
||||
debugLog(2, "Sending initial announce on interface %s", netIface.GetName())
|
||||
if err := announce.Propagate([]common.NetworkInterface{netIface}); err != nil {
|
||||
debugLog(1, "Failed to propagate initial announce: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start periodic announces
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
ticker := time.NewTicker(5 * time.Minute) // Adjust interval as needed
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
debugLog(3, "Starting periodic announce cycle")
|
||||
|
||||
// Create a new announce packet for this cycle
|
||||
periodicAnnounce, err := announce.NewAnnounce(
|
||||
r.identity,
|
||||
r.createNodeAppData(),
|
||||
nil, // No ratchet ID for now
|
||||
false,
|
||||
r.config,
|
||||
)
|
||||
if err != nil {
|
||||
debugLog(1, "Failed to create periodic announce: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Propagate announce to all online interfaces
|
||||
var onlineInterfaces []common.NetworkInterface
|
||||
for _, iface := range r.interfaces {
|
||||
if netIface, ok := iface.(common.NetworkInterface); ok {
|
||||
if netIface.IsEnabled() && netIface.IsOnline() {
|
||||
debugLog(2, "Sending periodic announce on interface %s", netIface.GetName())
|
||||
if err := announce.Propagate([]common.NetworkInterface{netIface}); err != nil {
|
||||
debugLog(1, "Failed to propagate periodic announce: %v", err)
|
||||
}
|
||||
onlineInterfaces = append(onlineInterfaces, netIface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(onlineInterfaces) > 0 {
|
||||
debugLog(2, "Sending periodic announce on %d interfaces", len(onlineInterfaces))
|
||||
if err := periodicAnnounce.Propagate(onlineInterfaces); err != nil {
|
||||
debugLog(1, "Failed to propagate periodic announce: %v", err)
|
||||
}
|
||||
} else {
|
||||
debugLog(3, "No online interfaces for periodic announce")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -440,7 +449,7 @@ func (r *Reticulum) Start() error {
|
||||
// Send initial announce once per interface
|
||||
initialAnnounce, err := announce.NewAnnounce(
|
||||
r.identity,
|
||||
createAppData(r.config.AppName, r.config.AppAspect),
|
||||
r.createNodeAppData(),
|
||||
nil,
|
||||
false,
|
||||
r.config,
|
||||
@@ -474,7 +483,7 @@ func (r *Reticulum) Start() error {
|
||||
|
||||
periodicAnnounce, err := announce.NewAnnounce(
|
||||
r.identity,
|
||||
createAppData(r.config.AppName, r.config.AppAspect),
|
||||
r.createNodeAppData(),
|
||||
nil,
|
||||
false,
|
||||
r.config,
|
||||
@@ -559,25 +568,82 @@ func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appD
|
||||
debugLog(DEBUG_INFO, "Received announce from %x", destHash)
|
||||
debugLog(DEBUG_PACKETS, "Raw announce data: %x", appData)
|
||||
|
||||
var isNode bool
|
||||
var nodeEnabled bool
|
||||
var nodeTimestamp int64
|
||||
var nodeMaxSize int16
|
||||
|
||||
// Parse msgpack array
|
||||
if len(appData) > 0 {
|
||||
if appData[0] == 0x92 { // msgpack array of 2 elements
|
||||
if appData[0] == 0x92 {
|
||||
// Format [name, ticket] for standard peers
|
||||
debugLog(DEBUG_VERBOSE, "Received standard peer announce")
|
||||
isNode = false
|
||||
var pos = 1
|
||||
|
||||
// Parse first element (name)
|
||||
if appData[pos] == 0xc4 { // bin 8 format
|
||||
// Parse first element (NameBytes)
|
||||
if pos+1 < len(appData) && appData[pos] == 0xc4 {
|
||||
nameLen := int(appData[pos+1])
|
||||
name := string(appData[pos+2 : pos+2+nameLen])
|
||||
pos += 2 + nameLen
|
||||
debugLog(DEBUG_VERBOSE, "Announce name: %s", name)
|
||||
if pos+2+nameLen <= len(appData) {
|
||||
nameBytes := appData[pos+2 : pos+2+nameLen]
|
||||
name := string(nameBytes)
|
||||
pos += 2 + nameLen
|
||||
debugLog(DEBUG_VERBOSE, "Peer name: %s (bytes: %x)", name, nameBytes)
|
||||
|
||||
// Parse second element (app data)
|
||||
if pos < len(appData) && appData[pos] == 0xc4 { // bin 8 format
|
||||
dataLen := int(appData[pos+1])
|
||||
data := appData[pos+2 : pos+2+dataLen]
|
||||
debugLog(DEBUG_VERBOSE, "Announce app data: %s", string(data))
|
||||
// Parse second element (TicketValue)
|
||||
if pos < len(appData) {
|
||||
ticketValue := appData[pos] // Assuming fixint for now
|
||||
debugLog(DEBUG_VERBOSE, "Peer ticket value: %d", ticketValue)
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Could not parse ticket value from announce appData")
|
||||
}
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Could not parse name bytes from announce appData")
|
||||
}
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Announce appData name is not in expected bin 8 format")
|
||||
}
|
||||
} else if appData[0] == 0x93 {
|
||||
// Format [enable, timestamp, maxsize] for nodes
|
||||
debugLog(DEBUG_VERBOSE, "Received node announce")
|
||||
isNode = true
|
||||
var pos = 1
|
||||
|
||||
// Parse first element (Boolean enable/disable)
|
||||
if pos < len(appData) {
|
||||
if appData[pos] == 0xc3 {
|
||||
nodeEnabled = true
|
||||
} else if appData[pos] == 0xc2 {
|
||||
nodeEnabled = false
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Unexpected format for node enabled status: %x", appData[pos])
|
||||
}
|
||||
pos++
|
||||
debugLog(DEBUG_VERBOSE, "Node enabled: %v", nodeEnabled)
|
||||
|
||||
// Parse second element (Int32 timestamp)
|
||||
if pos+4 < len(appData) && appData[pos] == 0xd2 {
|
||||
pos++
|
||||
timestamp := binary.BigEndian.Uint32(appData[pos : pos+4])
|
||||
nodeTimestamp = int64(timestamp)
|
||||
pos += 4
|
||||
debugLog(DEBUG_VERBOSE, "Node timestamp: %d (%s)", timestamp, time.Unix(nodeTimestamp, 0))
|
||||
|
||||
// Parse third element (Int16 max transfer size)
|
||||
if pos+2 < len(appData) && appData[pos] == 0xd1 {
|
||||
pos++
|
||||
maxSize := binary.BigEndian.Uint16(appData[pos : pos+2])
|
||||
nodeMaxSize = int16(maxSize)
|
||||
debugLog(DEBUG_VERBOSE, "Node max transfer size: %d KB", nodeMaxSize)
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Could not parse max transfer size from node announce")
|
||||
}
|
||||
} else {
|
||||
debugLog(DEBUG_ERROR, "Could not parse timestamp from node announce")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugLog(DEBUG_VERBOSE, "Unknown announce data format: %x", appData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,14 +664,22 @@ func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appD
|
||||
}
|
||||
}
|
||||
|
||||
// Store announce in history
|
||||
// Create a better record with more info
|
||||
recordType := "peer"
|
||||
if isNode {
|
||||
recordType = "node"
|
||||
debugLog(DEBUG_INFO, "Storing node in announce history: enabled=%v, timestamp=%d, maxsize=%dKB",
|
||||
nodeEnabled, nodeTimestamp, nodeMaxSize)
|
||||
}
|
||||
|
||||
h.reticulum.announceHistoryMu.Lock()
|
||||
h.reticulum.announceHistory[identity.GetHexHash()] = announceRecord{
|
||||
// You can add fields here to store relevant announce data
|
||||
timestamp: time.Now().Unix(),
|
||||
appData: appData,
|
||||
}
|
||||
h.reticulum.announceHistoryMu.Unlock()
|
||||
|
||||
debugLog(DEBUG_VERBOSE, "Stored announce in history for identity %s", identity.GetHexHash())
|
||||
debugLog(DEBUG_VERBOSE, "Stored %s announce in history for identity %s", recordType, identity.GetHexHash())
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -619,24 +693,33 @@ func (r *Reticulum) GetDestination() *destination.Destination {
|
||||
return r.destination
|
||||
}
|
||||
|
||||
func createAppData(appName, appAspect string) []byte {
|
||||
nameString := fmt.Sprintf("%s.%s", appName, appAspect)
|
||||
func (r *Reticulum) createNodeAppData() []byte {
|
||||
// Create a msgpack array with 3 elements
|
||||
// [Bool, Int32, Int16] for [enable, timestamp, max_transfer_size]
|
||||
appData := []byte{0x93} // Array with 3 elements
|
||||
|
||||
// Create MessagePack array with 2 elements
|
||||
appData := []byte{0x92} // Fix array with 2 elements
|
||||
// Element 0: Boolean for enable/disable peer
|
||||
if r.nodeEnabled {
|
||||
appData = append(appData, 0xc3) // true
|
||||
} else {
|
||||
appData = append(appData, 0xc2) // false
|
||||
}
|
||||
|
||||
// First element: name string (always use str 8 format for consistency)
|
||||
nameBytes := []byte(nameString)
|
||||
appData = append(appData, 0xd9) // str 8 format
|
||||
appData = append(appData, byte(len(nameBytes))) // length
|
||||
appData = append(appData, nameBytes...) // string data
|
||||
// Element 1: Int32 timestamp (current time)
|
||||
// Update the timestamp when creating new announcements
|
||||
r.nodeTimestamp = time.Now().Unix()
|
||||
appData = append(appData, 0xd2) // int32 format
|
||||
timeBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(timeBytes, uint32(r.nodeTimestamp))
|
||||
appData = append(appData, timeBytes...)
|
||||
|
||||
// Second element: version string (always use str 8 format for consistency)
|
||||
version := "0.1.0"
|
||||
versionBytes := []byte(version)
|
||||
appData = append(appData, 0xd9) // str 8 format
|
||||
appData = append(appData, byte(len(versionBytes))) // length
|
||||
appData = append(appData, versionBytes...) // string data
|
||||
// Element 2: Int16 max transfer size in KB
|
||||
appData = append(appData, 0xd1) // int16 format
|
||||
sizeBytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(sizeBytes, uint16(r.maxTransferSize))
|
||||
appData = append(appData, sizeBytes...)
|
||||
|
||||
log.Printf("[DEBUG-7] Created node appData (msgpack [enable=%v, timestamp=%d, maxsize=%d]): %x",
|
||||
r.nodeEnabled, r.nodeTimestamp, r.maxTransferSize, appData)
|
||||
return appData
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,4 +2,4 @@ module github.com/Sudo-Ivan/reticulum-go
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require golang.org/x/crypto v0.31.0
|
||||
require golang.org/x/crypto v0.37.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,2 +1,2 @@
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
|
||||
@@ -169,44 +169,108 @@ func (a *Announce) HandleAnnounce(data []byte) error {
|
||||
|
||||
log.Printf("[DEBUG-7] Handling announce packet of %d bytes", len(data))
|
||||
|
||||
// Minimum packet size validation (header(2) + desthash(16) + enckey(32) + signkey(32) + namehash(10) +
|
||||
// randomhash(10) + signature(64) + min app data(3))
|
||||
if len(data) < 169 {
|
||||
log.Printf("[DEBUG-7] Invalid announce data length: %d bytes", len(data))
|
||||
// Minimum packet size validation
|
||||
// header(2) + desthash(16) + context(1) + enckey(32) + signkey(32) + namehash(10) +
|
||||
// randomhash(10) + signature(64) + min app data(3)
|
||||
if len(data) < 170 {
|
||||
log.Printf("[DEBUG-7] Invalid announce data length: %d bytes (minimum 170)", len(data))
|
||||
return errors.New("invalid announce data length")
|
||||
}
|
||||
|
||||
// Parse fields
|
||||
// Extract header and check packet type
|
||||
header := data[:2]
|
||||
if header[0]&0x03 != PACKET_TYPE_ANNOUNCE {
|
||||
return errors.New("not an announce packet")
|
||||
}
|
||||
|
||||
// Get hop count
|
||||
hopCount := header[1]
|
||||
destHash := data[2:18]
|
||||
encKey := data[18:50]
|
||||
signKey := data[50:82]
|
||||
nameHash := data[82:92]
|
||||
randomHash := data[92:102]
|
||||
signature := data[102:166]
|
||||
appData := data[166:]
|
||||
|
||||
log.Printf("[DEBUG-7] Announce fields: destHash=%x, encKey=%x, signKey=%x",
|
||||
destHash, encKey, signKey)
|
||||
log.Printf("[DEBUG-7] Name hash=%x, random hash=%x", nameHash, randomHash)
|
||||
|
||||
// Validate hop count
|
||||
if hopCount > MAX_HOPS {
|
||||
log.Printf("[DEBUG-7] Announce exceeded max hops: %d", hopCount)
|
||||
return errors.New("announce exceeded maximum hop count")
|
||||
}
|
||||
|
||||
// Create announced identity from public keys
|
||||
// Parse the packet based on header type
|
||||
headerType := (header[0] & 0b01000000) >> 6
|
||||
var contextByte byte
|
||||
var packetData []byte
|
||||
|
||||
if headerType == HEADER_TYPE_2 {
|
||||
// Header type 2 format: header(2) + desthash(16) + transportid(16) + context(1) + data
|
||||
if len(data) < 35 {
|
||||
return errors.New("header type 2 packet too short")
|
||||
}
|
||||
destHash := data[2:18]
|
||||
transportID := data[18:34]
|
||||
contextByte = data[34]
|
||||
packetData = data[35:]
|
||||
|
||||
log.Printf("[DEBUG-7] Header type 2 announce: destHash=%x, transportID=%x, context=%d",
|
||||
destHash, transportID, contextByte)
|
||||
} else {
|
||||
// Header type 1 format: header(2) + desthash(16) + context(1) + data
|
||||
if len(data) < 19 {
|
||||
return errors.New("header type 1 packet too short")
|
||||
}
|
||||
destHash := data[2:18]
|
||||
contextByte = data[18]
|
||||
packetData = data[19:]
|
||||
|
||||
log.Printf("[DEBUG-7] Header type 1 announce: destHash=%x, context=%d",
|
||||
destHash, contextByte)
|
||||
}
|
||||
|
||||
// Now parse the data portion according to the spec
|
||||
// Public Key (32) + Signing Key (32) + Name Hash (10) + Random Hash (10) + [Ratchet] + Signature (64) + App Data
|
||||
|
||||
if len(packetData) < 148 { // 32 + 32 + 10 + 10 + 64
|
||||
return errors.New("announce data too short")
|
||||
}
|
||||
|
||||
// Extract the components
|
||||
encKey := packetData[:32]
|
||||
signKey := packetData[32:64]
|
||||
nameHash := packetData[64:74]
|
||||
randomHash := packetData[74:84]
|
||||
|
||||
// The next field could be a ratchet (32 bytes) or signature (64 bytes)
|
||||
// We need to detect this somehow or use a flag
|
||||
// For now, assume no ratchet
|
||||
|
||||
signature := packetData[84:148]
|
||||
appData := packetData[148:]
|
||||
|
||||
log.Printf("[DEBUG-7] Announce fields: encKey=%x, signKey=%x", encKey, signKey)
|
||||
log.Printf("[DEBUG-7] Name hash=%x, random hash=%x", nameHash, randomHash)
|
||||
log.Printf("[DEBUG-7] Signature=%x, appDataLen=%d", signature[:8], len(appData))
|
||||
|
||||
// Get the destination hash from header
|
||||
var destHash []byte
|
||||
if headerType == HEADER_TYPE_2 {
|
||||
destHash = data[2:18]
|
||||
} else {
|
||||
destHash = data[2:18]
|
||||
}
|
||||
|
||||
// Combine public keys
|
||||
pubKey := append(encKey, signKey...)
|
||||
|
||||
// Create announced identity from public keys
|
||||
announcedIdentity := identity.FromPublicKey(pubKey)
|
||||
if announcedIdentity == nil {
|
||||
return errors.New("invalid identity public key")
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
signData := append(destHash, appData...)
|
||||
if !announcedIdentity.Verify(signData, signature) {
|
||||
signedData := make([]byte, 0)
|
||||
signedData = append(signedData, destHash...)
|
||||
signedData = append(signedData, encKey...)
|
||||
signedData = append(signedData, signKey...)
|
||||
signedData = append(signedData, nameHash...)
|
||||
signedData = append(signedData, randomHash...)
|
||||
signedData = append(signedData, appData...)
|
||||
|
||||
if !announcedIdentity.Verify(signedData, signature) {
|
||||
return errors.New("invalid announce signature")
|
||||
}
|
||||
|
||||
@@ -257,8 +321,8 @@ func (a *Announce) CreatePacket() []byte {
|
||||
// Create header
|
||||
header := CreateHeader(
|
||||
IFAC_NONE,
|
||||
HEADER_TYPE_1,
|
||||
0, // No context flag
|
||||
HEADER_TYPE_2, // Use header type 2 for announces
|
||||
0, // No context flag
|
||||
PROP_TYPE_BROADCAST,
|
||||
DEST_TYPE_SINGLE,
|
||||
PACKET_TYPE_ANNOUNCE,
|
||||
@@ -270,38 +334,85 @@ func (a *Announce) CreatePacket() []byte {
|
||||
// Add destination hash (16 bytes)
|
||||
packet = append(packet, a.destinationHash...)
|
||||
|
||||
// If using header type 2, add transport ID (16 bytes)
|
||||
// For broadcast announces, this is filled with zeroes
|
||||
transportID := make([]byte, 16)
|
||||
packet = append(packet, transportID...)
|
||||
|
||||
// Add context byte
|
||||
packet = append(packet, byte(0)) // Context byte, 0 for announces
|
||||
|
||||
// Add public key parts (32 bytes each)
|
||||
pubKey := a.identity.GetPublicKey()
|
||||
packet = append(packet, pubKey[:32]...) // Encryption key
|
||||
packet = append(packet, pubKey[32:]...) // Signing key
|
||||
encKey := pubKey[:32] // Encryption key
|
||||
signKey := pubKey[32:] // Signing key
|
||||
|
||||
// Start building data portion according to spec
|
||||
data := make([]byte, 0, 32+32+10+10+32+64+len(a.appData))
|
||||
data = append(data, encKey...) // Encryption key (32 bytes)
|
||||
data = append(data, signKey...) // Signing key (32 bytes)
|
||||
|
||||
// Determine if this is a node announce based on appData format
|
||||
var appName string
|
||||
if len(a.appData) > 2 && a.appData[0] == 0x93 {
|
||||
// This is a node announcement
|
||||
appName = "reticulum.node"
|
||||
} else if len(a.appData) > 3 && a.appData[0] == 0x92 && a.appData[1] == 0xc4 {
|
||||
nameLen := int(a.appData[2])
|
||||
if 3+nameLen <= len(a.appData) {
|
||||
appName = string(a.appData[3 : 3+nameLen])
|
||||
} else {
|
||||
appName = fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect)
|
||||
}
|
||||
} else {
|
||||
// Default fallback using config values
|
||||
appName = fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect)
|
||||
}
|
||||
|
||||
// Add name hash (10 bytes)
|
||||
nameHash := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect)))
|
||||
packet = append(packet, nameHash[:10]...)
|
||||
nameHash := sha256.Sum256([]byte(appName))
|
||||
nameHash10 := nameHash[:10]
|
||||
log.Printf("[DEBUG-6] Using name hash for '%s': %x", appName, nameHash10)
|
||||
data = append(data, nameHash10...)
|
||||
|
||||
// Add random hash (10 bytes)
|
||||
randomBytes := make([]byte, 10)
|
||||
rand.Read(randomBytes)
|
||||
packet = append(packet, randomBytes...)
|
||||
// Add random hash (10 bytes) - 5 bytes random + 5 bytes time
|
||||
randomHash := make([]byte, 10)
|
||||
rand.Read(randomHash[:5])
|
||||
timeBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix()))
|
||||
copy(randomHash[5:], timeBytes[:5])
|
||||
data = append(data, randomHash...)
|
||||
|
||||
// Add ratchet ID (32 bytes) - required in the packet format
|
||||
if a.ratchetID != nil {
|
||||
data = append(data, a.ratchetID...)
|
||||
} else {
|
||||
// If there's no ratchet, add 32 zero bytes as placeholder
|
||||
data = append(data, make([]byte, 32)...)
|
||||
}
|
||||
|
||||
// Create validation data for signature
|
||||
// Signature consists of destination hash, public keys, name hash, random hash, and app data
|
||||
validationData := make([]byte, 0)
|
||||
validationData = append(validationData, a.destinationHash...)
|
||||
validationData = append(validationData, pubKey[:32]...) // Encryption key
|
||||
validationData = append(validationData, pubKey[32:]...) // Signing key
|
||||
validationData = append(validationData, nameHash[:10]...)
|
||||
validationData = append(validationData, randomBytes...)
|
||||
validationData = append(validationData, encKey...)
|
||||
validationData = append(validationData, signKey...)
|
||||
validationData = append(validationData, nameHash10...)
|
||||
validationData = append(validationData, randomHash...)
|
||||
validationData = append(validationData, a.appData...)
|
||||
|
||||
// Add signature (64 bytes)
|
||||
signature := a.identity.Sign(validationData)
|
||||
packet = append(packet, signature...)
|
||||
data = append(data, signature...)
|
||||
|
||||
// Add app data
|
||||
if len(a.appData) > 0 {
|
||||
packet = append(packet, a.appData...)
|
||||
data = append(data, a.appData...)
|
||||
}
|
||||
|
||||
// Combine header and data
|
||||
packet = append(packet, data...)
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
@@ -398,36 +509,8 @@ func (a *Announce) GetPacket() []byte {
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
if a.packet == nil {
|
||||
// Generate hash from announce data
|
||||
h := sha256.New()
|
||||
h.Write(a.destinationHash)
|
||||
h.Write(a.identity.GetPublicKey())
|
||||
h.Write([]byte{a.hops})
|
||||
h.Write(a.appData)
|
||||
if a.ratchetID != nil {
|
||||
h.Write(a.ratchetID)
|
||||
}
|
||||
|
||||
// Construct packet
|
||||
packet := make([]byte, 0)
|
||||
packet = append(packet, PACKET_TYPE_ANNOUNCE)
|
||||
packet = append(packet, a.destinationHash...)
|
||||
packet = append(packet, a.identity.GetPublicKey()...)
|
||||
packet = append(packet, a.hops)
|
||||
packet = append(packet, a.appData...)
|
||||
if a.ratchetID != nil {
|
||||
packet = append(packet, a.ratchetID...)
|
||||
}
|
||||
|
||||
// Add signature
|
||||
signData := append(a.destinationHash, a.appData...)
|
||||
if a.ratchetID != nil {
|
||||
signData = append(signData, a.ratchetID...)
|
||||
}
|
||||
signature := a.identity.Sign(signData)
|
||||
packet = append(packet, signature...)
|
||||
|
||||
a.packet = packet
|
||||
// Use CreatePacket to generate the packet
|
||||
a.packet = a.CreatePacket()
|
||||
}
|
||||
|
||||
return a.packet
|
||||
|
||||
@@ -16,6 +16,6 @@ const (
|
||||
DestinationLink = 3
|
||||
|
||||
// Minimum packet sizes
|
||||
MinAnnounceSize = 169 // header(2) + desthash(16) + enckey(32) + signkey(32) +
|
||||
MinAnnounceSize = 170 // header(2) + desthash(16) + context(1) + enckey(32) + signkey(32) +
|
||||
// namehash(10) + randomhash(10) + signature(64) + min appdata(3)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -217,14 +219,58 @@ func (p *Packet) Serialize() ([]byte, error) {
|
||||
}
|
||||
|
||||
func NewAnnouncePacket(destHash []byte, identity *identity.Identity, appData []byte, transportID []byte) (*Packet, error) {
|
||||
log.Printf("[DEBUG-7] Creating new announce packet: destHash=%x, appData=%s", destHash, string(appData))
|
||||
log.Printf("[DEBUG-7] Creating new announce packet: destHash=%x, appData=%s", destHash, fmt.Sprintf("%x", appData))
|
||||
|
||||
// Create combined public key
|
||||
// Get public key separated into encryption and signing keys
|
||||
pubKey := identity.GetPublicKey()
|
||||
log.Printf("[DEBUG-6] Using public key: %x", pubKey)
|
||||
encKey := pubKey[:32]
|
||||
signKey := pubKey[32:]
|
||||
log.Printf("[DEBUG-6] Using public keys: encKey=%x, signKey=%x", encKey, signKey)
|
||||
|
||||
// Create signed data
|
||||
signedData := append(destHash, pubKey...)
|
||||
// Parse app name from first msgpack element if possible
|
||||
// For nodes, we'll use "reticulum.node" as the name hash
|
||||
var appName string
|
||||
if len(appData) > 2 && appData[0] == 0x93 {
|
||||
// This is a node announce, use standard node name
|
||||
appName = "reticulum.node"
|
||||
} else if len(appData) > 3 && appData[0] == 0x92 && appData[1] == 0xc4 {
|
||||
// Try to extract name from peer announce appData
|
||||
nameLen := int(appData[2])
|
||||
if 3+nameLen <= len(appData) {
|
||||
appName = string(appData[3 : 3+nameLen])
|
||||
} else {
|
||||
// Default fallback
|
||||
appName = "reticulum-go.node"
|
||||
}
|
||||
} else {
|
||||
// Default fallback
|
||||
appName = "reticulum-go.node"
|
||||
}
|
||||
|
||||
// Create name hash (10 bytes)
|
||||
nameHash := sha256.Sum256([]byte(appName))
|
||||
nameHash10 := nameHash[:10]
|
||||
log.Printf("[DEBUG-6] Using name hash for '%s': %x", appName, nameHash10)
|
||||
|
||||
// Create random hash (10 bytes) - 5 bytes random + 5 bytes time
|
||||
randomHash := make([]byte, 10)
|
||||
rand.Read(randomHash[:5])
|
||||
timeBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix()))
|
||||
copy(randomHash[5:], timeBytes[:5])
|
||||
log.Printf("[DEBUG-6] Generated random hash: %x", randomHash)
|
||||
|
||||
// Prepare ratchet ID if available (not yet implemented)
|
||||
var ratchetID []byte
|
||||
|
||||
// Prepare data for signature
|
||||
// Signature consists of destination hash, public keys, name hash, random hash, and app data
|
||||
signedData := make([]byte, 0, len(destHash)+len(encKey)+len(signKey)+len(nameHash10)+len(randomHash)+len(appData))
|
||||
signedData = append(signedData, destHash...)
|
||||
signedData = append(signedData, encKey...)
|
||||
signedData = append(signedData, signKey...)
|
||||
signedData = append(signedData, nameHash10...)
|
||||
signedData = append(signedData, randomHash...)
|
||||
signedData = append(signedData, appData...)
|
||||
log.Printf("[DEBUG-5] Created signed data (%d bytes)", len(signedData))
|
||||
|
||||
@@ -232,11 +278,22 @@ func NewAnnouncePacket(destHash []byte, identity *identity.Identity, appData []b
|
||||
signature := identity.Sign(signedData)
|
||||
log.Printf("[DEBUG-6] Generated signature: %x", signature)
|
||||
|
||||
// Combine all data
|
||||
data := append(pubKey, appData...)
|
||||
data = append(data, signature...)
|
||||
// Combine all fields according to spec
|
||||
// Data structure: Public Key (32) + Signing Key (32) + Name Hash (10) + Random Hash (10) + Ratchet (optional) + Signature (64) + App Data
|
||||
data := make([]byte, 0, 32+32+10+10+64+len(appData))
|
||||
data = append(data, encKey...) // Encryption key (32 bytes)
|
||||
data = append(data, signKey...) // Signing key (32 bytes)
|
||||
data = append(data, nameHash10...) // Name hash (10 bytes)
|
||||
data = append(data, randomHash...) // Random hash (10 bytes)
|
||||
if ratchetID != nil {
|
||||
data = append(data, ratchetID...) // Ratchet ID (32 bytes if present)
|
||||
}
|
||||
data = append(data, signature...) // Signature (64 bytes)
|
||||
data = append(data, appData...) // Application data (variable)
|
||||
|
||||
log.Printf("[DEBUG-5] Combined packet data (%d bytes)", len(data))
|
||||
|
||||
// Create the packet with header type 2 (two address fields)
|
||||
p := &Packet{
|
||||
HeaderType: HeaderType2,
|
||||
PacketType: PacketTypeAnnounce,
|
||||
|
||||
Reference in New Issue
Block a user