mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
219 lines
5.5 KiB
Go
219 lines
5.5 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package transport
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
"quad4/reticulum-go/pkg/common"
|
|
"quad4/reticulum-go/pkg/identity"
|
|
)
|
|
|
|
func mockIface(name string, enabled bool) *mockInterface {
|
|
m := &mockInterface{}
|
|
m.Name = name
|
|
m.Enabled = enabled
|
|
m.Online = enabled
|
|
return m
|
|
}
|
|
|
|
func TestUnregisterInterfaceScrubsPaths(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
|
|
a := mockIface("a", true)
|
|
b := mockIface("b", true)
|
|
if err := tr.RegisterInterface("a", a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tr.RegisterInterface("b", b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dest := bytes.Repeat([]byte{0xAB}, 16)
|
|
tr.mutex.Lock()
|
|
tr.paths[pathMapKey(dest)] = &common.Path{
|
|
NextHop: bytes.Repeat([]byte{0x01}, 16),
|
|
Interface: b,
|
|
HopCount: 1,
|
|
LastUpdated: time.Now(),
|
|
}
|
|
tr.mutex.Unlock()
|
|
|
|
tr.UnregisterInterface("b")
|
|
|
|
if tr.HasPath(dest) {
|
|
t.Fatal("path should be removed when egress interface unregisters")
|
|
}
|
|
if tr.NextHopInterface(dest) != "" {
|
|
t.Fatalf("NextHopInterface want empty, got %q", tr.NextHopInterface(dest))
|
|
}
|
|
}
|
|
|
|
func TestUnregisterInterfaceScrubsLinkRelay(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
|
|
a := mockIface("a", true)
|
|
b := mockIface("b", true)
|
|
if err := tr.RegisterInterface("a", a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tr.RegisterInterface("b", b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
linkID := bytes.Repeat([]byte{0xCD}, identity.TruncatedHashLength/8)
|
|
tr.mutex.Lock()
|
|
tr.paths[pathMapKey(bytes.Repeat([]byte{0x11}, 16))] = &common.Path{
|
|
NextHop: bytes.Repeat([]byte{0x22}, 16),
|
|
Interface: b,
|
|
HopCount: 2,
|
|
LastUpdated: time.Now(),
|
|
}
|
|
tr.mutex.Unlock()
|
|
|
|
tr.linkTable.put(linkID, &LinkRelayEntry{
|
|
NextHop: bytes.Repeat([]byte{0x22}, 16),
|
|
NextHopIface: b,
|
|
ReceivedIface: a,
|
|
RemainingHops: 2,
|
|
TakenHops: 0,
|
|
DestinationHash: bytes.Repeat([]byte{0x33}, 16),
|
|
Validated: false,
|
|
ProofTimeout: time.Now().Add(time.Hour),
|
|
Timestamp: time.Now(),
|
|
OriginalLinkID: append([]byte(nil), linkID...),
|
|
})
|
|
|
|
tr.UnregisterInterface("b")
|
|
if _, ok := tr.linkTable.get(linkID); ok {
|
|
t.Fatal("link relay entry referencing removed interface should be dropped")
|
|
}
|
|
}
|
|
|
|
func TestUnregisterInterfaceScrubsDiscoveryAndAnnounceTables(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
|
|
a := mockIface("wan", true)
|
|
if err := tr.RegisterInterface("wan", a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dest := bytes.Repeat([]byte{0x55}, 16)
|
|
announceKey := destKey([]byte("xxxxxxxxxxxxxxxx"))
|
|
heldKey := destKey([]byte("yyyyyyyyyyyyyyyy"))
|
|
tr.mutex.Lock()
|
|
tr.discoveryPathRequests[destKey(dest)] = &DiscoveryPathRequest{
|
|
DestinationHash: dest,
|
|
Timeout: time.Now().Add(time.Hour),
|
|
RequestingIface: a,
|
|
}
|
|
tr.announceTable[announceKey] = &PathAnnounceEntry{
|
|
CreatedAt: time.Now(),
|
|
ReceivedFrom: a,
|
|
}
|
|
tr.heldAnnounces[heldKey] = &PathAnnounceEntry{
|
|
CreatedAt: time.Now(),
|
|
AttachedInterface: a,
|
|
}
|
|
tr.mutex.Unlock()
|
|
|
|
tr.UnregisterInterface("wan")
|
|
|
|
tr.mutex.RLock()
|
|
_, dOK := tr.discoveryPathRequests[destKey(dest)]
|
|
_, aOK := tr.announceTable[announceKey]
|
|
_, hOK := tr.heldAnnounces[heldKey]
|
|
tr.mutex.RUnlock()
|
|
|
|
if dOK || aOK || hOK {
|
|
t.Fatalf("expected tables cleared: discovery=%v announce=%v held=%v", dOK, aOK, hOK)
|
|
}
|
|
}
|
|
|
|
func TestReplaceInterfaceSwapsRegistration(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
|
|
first := mockIface("u1", true)
|
|
second := mockIface("u1", true)
|
|
if err := tr.RegisterInterface("u1", first); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dest := bytes.Repeat([]byte{0x77}, 16)
|
|
tr.UpdatePath(dest, bytes.Repeat([]byte{0x88}, 16), "u1", 1)
|
|
|
|
if err := tr.ReplaceInterface("u1", second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tr.HasPath(dest) {
|
|
t.Fatal("path tied to old iface pointer should be cleared on replace")
|
|
}
|
|
got, err := tr.GetInterface("u1")
|
|
if err != nil || got != second {
|
|
t.Fatalf("GetInterface: err=%v got=%p want second %p", err, got, second)
|
|
}
|
|
}
|
|
|
|
func TestRequestPathSkipsDisabledInterface(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
|
|
up := mockIface("up", true)
|
|
down := mockIface("down", false)
|
|
if err := tr.RegisterInterface("up", up); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tr.RegisterInterface("down", down); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dest := bytes.Repeat([]byte{0x99}, 16)
|
|
if err := tr.RequestPath(dest, "", nil, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(up.sent) == 0 {
|
|
t.Fatal("expected path request on enabled interface")
|
|
}
|
|
if len(down.sent) != 0 {
|
|
t.Fatal("disabled interface should not send path request")
|
|
}
|
|
}
|
|
|
|
func TestUnregisterInterfaceIdempotent(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
tr.UnregisterInterface("nonexistent")
|
|
}
|
|
|
|
func TestSendAnnounceSkipsDisabledInterface(t *testing.T) {
|
|
tr := NewTransport(&common.ReticulumConfig{})
|
|
defer tr.Close()
|
|
SetTransportInstance(tr)
|
|
defer SetTransportInstance(nil)
|
|
|
|
up := mockIface("up", true)
|
|
down := mockIface("down", false)
|
|
if err := tr.RegisterInterface("up", up); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tr.RegisterInterface("down", down); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pkt := []byte{0x01, 0x02, 0x03}
|
|
if err := SendAnnounce(pkt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(up.sent) == 0 {
|
|
t.Fatal("enabled iface should receive announce send")
|
|
}
|
|
if len(down.sent) != 0 {
|
|
t.Fatal("disabled iface should not send")
|
|
}
|
|
}
|