1 Commits
tinygo ... main

Author SHA1 Message Date
97353d430b chore: add CONTRIBUTORS file to document project contributors and their contributions
All checks were successful
Go Build Multi-Platform / build (amd64, linux) (push) Successful in 45s
Bearer / scan (push) Successful in 9s
Go Build Multi-Platform / build (amd64, darwin) (push) Successful in 46s
Go Build Multi-Platform / build (arm, windows) (push) Successful in 41s
Go Build Multi-Platform / build (arm, freebsd) (push) Successful in 43s
Go Build Multi-Platform / build (wasm, js) (push) Successful in 45s
Go Build Multi-Platform / build (arm64, windows) (push) Successful in 47s
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Successful in 1m21s
Go Revive Lint / lint (push) Successful in 57s
Run Gosec / tests (push) Successful in 1m9s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Successful in 2m33s
Go Build Multi-Platform / build (amd64, windows) (push) Successful in 9m27s
Go Build Multi-Platform / build (amd64, freebsd) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm, linux) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm64, darwin) (push) Successful in 9m28s
Go Build Multi-Platform / build (arm64, freebsd) (push) Successful in 9m30s
Go Build Multi-Platform / build (arm64, linux) (push) Successful in 9m28s
Go Build Multi-Platform / Create Release (push) Has been skipped
2026-01-01 13:10:55 -06:00
27 changed files with 191 additions and 1196 deletions

View File

@@ -5,15 +5,29 @@ on:
branches: [ "tinygo" ]
pull_request:
branches: [ "tinygo" ]
workflow_dispatch:
jobs:
tinygo-build-all:
tinygo-build:
permissions:
contents: read
strategy:
matrix:
include:
- name: tinygo-default
target: ""
output: reticulum-go-tinygo
make_target: tinygo-build
- name: tinygo-wasm
target: wasm
output: reticulum-go.wasm
make_target: tinygo-wasm
runs-on: ubuntu-latest
outputs:
build_complete: ${{ steps.build_step.outcome == 'success' }}
steps:
- name: Checkout code
uses: https://git.quad4.io/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -23,64 +37,28 @@ jobs:
with:
go-version: '1.24'
- name: Setup Task
uses: https://git.quad4.io/actions/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1
with:
version: '3.46.3'
- name: Install TinyGo
run: |
wget https://github.com/tinygo-org/tinygo/releases/download/v0.37.0/tinygo_0.37.0_amd64.deb
sudo dpkg -i tinygo_0.37.0_amd64.deb
- name: Build for all TinyGo targets
- name: Build with TinyGo
id: build_step
run: |
task tinygo-build-all || true
echo "Build process completed (some targets may have failed)"
make ${{ matrix.make_target }}
output_name="${{ matrix.output }}"
if [ -f "bin/${output_name}" ]; then
sha256sum "bin/${output_name}" | cut -d' ' -f1 > "bin/${output_name}.sha256"
echo "Built: ${output_name}"
echo "Generated checksum: bin/${output_name}.sha256"
else
echo "Build output not found: bin/${output_name}"
ls -la bin/
exit 1
fi
- name: Collect build results
run: |
mkdir -p artifacts
unsupported_file="artifacts/unsupported-microcontrollers.txt"
echo "# Unsupported Microcontrollers" > "$unsupported_file"
echo "# Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> "$unsupported_file"
echo "" >> "$unsupported_file"
failed_count=0
for log_file in bin/build-*.log; do
if [ -f "$log_file" ]; then
target=$(basename "$log_file" | sed 's/build-\(.*\)\.log/\1/')
binary_file="bin/reticulum-go-${target}"
if [ ! -f "$binary_file" ] || grep -qi "error\|Error\|ERROR\|failed\|Failed\|FAILED" "$log_file"; then
failed_count=$((failed_count + 1))
echo "## $target" >> "$unsupported_file"
echo "" >> "$unsupported_file"
if grep -qi "program too large\|overflowed\|too big\|LLVM ERROR\|Error while" "$log_file"; then
grep -i "program too large\|overflowed\|too big\|LLVM ERROR\|Error while" "$log_file" | head -5 >> "$unsupported_file"
else
tail -15 "$log_file" >> "$unsupported_file"
fi
echo "" >> "$unsupported_file"
echo "\`\`\`" >> "$unsupported_file"
tail -30 "$log_file" >> "$unsupported_file"
echo "\`\`\`" >> "$unsupported_file"
echo "" >> "$unsupported_file"
fi
fi
done
echo "Total failed builds: $failed_count" >> "$unsupported_file"
echo "Generated unsupported-microcontrollers.txt with $failed_count failed targets"
- name: Upload build artifacts
- name: Upload Artifact
uses: https://git.quad4.io/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
with:
name: tinygo-builds
path: |
bin/reticulum-go-*
artifacts/unsupported-microcontrollers.txt
if-no-files-found: warn
name: ${{ matrix.name }}
path: bin/${{ matrix.output }}*

14
CONTRIBUTORS Normal file
View File

@@ -0,0 +1,14 @@
CONTRIBUTORS
This file lists all contributors to the Reticulum-Go project.
Sudo-Ivan
Total commits: 442
First contribution: 2024-12-30
Last contribution: 2026-01-01
Mike Coles
Total commits: 1
First contribution: 2025-08-07
Last contribution: 2025-08-07

View File

@@ -243,37 +243,7 @@ tasks:
desc: Build binary with TinyGo compiler
cmds:
- mkdir -p {{.BUILD_DIR}}
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo -size short -opt=z -gc=leaking -panic=trap {{.MAIN_PACKAGE}}
tinygo-build-debug:
desc: Build binary optimized for debugging with TinyGo compiler
cmds:
- mkdir -p {{.BUILD_DIR}}
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo-debug -size short -opt=1 {{.MAIN_PACKAGE}}
tinygo-build-all:
desc: Build for all available TinyGo targets in parallel
cmds:
- mkdir -p {{.BUILD_DIR}}
- echo "Building for all TinyGo targets in parallel..."
- |
targets=$(tinygo targets)
failed=0
# Use xargs to build in parallel, limited to number of CPU cores
echo "$targets" | xargs -P $(nproc) -I {} sh -c '
target="{}";
if [ -n "$target" ]; then
echo "Building for target: $target";
if ! tinygo build -target "$target" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-"$target" -size short -opt=z -gc=leaking -panic=trap {{.MAIN_PACKAGE}} 2>&1 | tee {{.BUILD_DIR}}/build-"$target".log; then
echo "Failed to build for $target" >> {{.BUILD_DIR}}/build-"$target".log;
exit 1;
fi;
fi' || failed=1
echo "Build complete. Check {{.BUILD_DIR}}/ for outputs and logs."
if [ $failed -ne 0 ]; then
echo "Some target(s) failed to build. See logs in {{.BUILD_DIR}}/build-*.log"
fi
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo -size short {{.MAIN_PACKAGE}}
tinygo-wasm:
desc: Build WebAssembly binary with TinyGo compiler

View File

@@ -207,34 +207,6 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
} 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

View File

Binary file not shown.

4
go.mod
View File

@@ -3,6 +3,8 @@ module git.quad4.io/Networks/Reticulum-Go
go 1.24.0
require (
github.com/shamaton/msgpack/v2 v2.4.0
github.com/vmihailenco/msgpack/v5 v5.4.1
golang.org/x/crypto v0.46.0
)
require github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect

14
go.sum
View File

@@ -1,4 +1,14 @@
github.com/shamaton/msgpack/v2 v2.4.0 h1:O5Z08MRmbo0lA9o2xnQ4TXx6teJbPqEurqcCOQ8Oi/4=
github.com/shamaton/msgpack/v2 v2.4.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -10,8 +10,8 @@ import (
"sync"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
"github.com/vmihailenco/msgpack/v5"
)
type Manager struct {
@@ -88,7 +88,7 @@ func (m *Manager) SaveRatchet(identityHash []byte, ratchetKey []byte) error {
Received: time.Now().Unix(),
}
data, err := common.MsgpackMarshal(ratchetData)
data, err := msgpack.Marshal(ratchetData)
if err != nil {
return fmt.Errorf("failed to marshal ratchet data: %w", err)
}
@@ -146,7 +146,7 @@ func (m *Manager) LoadRatchets(identityHash []byte) (map[string][]byte, error) {
}
var ratchetData RatchetData
if err := common.MsgpackUnmarshal(data, &ratchetData); err != nil {
if err := msgpack.Unmarshal(data, &ratchetData); err != nil {
debug.Log(debug.DEBUG_ERROR, "Corrupted ratchet data", "file", entry.Name(), "error", err)
_ = os.Remove(filePath)
continue

View File

@@ -40,11 +40,6 @@ type InterfaceConfig struct {
DiscoveryScope string
DiscoveryPort int
DataPort int
Frequency uint32
Bandwidth uint32
SF uint8
CR uint8
TXPower uint8
}
// ReticulumConfig represents the main configuration structure

View File

@@ -1,17 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package common
import (
"github.com/shamaton/msgpack/v2"
)
// Marshal returns the MessagePack encoding of v.
func MsgpackMarshal(v interface{}) ([]byte, error) {
return msgpack.Marshal(v)
}
// Unmarshal parses the MessagePack-encoded data and stores the result in the value pointed to by v.
func MsgpackUnmarshal(data []byte, v interface{}) error {
return msgpack.Unmarshal(data, v)
}

View File

@@ -17,6 +17,7 @@ import (
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
"git.quad4.io/Networks/Reticulum-Go/pkg/identity"
"git.quad4.io/Networks/Reticulum-Go/pkg/packet"
"github.com/vmihailenco/msgpack/v5"
"golang.org/x/crypto/curve25519"
)
@@ -606,7 +607,7 @@ func (d *Destination) persistRatchets() error {
debug.Log(debug.DEBUG_PACKETS, "Persisting ratchets", "count", len(d.ratchets), "path", d.ratchetPath)
// Pack ratchets using msgpack
packedRatchets, err := common.MsgpackMarshal(d.ratchets)
packedRatchets, err := msgpack.Marshal(d.ratchets)
if err != nil {
return fmt.Errorf("failed to pack ratchets: %w", err)
}
@@ -624,7 +625,7 @@ func (d *Destination) persistRatchets() error {
}
// Pack the entire structure
finalData, err := common.MsgpackMarshal(persistedData)
finalData, err := msgpack.Marshal(persistedData)
if err != nil {
return fmt.Errorf("failed to pack ratchet data: %w", err)
}
@@ -687,7 +688,7 @@ func (d *Destination) reloadRatchets() error {
// Unpack outer structure
var persistedData map[string][]byte
if err := common.MsgpackUnmarshal(fileData, &persistedData); err != nil {
if err := msgpack.Unmarshal(fileData, &persistedData); err != nil {
return fmt.Errorf("failed to unpack ratchet data: %w", err)
}
@@ -704,7 +705,7 @@ func (d *Destination) reloadRatchets() error {
}
// Unpack ratchet list
if err := common.MsgpackUnmarshal(packedRatchets, &d.ratchets); err != nil {
if err := msgpack.Unmarshal(packedRatchets, &d.ratchets); err != nil {
return fmt.Errorf("failed to unpack ratchet list: %w", err)
}

View File

@@ -20,6 +20,7 @@ import (
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/cryptography"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
"github.com/vmihailenco/msgpack/v5"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
@@ -671,7 +672,7 @@ func (i *Identity) saveRatchets(path string) error {
}
// Pack ratchets using msgpack
packedRatchets, err := common.MsgpackMarshal(ratchetList)
packedRatchets, err := msgpack.Marshal(ratchetList)
if err != nil {
return fmt.Errorf("failed to pack ratchets: %w", err)
}
@@ -686,7 +687,7 @@ func (i *Identity) saveRatchets(path string) error {
}
// Pack the entire structure
finalData, err := common.MsgpackMarshal(persistedData)
finalData, err := msgpack.Marshal(persistedData)
if err != nil {
return fmt.Errorf("failed to pack ratchet data: %w", err)
}
@@ -799,7 +800,7 @@ func (i *Identity) loadRatchets(path string) error {
// Unpack outer structure: {"signature": ..., "ratchets": ...}
var persistedData map[string][]byte
if err := common.MsgpackUnmarshal(fileData, &persistedData); err != nil {
if err := msgpack.Unmarshal(fileData, &persistedData); err != nil {
return fmt.Errorf("failed to unpack ratchet data: %w", err)
}
@@ -817,7 +818,7 @@ func (i *Identity) loadRatchets(path string) error {
// Unpack ratchet list
var ratchetList [][]byte
if err := common.MsgpackUnmarshal(packedRatchets, &ratchetList); err != nil {
if err := msgpack.Unmarshal(packedRatchets, &ratchetList); err != nil {
return fmt.Errorf("failed to unpack ratchet list: %w", err)
}

View File

@@ -1,8 +1,5 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !tinygo
// +build !tinygo
package interfaces
import (

View File

@@ -1,96 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build tinygo
// +build tinygo
package interfaces
import (
"fmt"
"net"
"sync"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)
const (
HW_MTU = 1196
DEFAULT_DISCOVERY_PORT = 29716
DEFAULT_DATA_PORT = 42671
DEFAULT_GROUP_ID = "reticulum"
BITRATE_GUESS = 10 * 1000 * 1000
)
type AutoInterface struct {
BaseInterface
groupID []byte
discoveryPort int
dataPort int
discoveryScope string
peers map[string]*Peer
linkLocalAddrs []string
adoptedInterfaces map[string]string
interfaceServers map[string]net.Conn
multicastEchoes map[string]time.Time
mutex sync.RWMutex
outboundConn net.Conn
}
type Peer struct {
ifaceName string
lastHeard time.Time
conn net.PacketConn
}
func NewAutoInterface(name string, config *common.InterfaceConfig) (*AutoInterface, error) {
ai := &AutoInterface{
BaseInterface: BaseInterface{
Name: name,
Mode: common.IF_MODE_FULL,
Type: common.IF_TYPE_AUTO,
Online: false,
Enabled: config.Enabled,
Detached: false,
IN: true,
OUT: false,
MTU: HW_MTU,
Bitrate: BITRATE_GUESS,
},
discoveryPort: DEFAULT_DISCOVERY_PORT,
dataPort: DEFAULT_DATA_PORT,
peers: make(map[string]*Peer),
linkLocalAddrs: make([]string, 0),
adoptedInterfaces: make(map[string]string),
interfaceServers: make(map[string]net.Conn),
multicastEchoes: make(map[string]time.Time),
}
if config.Port != 0 {
ai.discoveryPort = config.Port
}
if config.GroupID != "" {
ai.groupID = []byte(config.GroupID)
} else {
ai.groupID = []byte("reticulum")
}
return ai, nil
}
func (ai *AutoInterface) Start() error {
// TinyGo doesn't support net.Interfaces() or multicast UDP
return fmt.Errorf("AutoInterface not supported in TinyGo - requires interface enumeration and multicast UDP")
}
func (ai *AutoInterface) Send(data []byte, address string) error {
return fmt.Errorf("Send not supported in TinyGo - requires UDP client connections")
}
func (ai *AutoInterface) Stop() error {
ai.Mutex.Lock()
defer ai.Mutex.Unlock()
ai.Online = false
return nil
}

View File

@@ -1,24 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package interfaces
const (
KISS_FEND = 0xC0
KISS_FESC = 0xDB
KISS_TFEND = 0xDC
KISS_TFESC = 0xDD
)
func escapeKISS(data []byte) []byte {
escaped := make([]byte, 0, len(data)*2)
for _, b := range data {
if b == KISS_FEND {
escaped = append(escaped, KISS_FESC, KISS_TFEND)
} else if b == KISS_FESC {
escaped = append(escaped, KISS_FESC, KISS_TFESC)
} else {
escaped = append(escaped, b)
}
}
return escaped
}

View File

@@ -1,29 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !tinygo
package interfaces
import (
"fmt"
)
type LoRaInterface struct {
BaseInterface
}
func NewLoRaInterface(name string, spi interface{}, cs, reset, dio0 interface{}, freq uint32, bw uint32, sf uint8, cr uint8, enabled bool) (*LoRaInterface, error) {
return nil, fmt.Errorf("LoRaInterface is only supported on TinyGo targets currently")
}
func (li *LoRaInterface) Start() error {
return fmt.Errorf("LoRaInterface is only supported on TinyGo targets currently")
}
func (li *LoRaInterface) Stop() error {
return nil
}
func (li *LoRaInterface) Send(data []byte, address string) error {
return fmt.Errorf("LoRaInterface is only supported on TinyGo targets currently")
}

View File

@@ -1,292 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build tinygo
package interfaces
import (
"fmt"
"machine"
"sync"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)
const (
REG_FIFO = 0x00
REG_OP_MODE = 0x01
REG_FRF_MSB = 0x06
REG_FRF_MID = 0x07
REG_FRF_LSB = 0x08
REG_PA_CONFIG = 0x09
REG_FIFO_ADDR_PTR = 0x0D
REG_FIFO_TX_BASE_ADDR = 0x0E
REG_FIFO_RX_BASE_ADDR = 0x0F
REG_FIFO_RX_CURRENT_ADDR = 0x10
REG_IRQ_FLAGS = 0x12
REG_RX_NB_BYTES = 0x13
REG_MODEM_CONFIG_1 = 0x1D
REG_MODEM_CONFIG_2 = 0x1E
REG_PREAMBLE_MSB = 0x20
REG_PREAMBLE_LSB = 0x21
REG_PAYLOAD_LENGTH = 0x22
REG_MODEM_CONFIG_3 = 0x26
REG_RSSI_WIDEBAND = 0x2C
REG_DETECTION_OPTIMIZE = 0x31
REG_DETECTION_THRESHOLD = 0x37
REG_SYNC_WORD = 0x39
REG_DIO_MAPPING_1 = 0x40
REG_VERSION = 0x42
MODE_LONG_RANGE_MODE = 0x80
MODE_SLEEP = 0x00
MODE_STDBY = 0x01
MODE_TX = 0x03
MODE_RX_CONTINUOUS = 0x05
IRQ_RX_DONE_MASK = 0x40
IRQ_PAYLOAD_CRC_ERROR_MASK = 0x20
IRQ_TX_DONE_MASK = 0x08
MAX_PKT_LENGTH = 255
)
// LoRaInterface provides a TinyGo SPI-based LoRa interface for SX127x.
type LoRaInterface struct {
BaseInterface
spi machine.SPI
cs machine.Pin
reset machine.Pin
dio0 machine.Pin
freq uint32
bw uint32
sf uint8
cr uint8
txPower uint8
done chan struct{}
stopOnce sync.Once
}
// NewLoRaInterface initializes a new LoRaInterface.
func NewLoRaInterface(name string, spi machine.SPI, cs, reset, dio0 machine.Pin, freq uint32, bw uint32, sf uint8, cr uint8, enabled bool) (*LoRaInterface, error) {
li := &LoRaInterface{
BaseInterface: NewBaseInterface(name, common.IF_TYPE_SERIAL, enabled),
spi: spi,
cs: cs,
reset: reset,
dio0: dio0,
freq: freq,
bw: bw,
sf: sf,
cr: cr,
txPower: 17,
done: make(chan struct{}),
}
li.MTU = MAX_PKT_LENGTH
li.Bitrate = int64(bw * uint32(sf) / (1 << (sf - 1)))
if enabled {
err := li.Start()
if err != nil {
return nil, err
}
}
return li, nil
}
// Start configures and brings the LoRaInterface online.
func (li *LoRaInterface) Start() error {
li.Mutex.Lock()
defer li.Mutex.Unlock()
if li.Online {
return nil
}
li.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
li.cs.High()
li.reset.Configure(machine.PinConfig{Mode: machine.PinOutput})
li.dio0.Configure(machine.PinConfig{Mode: machine.PinInput})
li.reset.Low()
time.Sleep(10 * time.Millisecond)
li.reset.High()
time.Sleep(10 * time.Millisecond)
version := li.readReg(REG_VERSION)
if version != 0x12 {
return fmt.Errorf("LoRa chip not found, version: 0x%02x", version)
}
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_SLEEP)
time.Sleep(10 * time.Millisecond)
frf := uint64(li.freq) << 19 / 32000000
li.writeReg(REG_FRF_MSB, uint8(frf>>16))
li.writeReg(REG_FRF_MID, uint8(frf>>8))
li.writeReg(REG_FRF_LSB, uint8(frf))
li.writeReg(REG_FIFO_TX_BASE_ADDR, 0)
li.writeReg(REG_FIFO_RX_BASE_ADDR, 0)
li.writeReg(0x0C, 0x23)
li.writeReg(REG_MODEM_CONFIG_3, 0x04)
li.writeReg(REG_PA_CONFIG, 0x80|(li.txPower-2))
var bwVal uint8
switch li.bw {
case 125000:
bwVal = 7
case 250000:
bwVal = 8
case 500000:
bwVal = 9
default:
bwVal = 7
}
li.writeReg(REG_MODEM_CONFIG_1, (bwVal<<4)|(li.cr-4)<<1|0x00)
li.writeReg(REG_MODEM_CONFIG_2, (li.sf<<4)|0x04)
li.writeReg(REG_SYNC_WORD, 0x12)
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_STDBY)
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_RX_CONTINUOUS)
li.Online = true
li.Enabled = true
go li.readLoop()
return nil
}
// readReg reads a byte from the given register.
func (li *LoRaInterface) readReg(reg uint8) uint8 {
li.cs.Low()
li.spi.Transfer(reg & 0x7F)
val, _ := li.spi.Transfer(0)
li.cs.High()
return val
}
// writeReg writes a byte to the given register.
func (li *LoRaInterface) writeReg(reg uint8, val uint8) {
li.cs.Low()
li.spi.Transfer(reg | 0x80)
li.spi.Transfer(val)
li.cs.High()
}
// readLoop polls the radio for received packets and dispatches them.
func (li *LoRaInterface) readLoop() {
for {
li.Mutex.RLock()
online := li.Online
done := li.done
li.Mutex.RUnlock()
if !online {
return
}
select {
case <-done:
return
default:
}
irq := li.readReg(REG_IRQ_FLAGS)
if irq&IRQ_RX_DONE_MASK != 0 {
li.writeReg(REG_IRQ_FLAGS, IRQ_RX_DONE_MASK)
if irq&IRQ_PAYLOAD_CRC_ERROR_MASK == 0 {
currentAddr := li.readReg(REG_FIFO_RX_CURRENT_ADDR)
li.writeReg(REG_FIFO_ADDR_PTR, currentAddr)
count := li.readReg(REG_RX_NB_BYTES)
packet := make([]byte, count)
li.cs.Low()
li.spi.Transfer(REG_FIFO)
for i := uint8(0); i < count; i++ {
packet[i], _ = li.spi.Transfer(0)
}
li.cs.High()
li.ProcessIncoming(packet)
}
}
time.Sleep(10 * time.Millisecond)
}
}
// Send transmits a packet over LoRa.
func (li *LoRaInterface) Send(data []byte, address string) error {
return li.ProcessOutgoing(data)
}
// ProcessOutgoing encodes and sends a packet.
func (li *LoRaInterface) ProcessOutgoing(data []byte) error {
li.Mutex.Lock()
defer li.Mutex.Unlock()
if !li.Online {
return fmt.Errorf("interface offline")
}
if len(data) > MAX_PKT_LENGTH {
return fmt.Errorf("packet too long for LoRa: %d", len(data))
}
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_STDBY)
li.writeReg(REG_FIFO_ADDR_PTR, 0)
li.cs.Low()
li.spi.Transfer(REG_FIFO | 0x80)
for _, b := range data {
li.spi.Transfer(b)
}
li.cs.High()
li.writeReg(REG_PAYLOAD_LENGTH, uint8(len(data)))
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_TX)
start := time.Now()
for {
if li.readReg(REG_IRQ_FLAGS)&IRQ_TX_DONE_MASK != 0 {
li.writeReg(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK)
break
}
if time.Since(start) > 2*time.Second {
debug.Log(debug.DEBUG_ERROR, "LoRa TX timeout")
break
}
time.Sleep(1 * time.Millisecond)
}
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_RX_CONTINUOUS)
li.TxBytes += uint64(len(data))
li.lastTx = time.Now()
return nil
}
// Stop disables the LoRaInterface.
func (li *LoRaInterface) Stop() error {
li.Mutex.Lock()
li.Online = false
li.Enabled = false
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_SLEEP)
li.Mutex.Unlock()
li.stopOnce.Do(func() {
if li.done != nil {
close(li.done)
}
})
return nil
}

View File

@@ -1,262 +0,0 @@
// SPDX-License-Identifier: 0BSD
package interfaces
import (
"encoding/binary"
"fmt"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)
const (
RNODE_CMD_DATA = 0x00
RNODE_CMD_FREQUENCY = 0x01
RNODE_CMD_BANDWIDTH = 0x02
RNODE_CMD_TXPOWER = 0x03
RNODE_CMD_SF = 0x04
RNODE_CMD_CR = 0x05
RNODE_CMD_RADIO_STATE = 0x06
RNODE_CMD_RADIO_LOCK = 0x07
RNODE_CMD_DETECT = 0x08
RNODE_CMD_LEAVE = 0x0A
RNODE_CMD_ST_ALOCK = 0x0B
RNODE_CMD_LT_ALOCK = 0x0C
RNODE_CMD_READY = 0x0F
RNODE_CMD_STAT_RX = 0x21
RNODE_CMD_STAT_TX = 0x22
RNODE_CMD_STAT_RSSI = 0x23
RNODE_CMD_STAT_SNR = 0x24
RNODE_CMD_FW_VERSION = 0x50
RNODE_CMD_PLATFORM = 0x48
RNODE_CMD_MCU = 0x49
RNODE_DETECT_REQ = 0x73
RNODE_DETECT_RESP = 0x46
RNODE_RSSI_OFFSET = 157
)
// RNodeInterface represents a Reticulum node interface.
type RNodeInterface struct {
Interface
frequency uint32
bandwidth uint32
sf uint8
cr uint8
txPower uint8
callback common.PacketCallback
rFrequency uint32
rBandwidth uint32
rTXPower uint8
rSF uint8
rCR uint8
rState uint8
rDetected bool
rMajVer uint8
rMinVer uint8
interfaceReady bool
packetQueue [][]byte
}
// NewRNodeInterface creates a new RNodeInterface.
func NewRNodeInterface(name string, underlying Interface, freq uint32, bw uint32, sf uint8, cr uint8, txPower uint8) (*RNodeInterface, error) {
ri := &RNodeInterface{
Interface: underlying,
frequency: freq,
bandwidth: bw,
sf: sf,
cr: cr,
txPower: txPower,
}
underlying.SetPacketCallback(ri.handleIncoming)
return ri, nil
}
// SetPacketCallback sets the packet callback for the RNodeInterface.
func (ri *RNodeInterface) SetPacketCallback(cb common.PacketCallback) {
ri.callback = cb
}
func (ri *RNodeInterface) handleIncoming(data []byte, ni common.NetworkInterface) {
if len(data) < 1 {
return
}
cmd := data[0]
payload := data[1:]
switch cmd {
case RNODE_CMD_DATA:
if ri.callback != nil {
ri.callback(payload, ri)
}
case RNODE_CMD_READY:
ri.processQueue()
case RNODE_CMD_DETECT:
if len(payload) >= 1 && payload[0] == RNODE_DETECT_RESP {
ri.rDetected = true
}
case RNODE_CMD_FW_VERSION:
if len(payload) >= 2 {
ri.rMajVer = payload[0]
ri.rMinVer = payload[1]
debug.Log(debug.DEBUG_INFO, "RNode firmware version", "name", ri.GetName(), "version", fmt.Sprintf("%d.%d", ri.rMajVer, ri.rMinVer))
}
case RNODE_CMD_FREQUENCY:
if len(payload) >= 4 {
ri.rFrequency = binary.BigEndian.Uint32(payload)
}
case RNODE_CMD_BANDWIDTH:
if len(payload) >= 4 {
ri.rBandwidth = binary.BigEndian.Uint32(payload)
}
case RNODE_CMD_TXPOWER:
if len(payload) >= 1 {
ri.rTXPower = payload[0]
}
case RNODE_CMD_SF:
if len(payload) >= 1 {
ri.rSF = payload[0]
}
case RNODE_CMD_CR:
if len(payload) >= 1 {
ri.rCR = payload[0]
}
case RNODE_CMD_RADIO_STATE:
if len(payload) >= 1 {
ri.rState = payload[0]
}
case RNODE_CMD_STAT_RSSI:
if len(payload) >= 1 {
rssi := int(payload[0]) - RNODE_RSSI_OFFSET
debug.Log(debug.DEBUG_VERBOSE, "RNode RSSI", "name", ri.GetName(), "rssi", rssi)
}
case RNODE_CMD_STAT_SNR:
if len(payload) >= 1 {
snr := float32(int8(payload[0])) * 0.25
debug.Log(debug.DEBUG_VERBOSE, "RNode SNR", "name", ri.GetName(), "snr", snr)
}
default:
debug.Log(debug.DEBUG_ALL, "RNode received command", "cmd", fmt.Sprintf("0x%02x", cmd), "len", len(payload))
}
}
func (ri *RNodeInterface) processQueue() {
ri.interfaceReady = true
if len(ri.packetQueue) > 0 {
packet := ri.packetQueue[0]
ri.packetQueue = ri.packetQueue[1:]
_ = ri.Send(packet, "")
}
}
// Start initializes the RNodeInterface and configures device parameters.
func (ri *RNodeInterface) Start() error {
err := ri.Interface.Start()
if err != nil {
return err
}
time.Sleep(2 * time.Second)
if err := ri.detect(); err != nil {
return err
}
debug.Log(debug.DEBUG_INFO, "Initializing RNode...", "name", ri.GetName())
if ri.frequency != 0 {
freqBytes := make([]byte, 4)
binary.BigEndian.PutUint32(freqBytes, ri.frequency)
if err := ri.sendRNodeCommand(RNODE_CMD_FREQUENCY, freqBytes); err != nil {
return err
}
}
if ri.bandwidth != 0 {
bwBytes := make([]byte, 4)
binary.BigEndian.PutUint32(bwBytes, ri.bandwidth)
if err := ri.sendRNodeCommand(RNODE_CMD_BANDWIDTH, bwBytes); err != nil {
return err
}
}
if ri.sf != 0 {
if err := ri.sendRNodeCommand(RNODE_CMD_SF, []byte{ri.sf}); err != nil {
return err
}
}
if ri.cr != 0 {
if err := ri.sendRNodeCommand(RNODE_CMD_CR, []byte{ri.cr}); err != nil {
return err
}
}
if ri.txPower != 0 {
if err := ri.sendRNodeCommand(RNODE_CMD_TXPOWER, []byte{ri.txPower}); err != nil {
return err
}
}
if err := ri.sendRNodeCommand(RNODE_CMD_RADIO_STATE, []byte{0x01}); err != nil {
return err
}
ri.interfaceReady = true
debug.Log(debug.DEBUG_INFO, "RNode initialized", "name", ri.GetName())
return nil
}
// detect attempts to detect the RNode device and obtain firmware version.
func (ri *RNodeInterface) detect() error {
detectCmd := []byte{RNODE_DETECT_REQ}
if err := ri.sendRNodeCommand(RNODE_CMD_DETECT, detectCmd); err != nil {
return err
}
start := time.Now()
for !ri.rDetected {
if time.Since(start) > 2*time.Second {
debug.Log(debug.DEBUG_ERROR, "RNode detection timed out", "name", ri.GetName())
break
}
time.Sleep(100 * time.Millisecond)
}
if err := ri.sendRNodeCommand(RNODE_CMD_FW_VERSION, []byte{0x00}); err != nil {
return err
}
return nil
}
// sendRNodeCommand sends a command to the RNode device.
func (ri *RNodeInterface) sendRNodeCommand(cmd byte, data []byte) error {
if kissInterface, ok := ri.Interface.(interface {
SendKISS(byte, []byte) error
}); ok {
return kissInterface.SendKISS(cmd, data)
}
frame := make([]byte, 0, len(data)+1)
frame = append(frame, cmd)
frame = append(frame, data...)
return ri.Interface.Send(frame, "")
}
// Send transmits data using the underlying interface.
func (ri *RNodeInterface) Send(data []byte, addr string) error {
if !ri.interfaceReady {
ri.packetQueue = append(ri.packetQueue, data)
return nil
}
return ri.Interface.Send(data, addr)
}

View File

@@ -1,29 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !tinygo
package interfaces
import (
"fmt"
)
type SerialInterface struct {
BaseInterface
}
func NewSerialInterface(name string, portName string, baud uint32, enabled bool) (*SerialInterface, error) {
return nil, fmt.Errorf("SerialInterface is only supported on TinyGo targets currently")
}
func (si *SerialInterface) Start() error {
return fmt.Errorf("SerialInterface is only supported on TinyGo targets currently")
}
func (si *SerialInterface) Stop() error {
return nil
}
func (si *SerialInterface) Send(data []byte, address string) error {
return fmt.Errorf("SerialInterface is only supported on TinyGo targets currently")
}

View File

@@ -1,221 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build tinygo
package interfaces
import (
"fmt"
"machine"
"sync"
"time"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
)
const (
SERIAL_DEFAULT_BAUD = 115200
SERIAL_MTU = 1500
)
// SerialInterface implements a serial interface using TinyGo UART.
type SerialInterface struct {
BaseInterface
uart *machine.UART
baud uint32
done chan struct{}
stopOnce sync.Once
}
// NewSerialInterface creates and initializes a new SerialInterface.
func NewSerialInterface(name string, portName string, baud uint32, enabled bool) (*SerialInterface, error) {
if baud == 0 {
baud = SERIAL_DEFAULT_BAUD
}
uart, err := getUART(portName)
if err != nil {
return nil, err
}
si := &SerialInterface{
BaseInterface: NewBaseInterface(name, common.IF_TYPE_SERIAL, enabled),
uart: uart,
baud: baud,
done: make(chan struct{}),
}
si.MTU = SERIAL_MTU
si.Bitrate = int64(baud)
if enabled {
err := si.Start()
if err != nil {
return nil, err
}
}
return si, nil
}
// getUART returns a TinyGo UART handle by name or index.
func getUART(name string) (*machine.UART, error) {
switch name {
case "UART0", "0":
return machine.UART0, nil
case "UART1", "1":
return machine.UART1, nil
case "UART2", "2":
return machine.UART2, nil
default:
if name == "" {
return machine.UART0, nil
}
return nil, fmt.Errorf("unknown UART: %s", name)
}
}
// Start enables the serial interface and starts the read loop.
func (si *SerialInterface) Start() error {
si.Mutex.Lock()
defer si.Mutex.Unlock()
if si.Online {
return nil
}
err := si.uart.Configure(machine.UARTConfig{
BaudRate: si.baud,
})
if err != nil {
return fmt.Errorf("failed to configure UART: %w", err)
}
si.Online = true
si.Enabled = true
go si.readLoop()
return nil
}
// Stop disables the serial interface.
func (si *SerialInterface) Stop() error {
si.Mutex.Lock()
si.Online = false
si.Enabled = false
si.Mutex.Unlock()
si.stopOnce.Do(func() {
if si.done != nil {
close(si.done)
}
})
return nil
}
// readLoop reads and processes frames from the UART, handling KISS framing.
func (si *SerialInterface) readLoop() {
buffer := make([]byte, si.MTU)
dataBuffer := make([]byte, 0, si.MTU)
inFrame := false
escape := false
for {
si.Mutex.RLock()
online := si.Online
done := si.done
si.Mutex.RUnlock()
if !online {
return
}
select {
case <-done:
return
default:
}
if si.uart.Buffered() > 0 {
n, err := si.uart.Read(buffer)
if err != nil {
debug.Log(debug.DEBUG_ERROR, "Serial read error", "name", si.Name, "error", err)
time.Sleep(100 * time.Millisecond)
continue
}
if n > 0 {
for i := 0; i < n; i++ {
b := buffer[i]
if b == KISS_FEND {
if inFrame && len(dataBuffer) > 0 {
packet := make([]byte, len(dataBuffer))
copy(packet, dataBuffer)
si.ProcessIncoming(packet)
dataBuffer = dataBuffer[:0]
}
inFrame = true
escape = false
continue
}
if inFrame {
if b == KISS_FESC {
escape = true
} else {
if escape {
if b == KISS_TFEND {
b = KISS_FEND
} else if b == KISS_TFESC {
b = KISS_FESC
}
escape = false
}
dataBuffer = append(dataBuffer, b)
}
}
}
}
} else {
time.Sleep(10 * time.Millisecond)
}
}
}
// Send transmits data using KISS protocol with the default command 0x00.
func (si *SerialInterface) Send(data []byte, address string) error {
return si.SendKISS(0x00, data)
}
// SendKISS sends a KISS-encoded packet over the serial UART.
func (si *SerialInterface) SendKISS(command byte, data []byte) error {
si.Mutex.RLock()
online := si.Online
si.Mutex.RUnlock()
if !online {
return fmt.Errorf("interface offline")
}
frame := make([]byte, 0, len(data)*2+3)
frame = append(frame, KISS_FEND)
frame = append(frame, command)
frame = append(frame, escapeKISS(data)...)
frame = append(frame, KISS_FEND)
_, err := si.uart.Write(frame)
if err != nil {
return err
}
si.Mutex.Lock()
si.TxBytes += uint64(len(frame))
si.lastTx = time.Now()
si.Mutex.Unlock()
return nil
}

View File

@@ -18,6 +18,11 @@ const (
HDLC_ESC = 0x7D
HDLC_ESC_MASK = 0x20
KISS_FEND = 0xC0
KISS_FESC = 0xDB
KISS_TFEND = 0xDC
KISS_TFESC = 0xDD
DEFAULT_MTU = 1064
BITRATE_GUESS_VAL = 10 * 1000 * 1000
RECONNECT_WAIT = 5
@@ -324,6 +329,20 @@ func escapeHDLC(data []byte) []byte {
return escaped
}
func escapeKISS(data []byte) []byte {
escaped := make([]byte, 0, len(data)*2)
for _, b := range data {
if b == KISS_FEND {
escaped = append(escaped, KISS_FESC, KISS_TFEND)
} else if b == KISS_FESC {
escaped = append(escaped, KISS_FESC, KISS_TFESC)
} else {
escaped = append(escaped, b)
}
}
return escaped
}
func (tc *TCPClientInterface) SetPacketCallback(cb common.PacketCallback) {
tc.packetCallback = cb
}

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !linux || tinygo
// +build !linux tinygo
//go:build !linux
// +build !linux
package interfaces
@@ -14,11 +14,3 @@ import (
func platformGetRTT(fd uintptr) time.Duration {
return 0
}
func (tc *TCPClientInterface) setTimeoutsLinux() error {
return nil
}
func (tc *TCPClientInterface) setTimeoutsOSX() error {
return nil
}

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build linux && !tinygo
// +build linux,!tinygo
//go:build linux
// +build linux
package interfaces

View File

@@ -1,8 +1,5 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !tinygo
// +build !tinygo
package interfaces
import (
@@ -51,6 +48,30 @@ func NewUDPInterface(name string, addr string, target string, enabled bool) (*UD
return ui, nil
}
func (ui *UDPInterface) GetName() string {
return ui.Name
}
func (ui *UDPInterface) GetType() common.InterfaceType {
return ui.Type
}
func (ui *UDPInterface) GetMode() common.InterfaceMode {
return ui.Mode
}
func (ui *UDPInterface) IsOnline() bool {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.Online
}
func (ui *UDPInterface) IsDetached() bool {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.Detached
}
func (ui *UDPInterface) Detach() {
ui.Mutex.Lock()
defer ui.Mutex.Unlock()
@@ -90,12 +111,34 @@ func (ui *UDPInterface) Send(data []byte, addr string) error {
return err
}
func (ui *UDPInterface) SetPacketCallback(callback common.PacketCallback) {
ui.Mutex.Lock()
defer ui.Mutex.Unlock()
ui.packetCallback = callback
}
func (ui *UDPInterface) GetPacketCallback() common.PacketCallback {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.packetCallback
}
func (ui *UDPInterface) ProcessIncoming(data []byte) {
if callback := ui.GetPacketCallback(); callback != nil {
callback(data, ui)
}
}
func (ui *UDPInterface) ProcessOutgoing(data []byte) error {
if !ui.IsOnline() {
return fmt.Errorf("interface offline")
}
_, err := ui.conn.Write(data)
if ui.targetAddr == nil {
return fmt.Errorf("no target address configured")
}
_, err := ui.conn.WriteToUDP(data, ui.targetAddr)
if err != nil {
return fmt.Errorf("UDP write failed: %v", err)
}
@@ -111,6 +154,38 @@ func (ui *UDPInterface) GetConn() net.Conn {
return ui.conn
}
func (ui *UDPInterface) GetTxBytes() uint64 {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.TxBytes
}
func (ui *UDPInterface) GetRxBytes() uint64 {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.RxBytes
}
func (ui *UDPInterface) GetMTU() int {
return ui.MTU
}
func (ui *UDPInterface) GetBitrate() int {
return int(ui.Bitrate)
}
func (ui *UDPInterface) Enable() {
ui.Mutex.Lock()
defer ui.Mutex.Unlock()
ui.Online = true
}
func (ui *UDPInterface) Disable() {
ui.Mutex.Lock()
defer ui.Mutex.Unlock()
ui.Online = false
}
func (ui *UDPInterface) Start() error {
ui.Mutex.Lock()
if ui.conn != nil {
@@ -210,3 +285,9 @@ func (ui *UDPInterface) readLoop() {
}
}
}
func (ui *UDPInterface) IsEnabled() bool {
ui.Mutex.RLock()
defer ui.Mutex.RUnlock()
return ui.Enabled && ui.Online && !ui.Detached
}

View File

@@ -1,68 +0,0 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build tinygo
// +build tinygo
package interfaces
import (
"fmt"
"net"
"sync"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)
type UDPInterface struct {
BaseInterface
conn net.Conn
addr *net.UDPAddr
targetAddr *net.UDPAddr
readBuffer []byte
done chan struct{}
stopOnce sync.Once
}
func NewUDPInterface(name string, addr string, target string, enabled bool) (*UDPInterface, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
var targetAddr *net.UDPAddr
if target != "" {
targetAddr, err = net.ResolveUDPAddr("udp", target)
if err != nil {
return nil, err
}
}
ui := &UDPInterface{
BaseInterface: NewBaseInterface(name, common.IF_TYPE_UDP, enabled),
addr: udpAddr,
targetAddr: targetAddr,
readBuffer: make([]byte, common.NUM_1064),
done: make(chan struct{}),
}
ui.MTU = common.NUM_1064
return ui, nil
}
func (ui *UDPInterface) Start() error {
// TinyGo doesn't support UDP servers, only clients
return fmt.Errorf("UDPInterface not supported in TinyGo - UDP server functionality requires net.ListenUDP")
}
func (ui *UDPInterface) Send(data []byte, addr string) error {
// TinyGo doesn't support UDP sending
return fmt.Errorf("UDPInterface Send not supported in TinyGo - requires UDP client functionality")
}
func (ui *UDPInterface) Stop() error {
ui.Mutex.Lock()
defer ui.Mutex.Unlock()
ui.Online = false
return nil
}

View File

@@ -28,6 +28,7 @@ import (
"git.quad4.io/Networks/Reticulum-Go/pkg/resolver"
"git.quad4.io/Networks/Reticulum-Go/pkg/resource"
"git.quad4.io/Networks/Reticulum-Go/pkg/transport"
"github.com/vmihailenco/msgpack/v5"
)
const (
@@ -307,7 +308,7 @@ func (l *Link) Request(path string, data []byte, timeout time.Duration) (*Reques
pathHash := identity.TruncatedHash([]byte(path))
requestData := []interface{}{time.Now().Unix(), pathHash, data}
packedRequest, err := common.MsgpackMarshal(requestData)
packedRequest, err := msgpack.Marshal(requestData)
if err != nil {
return nil, fmt.Errorf("failed to pack request: %w", err)
}
@@ -1028,7 +1029,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
}
var requestData []interface{}
if err := common.MsgpackUnmarshal(plaintext, &requestData); err != nil {
if err := msgpack.Unmarshal(plaintext, &requestData); err != nil {
return fmt.Errorf("failed to unpack request: %w", err)
}
@@ -1059,7 +1060,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
case string:
requestPayload = []byte(payload)
default:
packed, err := common.MsgpackMarshal(payload)
packed, err := msgpack.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to pack request_payload: %w", err)
}
@@ -1088,7 +1089,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
func (l *Link) handleResponse(plaintext []byte) error {
var responseData []interface{}
if err := common.MsgpackUnmarshal(plaintext, &responseData); err != nil {
if err := msgpack.Unmarshal(plaintext, &responseData); err != nil {
return fmt.Errorf("failed to unpack response: %w", err)
}
@@ -1123,7 +1124,7 @@ func (l *Link) handleResponse(plaintext []byte) error {
func (l *Link) sendResponse(requestID []byte, response interface{}) error {
responseData := []interface{}{requestID, response}
packedResponse, err := common.MsgpackMarshal(responseData)
packedResponse, err := msgpack.Marshal(responseData)
if err != nil {
return fmt.Errorf("failed to pack response: %w", err)
}

View File

@@ -6,7 +6,7 @@ import (
"fmt"
"math"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
"github.com/vmihailenco/msgpack/v5"
)
const (
@@ -117,12 +117,12 @@ func (ra *ResourceAdvertisement) Pack(segment int) ([]byte, error) {
"m": hashmap,
}
return common.MsgpackMarshal(dict)
return msgpack.Marshal(dict)
}
func UnpackResourceAdvertisement(data []byte) (*ResourceAdvertisement, error) {
var dict map[string]interface{}
if err := common.MsgpackUnmarshal(data, &dict); err != nil {
if err := msgpack.Unmarshal(data, &dict); err != nil {
return nil, fmt.Errorf("failed to unpack advertisement: %w", err)
}