diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go index c69d785..0ad8d31 100644 --- a/pkg/transport/transport.go +++ b/pkg/transport/transport.go @@ -577,8 +577,11 @@ func (t *Transport) RequestPath(destinationHash []byte, onInterface string, tag pathRequestData = append(destinationHash, tag...) } - destHashFull := sha256.Sum256([]byte("rnstransport.path.request")) - pathRequestDestHash := destHashFull[:common.SIZE_16] + pathRequestName := "rnstransport.path.request" + nameHashFull := sha256.Sum256([]byte(pathRequestName)) + nameHash10 := nameHashFull[:10] + finalHashFull := sha256.Sum256(nameHash10) + pathRequestDestHash := finalHashFull[:16] pkt := packet.NewPacket( packet.DestinationPlain, @@ -586,11 +589,12 @@ func (t *Transport) RequestPath(destinationHash []byte, onInterface string, tag 0x00, 0x00, packet.PropagationBroadcast, - 0x01, - pathRequestDestHash, + 0x00, // Header Type 1 + nil, false, 0x00, ) + pkt.DestinationHash = pathRequestDestHash if err := pkt.Pack(); err != nil { return fmt.Errorf("failed to pack path request: %w", err) @@ -1110,16 +1114,15 @@ func (t *Transport) handleAnnouncePacket(data []byte, iface common.NetworkInterf // Register the path from this announce // The destination is reachable via the interface that received this announce if iface != nil { - // Use unlocked version since we may be called in a locked context t.mutex.Lock() - t.updatePathUnlocked(destinationHash, nil, iface.GetName(), hopCount) + t.updatePathUnlocked(destinationHash, nil, iface.GetName(), hopCount+1) t.mutex.Unlock() - debug.Log(debug.DEBUG_INFO, "Registered path", "hash", fmt.Sprintf("%x", destinationHash), "interface", iface.GetName(), "hops", hopCount) + debug.Log(debug.DEBUG_INFO, "Registered path", "hash", fmt.Sprintf("%x", destinationHash), "interface", iface.GetName(), "hops", hopCount+1) } // Notify handlers first, regardless of forwarding limits debug.Log(debug.DEBUG_INFO, "Notifying announce handlers", "destHash", fmt.Sprintf("%x", destinationHash), "appDataLen", len(appData)) - t.notifyAnnounceHandlers(destinationHash, id, appData, hopCount) + t.notifyAnnounceHandlers(destinationHash, id, appData, hopCount+1) debug.Log(debug.DEBUG_INFO, "Announce handlers notified") // Don't forward if max hops reached @@ -1376,7 +1379,7 @@ func (t *Transport) InitializePathRequestHandler() error { return errors.New("transport identity not initialized") } - pathRequestDest, err := destination.New(t.transportIdentity, destination.IN, destination.PLAIN, "rnstransport", t, "path", "request") + pathRequestDest, err := destination.New(nil, destination.IN, destination.PLAIN, "rnstransport", t, "path", "request") if err != nil { return fmt.Errorf("failed to create path request destination: %w", err) } @@ -1692,6 +1695,14 @@ func (l *Link) HandleResource(resource interface{}) bool { } } +// SetIdentity sets the identity for the Transport. +func (t *Transport) SetIdentity(id *identity.Identity) { + t.mutex.Lock() + defer t.mutex.Unlock() + t.transportIdentity = id +} + +// Start initializes the Transport. func (t *Transport) Start() error { t.mutex.Lock() defer t.mutex.Unlock() diff --git a/pkg/transport/transport_test.go b/pkg/transport/transport_test.go index aaf9382..232246b 100644 --- a/pkg/transport/transport_test.go +++ b/pkg/transport/transport_test.go @@ -3,8 +3,12 @@ package transport import ( "bytes" "testing" + "time" "git.quad4.io/Networks/Reticulum-Go/pkg/common" + "git.quad4.io/Networks/Reticulum-Go/pkg/destination" + "git.quad4.io/Networks/Reticulum-Go/pkg/identity" + "git.quad4.io/Networks/Reticulum-Go/pkg/packet" ) type mockInterface struct { @@ -118,3 +122,68 @@ func TestTransportStatus(t *testing.T) { t.Error("Path should be responsive again") } } + +func TestAnnounceHopCount(t *testing.T) { + config := common.DefaultConfig() + tr := NewTransport(config) + defer tr.Close() + + iface := &mockInterface{} + iface.Name = "wasm0" + iface.Enabled = true + _ = tr.RegisterInterface("wasm0", iface) + + // Create an identity for the announce + id, _ := identity.New() + + // Create a destination to get a valid hash for this identity + // NewAnnouncePacket uses "reticulum-go.node" by default + dest, _ := destination.New(id, destination.IN, destination.SINGLE, "reticulum-go.node", tr) + destHash := dest.GetHash() + + // Create a raw announce packet manually to control hop count + // Header(2) + DestHash(16) + Context(1) + Payload... + // Header: 0x21 (Announce, Header Type 1, Broadcast, Destination Type Single) + // Hop count: 0 + raw := make([]byte, 2+16+1+148) // header + dest + context + min_announce_payload + raw[0] = 0x21 + raw[1] = 0 // Initial hop count + copy(raw[2:18], destHash) + raw[18] = 0 // context + + // Announce payload: pubKey(64) + nameHash(10) + randomHash(10) + signature(64) + payload := raw[19:] + copy(payload[0:64], id.GetPublicKey()) + // Name hash, random hash, signature - filling with dummy data but valid length + // Normally we would sign it properly, but handleAnnouncePacket validates it. + // Actually, handleAnnouncePacket WILL fail if signature is invalid. + // Use NewAnnouncePacket to get a valid signed packet + transportID := make([]byte, 16) + annPkt, err := packet.NewAnnouncePacket(destHash, id, []byte("test"), transportID) + if err != nil { + t.Fatalf("NewAnnouncePacket failed: %v", err) + } + annRaw, err := annPkt.Serialize() + if err != nil { + t.Fatalf("Serialize failed: %v", err) + } + + // Override hop count to 0 to simulate neighbor + annRaw[1] = 0 + + // Handle the packet + tr.HandlePacket(annRaw, iface) + + // Wait a bit for the async processing + time.Sleep(100 * time.Millisecond) + + // Check stored hops + if !tr.HasPath(destHash) { + t.Fatal("Path not registered from announce") + } + + hops := tr.HopsTo(destHash) + if hops != 1 { + t.Errorf("Expected 1 hop for neighbor (received 0), got %d", hops) + } +}