31 Commits
main ... tinygo

Author SHA1 Message Date
6ae409c1db fix: add #nosec annotation for Bitrate conversion in SerialInterface initialization
All checks were successful
TinyGo Build / tinygo-build-all (push) Successful in 10m10s
2026-01-01 01:02:13 -06:00
bfa0669143 style: format code across all files 2026-01-01 01:01:27 -06:00
2867f68a90 refactor: replace msgpack calls with common package methods in advertisement.go for packing and unpacking 2026-01-01 01:00:06 -06:00
fbb48f2295 refactor: replace msgpack calls with common package methods in link.go for request and response handling 2026-01-01 01:00:02 -06:00
132872c2d6 refactor: remove KISS protocol escape functions and related constants from tcp.go 2026-01-01 00:59:57 -06:00
1d3590985e feat: implement SerialInterface for TinyGo with UART communication and KISS protocol handling 2026-01-01 00:59:52 -06:00
ae3f93a3bf feat: add SerialInterface stub with error handling for non-TinyGo targets 2026-01-01 00:59:47 -06:00
cbf2eaea78 feat: implement RNodeInterface for Reticulum with command handling and device initialization 2026-01-01 00:59:39 -06:00
1133a918f1 feat: add LoRaInterface implementation for TinyGo with SPI communication and packet handling 2026-01-01 00:59:31 -06:00
f5bb6a2b6d feat: introduce LoRaInterface stub with error handling for non-TinyGo targets 2026-01-01 00:59:27 -06:00
718f550180 feat: add KISS protocol escape functions in new kiss.go interface 2026-01-01 00:59:24 -06:00
07dc008a31 refactor: update Identity methods to use common package for Msgpack serialization 2026-01-01 00:59:20 -06:00
cdc512c391 refactor: replace msgpack calls with common package methods for ratchet data serialization 2026-01-01 00:59:16 -06:00
1df3e41191 feat: implement MsgpackMarshal and MsgpackUnmarshal functions for MessagePack encoding and decoding 2026-01-01 00:59:12 -06:00
dea65ad94c feat: add new configuration fields for Frequency, Bandwidth, SF, CR, and TXPower in InterfaceConfig 2026-01-01 00:59:08 -06:00
4c9a395ff2 refactor: replace msgpack functions with common package equivalents for ratchet data serialization 2026-01-01 00:59:03 -06:00
09eec1c8fc feat: add SerialInterface and RNodeInterface support in Reticulum configuration 2026-01-01 00:58:59 -06:00
fbaafacd8a refactor: update tinygo-build-all task to build targets in parallel using xargs 2026-01-01 00:58:55 -06:00
40e7a168ec chore: update msgpack dependency from vmihailenco to shamaton and remove unused tagparser dependency 2026-01-01 00:58:31 -06:00
d6e0874f05 Add missing TCP timeout stubs for TinyGo
All checks were successful
TinyGo Build / tinygo-build-all (push) Successful in 2m39s
2025-12-31 22:54:29 -06:00
9fa05cce5c Merge main into tinygo and fix conflicts 2025-12-31 22:53:35 -06:00
8725c68b24 update: tinygo workflow
Some checks failed
TinyGo Build / tinygo-build-all (push) Failing after 1h7m54s
2025-11-10 10:17:44 -06:00
07512a3c88 update 2025-11-10 10:15:42 -06:00
6217a889e1 update Makefile with tinygo build options
Some checks failed
TinyGo Build / tinygo-build (tinygo-wasm, tinygo-wasm, reticulum-go.wasm, wasm) (push) Failing after 1m21s
TinyGo Build / tinygo-build (tinygo-build, tinygo-default, reticulum-go-tinygo, ) (push) Failing after 2m2s
2025-11-10 10:11:45 -06:00
92e4651c0c update build constraints for TCP interfaces to support TinyGo 2025-11-10 10:11:06 -06:00
ivan
a01bad919c Merge pull request 'Update TinyGo branch with debug logging' (#2) from main into tinygo
Reviewed-on: #2
2025-11-07 18:53:54 +00:00
ivan
cfc88e887b Merge pull request 'update tinygo' (#1) from main into tinygo
Reviewed-on: #1
2025-11-06 21:16:11 +00:00
d325ee6a2d Update UDPInterface Send method to return an error for unsupported UDP functionality in TinyGo 2025-10-30 19:08:42 -05:00
a45ad400f9 Fixes 2025-10-30 19:05:20 -05:00
f599cc4d43 Update AutoInterface and UDPInterface to use net.PacketConn for better compatibility with TinyGo. Update methods to handle errors related to unsupported features in TinyGo, including interface enumeration and multicast UDP. 2025-10-30 19:01:33 -05:00
1b3f4e59db Update GitHub Actions workflow to trigger on 'tinygo' branch for builds 2025-10-30 19:01:18 -05:00
26 changed files with 1196 additions and 177 deletions

View File

@@ -5,29 +5,15 @@ on:
branches: [ "tinygo" ]
pull_request:
branches: [ "tinygo" ]
workflow_dispatch:
jobs:
tinygo-build:
tinygo-build-all:
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
@@ -37,28 +23,64 @@ 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 with TinyGo
- name: Build for all TinyGo targets
id: build_step
run: |
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
task tinygo-build-all || true
echo "Build process completed (some targets may have failed)"
- name: Upload Artifact
- 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
uses: https://git.quad4.io/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
with:
name: ${{ matrix.name }}
path: bin/${{ matrix.output }}*
name: tinygo-builds
path: |
bin/reticulum-go-*
artifacts/unsupported-microcontrollers.txt
if-no-files-found: warn

View File

@@ -243,7 +243,37 @@ tasks:
desc: Build binary with TinyGo compiler
cmds:
- mkdir -p {{.BUILD_DIR}}
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo -size short {{.MAIN_PACKAGE}}
- 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-wasm:
desc: Build WebAssembly binary with TinyGo compiler

View File

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

14
go.sum
View File

@@ -1,14 +1,4 @@
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=
github.com/shamaton/msgpack/v2 v2.4.0 h1:O5Z08MRmbo0lA9o2xnQ4TXx6teJbPqEurqcCOQ8Oi/4=
github.com/shamaton/msgpack/v2 v2.4.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
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 := msgpack.Marshal(ratchetData)
data, err := common.MsgpackMarshal(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 := msgpack.Unmarshal(data, &ratchetData); err != nil {
if err := common.MsgpackUnmarshal(data, &ratchetData); err != nil {
debug.Log(debug.DEBUG_ERROR, "Corrupted ratchet data", "file", entry.Name(), "error", err)
_ = os.Remove(filePath)
continue

View File

@@ -40,6 +40,11 @@ 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

17
pkg/common/msgpack.go Normal file
View File

@@ -0,0 +1,17 @@
// 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,7 +17,6 @@ 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"
)
@@ -607,7 +606,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 := msgpack.Marshal(d.ratchets)
packedRatchets, err := common.MsgpackMarshal(d.ratchets)
if err != nil {
return fmt.Errorf("failed to pack ratchets: %w", err)
}
@@ -625,7 +624,7 @@ func (d *Destination) persistRatchets() error {
}
// Pack the entire structure
finalData, err := msgpack.Marshal(persistedData)
finalData, err := common.MsgpackMarshal(persistedData)
if err != nil {
return fmt.Errorf("failed to pack ratchet data: %w", err)
}
@@ -688,7 +687,7 @@ func (d *Destination) reloadRatchets() error {
// Unpack outer structure
var persistedData map[string][]byte
if err := msgpack.Unmarshal(fileData, &persistedData); err != nil {
if err := common.MsgpackUnmarshal(fileData, &persistedData); err != nil {
return fmt.Errorf("failed to unpack ratchet data: %w", err)
}
@@ -705,7 +704,7 @@ func (d *Destination) reloadRatchets() error {
}
// Unpack ratchet list
if err := msgpack.Unmarshal(packedRatchets, &d.ratchets); err != nil {
if err := common.MsgpackUnmarshal(packedRatchets, &d.ratchets); err != nil {
return fmt.Errorf("failed to unpack ratchet list: %w", err)
}

View File

@@ -20,7 +20,6 @@ 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"
)
@@ -672,7 +671,7 @@ func (i *Identity) saveRatchets(path string) error {
}
// Pack ratchets using msgpack
packedRatchets, err := msgpack.Marshal(ratchetList)
packedRatchets, err := common.MsgpackMarshal(ratchetList)
if err != nil {
return fmt.Errorf("failed to pack ratchets: %w", err)
}
@@ -687,7 +686,7 @@ func (i *Identity) saveRatchets(path string) error {
}
// Pack the entire structure
finalData, err := msgpack.Marshal(persistedData)
finalData, err := common.MsgpackMarshal(persistedData)
if err != nil {
return fmt.Errorf("failed to pack ratchet data: %w", err)
}
@@ -800,7 +799,7 @@ func (i *Identity) loadRatchets(path string) error {
// Unpack outer structure: {"signature": ..., "ratchets": ...}
var persistedData map[string][]byte
if err := msgpack.Unmarshal(fileData, &persistedData); err != nil {
if err := common.MsgpackUnmarshal(fileData, &persistedData); err != nil {
return fmt.Errorf("failed to unpack ratchet data: %w", err)
}
@@ -818,7 +817,7 @@ func (i *Identity) loadRatchets(path string) error {
// Unpack ratchet list
var ratchetList [][]byte
if err := msgpack.Unmarshal(packedRatchets, &ratchetList); err != nil {
if err := common.MsgpackUnmarshal(packedRatchets, &ratchetList); err != nil {
return fmt.Errorf("failed to unpack ratchet list: %w", err)
}

View File

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

View File

@@ -0,0 +1,96 @@
// 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
}

24
pkg/interfaces/kiss.go Normal file
View File

@@ -0,0 +1,24 @@
// 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

@@ -0,0 +1,29 @@
// 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

@@ -0,0 +1,292 @@
// 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
}

262
pkg/interfaces/rnode.go Normal file
View File

@@ -0,0 +1,262 @@
// 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

@@ -0,0 +1,29 @@
// 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

@@ -0,0 +1,221 @@
// 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,11 +18,6 @@ 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
@@ -329,20 +324,6 @@ 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
// +build !linux
//go:build !linux || tinygo
// +build !linux tinygo
package interfaces
@@ -14,3 +14,11 @@ 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
// +build linux
//go:build linux && !tinygo
// +build linux,!tinygo
package interfaces

View File

@@ -1,5 +1,8 @@
// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
//go:build !tinygo
// +build !tinygo
package interfaces
import (
@@ -48,30 +51,6 @@ 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()
@@ -111,34 +90,12 @@ 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")
}
if ui.targetAddr == nil {
return fmt.Errorf("no target address configured")
}
_, err := ui.conn.WriteToUDP(data, ui.targetAddr)
_, err := ui.conn.Write(data)
if err != nil {
return fmt.Errorf("UDP write failed: %v", err)
}
@@ -154,38 +111,6 @@ 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 {
@@ -285,9 +210,3 @@ 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

@@ -0,0 +1,68 @@
// 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,7 +28,6 @@ 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 (
@@ -308,7 +307,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 := msgpack.Marshal(requestData)
packedRequest, err := common.MsgpackMarshal(requestData)
if err != nil {
return nil, fmt.Errorf("failed to pack request: %w", err)
}
@@ -1029,7 +1028,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
}
var requestData []interface{}
if err := msgpack.Unmarshal(plaintext, &requestData); err != nil {
if err := common.MsgpackUnmarshal(plaintext, &requestData); err != nil {
return fmt.Errorf("failed to unpack request: %w", err)
}
@@ -1060,7 +1059,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
case string:
requestPayload = []byte(payload)
default:
packed, err := msgpack.Marshal(payload)
packed, err := common.MsgpackMarshal(payload)
if err != nil {
return fmt.Errorf("failed to pack request_payload: %w", err)
}
@@ -1089,7 +1088,7 @@ func (l *Link) handleRequest(plaintext []byte, pkt *packet.Packet) error {
func (l *Link) handleResponse(plaintext []byte) error {
var responseData []interface{}
if err := msgpack.Unmarshal(plaintext, &responseData); err != nil {
if err := common.MsgpackUnmarshal(plaintext, &responseData); err != nil {
return fmt.Errorf("failed to unpack response: %w", err)
}
@@ -1124,7 +1123,7 @@ func (l *Link) handleResponse(plaintext []byte) error {
func (l *Link) sendResponse(requestID []byte, response interface{}) error {
responseData := []interface{}{requestID, response}
packedResponse, err := msgpack.Marshal(responseData)
packedResponse, err := common.MsgpackMarshal(responseData)
if err != nil {
return fmt.Errorf("failed to pack response: %w", err)
}

View File

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