Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
6ae409c1db
|
|||
|
bfa0669143
|
|||
|
2867f68a90
|
|||
|
fbb48f2295
|
|||
|
132872c2d6
|
|||
|
1d3590985e
|
|||
|
ae3f93a3bf
|
|||
|
cbf2eaea78
|
|||
|
1133a918f1
|
|||
|
f5bb6a2b6d
|
|||
|
718f550180
|
|||
|
07dc008a31
|
|||
|
cdc512c391
|
|||
|
1df3e41191
|
|||
|
dea65ad94c
|
|||
|
4c9a395ff2
|
|||
|
09eec1c8fc
|
|||
|
fbaafacd8a
|
|||
|
40e7a168ec
|
|||
|
d6e0874f05
|
|||
|
9fa05cce5c
|
|||
|
8725c68b24
|
|||
|
07512a3c88
|
|||
|
6217a889e1
|
|||
|
92e4651c0c
|
|||
|
|
a01bad919c | ||
|
|
cfc88e887b | ||
| d325ee6a2d | |||
| a45ad400f9 | |||
| f599cc4d43 | |||
| 1b3f4e59db |
@@ -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
|
||||
|
||||
32
Taskfile.yml
32
Taskfile.yml
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
examples/wasm/public/static/reticulum-go.wasm
Executable file
BIN
examples/wasm/public/static/reticulum-go.wasm
Executable file
Binary file not shown.
4
go.mod
4
go.mod
@@ -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
14
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
17
pkg/common/msgpack.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-License-Identifier: 0BSD
|
||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||
//go:build !tinygo
|
||||
// +build !tinygo
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
|
||||
96
pkg/interfaces/auto_tinygo.go
Normal file
96
pkg/interfaces/auto_tinygo.go
Normal 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
24
pkg/interfaces/kiss.go
Normal 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
|
||||
}
|
||||
29
pkg/interfaces/lora_stub.go
Normal file
29
pkg/interfaces/lora_stub.go
Normal 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")
|
||||
}
|
||||
292
pkg/interfaces/lora_tinygo.go
Normal file
292
pkg/interfaces/lora_tinygo.go
Normal 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
262
pkg/interfaces/rnode.go
Normal 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)
|
||||
}
|
||||
29
pkg/interfaces/serial_stub.go
Normal file
29
pkg/interfaces/serial_stub.go
Normal 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")
|
||||
}
|
||||
221
pkg/interfaces/serial_tinygo.go
Normal file
221
pkg/interfaces/serial_tinygo.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
68
pkg/interfaces/udp_tinygo.go
Normal file
68
pkg/interfaces/udp_tinygo.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user