Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
97353d430b
|
@@ -5,15 +5,29 @@ on:
|
|||||||
branches: [ "tinygo" ]
|
branches: [ "tinygo" ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "tinygo" ]
|
branches: [ "tinygo" ]
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tinygo-build-all:
|
tinygo-build:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
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
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
build_complete: ${{ steps.build_step.outcome == 'success' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: https://git.quad4.io/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: https://git.quad4.io/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
@@ -23,64 +37,28 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
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
|
- name: Install TinyGo
|
||||||
run: |
|
run: |
|
||||||
wget https://github.com/tinygo-org/tinygo/releases/download/v0.37.0/tinygo_0.37.0_amd64.deb
|
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
|
sudo dpkg -i tinygo_0.37.0_amd64.deb
|
||||||
|
|
||||||
- name: Build for all TinyGo targets
|
- name: Build with TinyGo
|
||||||
id: build_step
|
id: build_step
|
||||||
run: |
|
run: |
|
||||||
task tinygo-build-all || true
|
make ${{ matrix.make_target }}
|
||||||
echo "Build process completed (some targets may have failed)"
|
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
|
- name: Upload Artifact
|
||||||
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
|
uses: https://git.quad4.io/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
|
||||||
with:
|
with:
|
||||||
name: tinygo-builds
|
name: ${{ matrix.name }}
|
||||||
path: |
|
path: bin/${{ matrix.output }}*
|
||||||
bin/reticulum-go-*
|
|
||||||
artifacts/unsupported-microcontrollers.txt
|
|
||||||
if-no-files-found: warn
|
|
||||||
|
|||||||
14
CONTRIBUTORS
Normal file
14
CONTRIBUTORS
Normal 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
|
||||||
32
Taskfile.yml
32
Taskfile.yml
@@ -243,37 +243,7 @@ tasks:
|
|||||||
desc: Build binary with TinyGo compiler
|
desc: Build binary with TinyGo compiler
|
||||||
cmds:
|
cmds:
|
||||||
- mkdir -p {{.BUILD_DIR}}
|
- mkdir -p {{.BUILD_DIR}}
|
||||||
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo -size short -opt=z -gc=leaking -panic=trap {{.MAIN_PACKAGE}}
|
- tinygo build -o {{.BUILD_DIR}}/{{.BINARY_NAME}}-tinygo -size short {{.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:
|
tinygo-wasm:
|
||||||
desc: Build WebAssembly binary with TinyGo compiler
|
desc: Build WebAssembly binary with TinyGo compiler
|
||||||
|
|||||||
@@ -207,34 +207,6 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) {
|
|||||||
} else {
|
} else {
|
||||||
debug.Log(debug.DEBUG_INFO, "WebSocket interface created successfully", common.STR_NAME, name)
|
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:
|
default:
|
||||||
debug.Log(debug.DEBUG_CRITICAL, "Unknown interface type", common.STR_TYPE, ifaceConfig.Type)
|
debug.Log(debug.DEBUG_CRITICAL, "Unknown interface type", common.STR_TYPE, ifaceConfig.Type)
|
||||||
continue
|
continue
|
||||||
|
|||||||
Binary file not shown.
4
go.mod
4
go.mod
@@ -3,6 +3,8 @@ module git.quad4.io/Networks/Reticulum-Go
|
|||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/shamaton/msgpack/v2 v2.4.0
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.46.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -1,4 +1,14 @@
|
|||||||
github.com/shamaton/msgpack/v2 v2.4.0 h1:O5Z08MRmbo0lA9o2xnQ4TXx6teJbPqEurqcCOQ8Oi/4=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/shamaton/msgpack/v2 v2.4.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
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 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
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=
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@@ -88,7 +88,7 @@ func (m *Manager) SaveRatchet(identityHash []byte, ratchetKey []byte) error {
|
|||||||
Received: time.Now().Unix(),
|
Received: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := common.MsgpackMarshal(ratchetData)
|
data, err := msgpack.Marshal(ratchetData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal ratchet data: %w", err)
|
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
|
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)
|
debug.Log(debug.DEBUG_ERROR, "Corrupted ratchet data", "file", entry.Name(), "error", err)
|
||||||
_ = os.Remove(filePath)
|
_ = os.Remove(filePath)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ type InterfaceConfig struct {
|
|||||||
DiscoveryScope string
|
DiscoveryScope string
|
||||||
DiscoveryPort int
|
DiscoveryPort int
|
||||||
DataPort int
|
DataPort int
|
||||||
Frequency uint32
|
|
||||||
Bandwidth uint32
|
|
||||||
SF uint8
|
|
||||||
CR uint8
|
|
||||||
TXPower uint8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReticulumConfig represents the main configuration structure
|
// ReticulumConfig represents the main configuration structure
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/identity"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/identity"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/packet"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/packet"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
"golang.org/x/crypto/curve25519"
|
"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)
|
debug.Log(debug.DEBUG_PACKETS, "Persisting ratchets", "count", len(d.ratchets), "path", d.ratchetPath)
|
||||||
|
|
||||||
// Pack ratchets using msgpack
|
// Pack ratchets using msgpack
|
||||||
packedRatchets, err := common.MsgpackMarshal(d.ratchets)
|
packedRatchets, err := msgpack.Marshal(d.ratchets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack ratchets: %w", err)
|
return fmt.Errorf("failed to pack ratchets: %w", err)
|
||||||
}
|
}
|
||||||
@@ -624,7 +625,7 @@ func (d *Destination) persistRatchets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pack the entire structure
|
// Pack the entire structure
|
||||||
finalData, err := common.MsgpackMarshal(persistedData)
|
finalData, err := msgpack.Marshal(persistedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack ratchet data: %w", err)
|
return fmt.Errorf("failed to pack ratchet data: %w", err)
|
||||||
}
|
}
|
||||||
@@ -687,7 +688,7 @@ func (d *Destination) reloadRatchets() error {
|
|||||||
|
|
||||||
// Unpack outer structure
|
// Unpack outer structure
|
||||||
var persistedData map[string][]byte
|
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)
|
return fmt.Errorf("failed to unpack ratchet data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +705,7 @@ func (d *Destination) reloadRatchets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unpack ratchet list
|
// 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)
|
return fmt.Errorf("failed to unpack ratchet list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/cryptography"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/cryptography"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
@@ -671,7 +672,7 @@ func (i *Identity) saveRatchets(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pack ratchets using msgpack
|
// Pack ratchets using msgpack
|
||||||
packedRatchets, err := common.MsgpackMarshal(ratchetList)
|
packedRatchets, err := msgpack.Marshal(ratchetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack ratchets: %w", err)
|
return fmt.Errorf("failed to pack ratchets: %w", err)
|
||||||
}
|
}
|
||||||
@@ -686,7 +687,7 @@ func (i *Identity) saveRatchets(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pack the entire structure
|
// Pack the entire structure
|
||||||
finalData, err := common.MsgpackMarshal(persistedData)
|
finalData, err := msgpack.Marshal(persistedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack ratchet data: %w", err)
|
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": ...}
|
// Unpack outer structure: {"signature": ..., "ratchets": ...}
|
||||||
var persistedData map[string][]byte
|
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)
|
return fmt.Errorf("failed to unpack ratchet data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,7 +818,7 @@ func (i *Identity) loadRatchets(path string) error {
|
|||||||
|
|
||||||
// Unpack ratchet list
|
// Unpack ratchet list
|
||||||
var ratchetList [][]byte
|
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)
|
return fmt.Errorf("failed to unpack ratchet list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||||
//go:build !tinygo
|
|
||||||
// +build !tinygo
|
|
||||||
|
|
||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,11 @@ const (
|
|||||||
HDLC_ESC = 0x7D
|
HDLC_ESC = 0x7D
|
||||||
HDLC_ESC_MASK = 0x20
|
HDLC_ESC_MASK = 0x20
|
||||||
|
|
||||||
|
KISS_FEND = 0xC0
|
||||||
|
KISS_FESC = 0xDB
|
||||||
|
KISS_TFEND = 0xDC
|
||||||
|
KISS_TFESC = 0xDD
|
||||||
|
|
||||||
DEFAULT_MTU = 1064
|
DEFAULT_MTU = 1064
|
||||||
BITRATE_GUESS_VAL = 10 * 1000 * 1000
|
BITRATE_GUESS_VAL = 10 * 1000 * 1000
|
||||||
RECONNECT_WAIT = 5
|
RECONNECT_WAIT = 5
|
||||||
@@ -324,6 +329,20 @@ func escapeHDLC(data []byte) []byte {
|
|||||||
return escaped
|
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) {
|
func (tc *TCPClientInterface) SetPacketCallback(cb common.PacketCallback) {
|
||||||
tc.packetCallback = cb
|
tc.packetCallback = cb
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||||
//go:build !linux || tinygo
|
//go:build !linux
|
||||||
// +build !linux tinygo
|
// +build !linux
|
||||||
|
|
||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
@@ -14,11 +14,3 @@ import (
|
|||||||
func platformGetRTT(fd uintptr) time.Duration {
|
func platformGetRTT(fd uintptr) time.Duration {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TCPClientInterface) setTimeoutsLinux() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TCPClientInterface) setTimeoutsOSX() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||||
//go:build linux && !tinygo
|
//go:build linux
|
||||||
// +build linux,!tinygo
|
// +build linux
|
||||||
|
|
||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||||
//go:build !tinygo
|
|
||||||
// +build !tinygo
|
|
||||||
|
|
||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -51,6 +48,30 @@ func NewUDPInterface(name string, addr string, target string, enabled bool) (*UD
|
|||||||
return ui, nil
|
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() {
|
func (ui *UDPInterface) Detach() {
|
||||||
ui.Mutex.Lock()
|
ui.Mutex.Lock()
|
||||||
defer ui.Mutex.Unlock()
|
defer ui.Mutex.Unlock()
|
||||||
@@ -90,12 +111,34 @@ func (ui *UDPInterface) Send(data []byte, addr string) error {
|
|||||||
return err
|
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 {
|
func (ui *UDPInterface) ProcessOutgoing(data []byte) error {
|
||||||
if !ui.IsOnline() {
|
if !ui.IsOnline() {
|
||||||
return fmt.Errorf("interface offline")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("UDP write failed: %v", err)
|
return fmt.Errorf("UDP write failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -111,6 +154,38 @@ func (ui *UDPInterface) GetConn() net.Conn {
|
|||||||
return ui.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 {
|
func (ui *UDPInterface) Start() error {
|
||||||
ui.Mutex.Lock()
|
ui.Mutex.Lock()
|
||||||
if ui.conn != nil {
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/resolver"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/resolver"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/resource"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/resource"
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/transport"
|
"git.quad4.io/Networks/Reticulum-Go/pkg/transport"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -307,7 +308,7 @@ func (l *Link) Request(path string, data []byte, timeout time.Duration) (*Reques
|
|||||||
|
|
||||||
pathHash := identity.TruncatedHash([]byte(path))
|
pathHash := identity.TruncatedHash([]byte(path))
|
||||||
requestData := []interface{}{time.Now().Unix(), pathHash, data}
|
requestData := []interface{}{time.Now().Unix(), pathHash, data}
|
||||||
packedRequest, err := common.MsgpackMarshal(requestData)
|
packedRequest, err := msgpack.Marshal(requestData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to pack request: %w", err)
|
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{}
|
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)
|
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:
|
case string:
|
||||||
requestPayload = []byte(payload)
|
requestPayload = []byte(payload)
|
||||||
default:
|
default:
|
||||||
packed, err := common.MsgpackMarshal(payload)
|
packed, err := msgpack.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack request_payload: %w", err)
|
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 {
|
func (l *Link) handleResponse(plaintext []byte) error {
|
||||||
var responseData []interface{}
|
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)
|
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 {
|
func (l *Link) sendResponse(requestID []byte, response interface{}) error {
|
||||||
responseData := []interface{}{requestID, response}
|
responseData := []interface{}{requestID, response}
|
||||||
packedResponse, err := common.MsgpackMarshal(responseData)
|
packedResponse, err := msgpack.Marshal(responseData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to pack response: %w", err)
|
return fmt.Errorf("failed to pack response: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -117,12 +117,12 @@ func (ra *ResourceAdvertisement) Pack(segment int) ([]byte, error) {
|
|||||||
"m": hashmap,
|
"m": hashmap,
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.MsgpackMarshal(dict)
|
return msgpack.Marshal(dict)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnpackResourceAdvertisement(data []byte) (*ResourceAdvertisement, error) {
|
func UnpackResourceAdvertisement(data []byte) (*ResourceAdvertisement, error) {
|
||||||
var dict map[string]interface{}
|
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)
|
return nil, fmt.Errorf("failed to unpack advertisement: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user