diff --git a/cmd/client/client.go b/cmd/client/client.go index 1e34d5d..8e4e7cb 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -9,12 +9,15 @@ import ( "os/signal" "strings" "syscall" + "time" + "encoding/binary" "github.com/Sudo-Ivan/reticulum-go/internal/config" "github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/transport" "github.com/Sudo-Ivan/reticulum-go/pkg/interfaces" + "github.com/Sudo-Ivan/reticulum-go/pkg/announce" ) var ( @@ -27,6 +30,7 @@ type Client struct { config *common.ReticulumConfig transport *transport.Transport interfaces []common.NetworkInterface + identity *identity.Identity } func NewClient(cfg *common.ReticulumConfig) (*Client, error) { @@ -43,10 +47,16 @@ func NewClient(cfg *common.ReticulumConfig) (*Client, error) { return nil, fmt.Errorf("failed to initialize transport: %v", err) } + id, err := identity.New() + if err != nil { + return nil, fmt.Errorf("failed to create identity: %v", err) + } + return &Client{ config: cfg, transport: t, interfaces: make([]common.NetworkInterface, 0), + identity: id, }, nil } @@ -69,7 +79,7 @@ func (c *Client) Start() error { } callback := common.PacketCallback(func(data []byte, iface interface{}) { - c.transport.HandlePacket(data, iface) + c.handlePacket(data, iface) }) client.SetPacketCallback(callback) iface = client @@ -87,7 +97,7 @@ func (c *Client) Start() error { } callback := common.PacketCallback(func(data []byte, iface interface{}) { - c.transport.HandlePacket(data, iface) + c.handlePacket(data, iface) }) udp.SetPacketCallback(callback) iface = udp @@ -106,9 +116,110 @@ func (c *Client) Start() error { } } + // Start periodic announce after interfaces are set up + go func() { + // Initial delay to allow interfaces to connect + time.Sleep(5 * time.Second) + + // Send first announce + c.sendAnnounce() + + // Set up periodic announces + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + c.sendAnnounce() + } + } + }() + + log.Printf("Client started with %d interfaces", len(c.interfaces)) return nil } +func (c *Client) handlePacket(data []byte, iface interface{}) { + if len(data) < 1 { + return + } + + packetType := data[0] + switch packetType { + case 0x04: // Announce packet + c.handleAnnounce(data[1:]) + default: + c.transport.HandlePacket(data, iface) + } +} + +func (c *Client) handleAnnounce(data []byte) { + if len(data) < 42 { // 32 bytes hash + 8 bytes timestamp + 1 byte hops + 1 byte flags + log.Printf("Received malformed announce packet (too short)") + return + } + + destHash := data[:32] + timestamp := binary.BigEndian.Uint64(data[32:40]) + hops := data[40] + flags := data[41] + + log.Printf("Received announce from %x", destHash) + log.Printf(" Timestamp: %d", timestamp) + log.Printf(" Hops: %d", hops) + log.Printf(" Flags: %x", flags) + + if len(data) > 42 { + // Extract app data if present + dataLen := binary.BigEndian.Uint16(data[42:44]) + if len(data) >= 44+int(dataLen) { + appData := data[44:44+dataLen] + log.Printf(" App Data: %s", string(appData)) + } + } +} + +func (c *Client) sendAnnounce() { + // Create announce packet following RNS protocol + announceData := make([]byte, 0, 128) + announceData = append(announceData, 0x04) // Announce packet type + announceData = append(announceData, c.identity.Hash()...) // Identity hash (32 bytes) + + // Add timestamp (8 bytes, big-endian) + timestamp := time.Now().Unix() + timeBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timeBytes, uint64(timestamp)) + announceData = append(announceData, timeBytes...) + + // Add hops (1 byte) + announceData = append(announceData, 0x00) // Initial hop count + + // Add flags (1 byte) + announceData = append(announceData, byte(announce.ANNOUNCE_IDENTITY)) // Using identity announce type + + // Add app data with length prefix + appData := []byte("RNS.Go.Client") + lenBytes := make([]byte, 2) + binary.BigEndian.PutUint16(lenBytes, uint16(len(appData))) + announceData = append(announceData, lenBytes...) + announceData = append(announceData, appData...) + + // Sign the announce packet + signature := c.identity.Sign(announceData) + announceData = append(announceData, signature...) + + log.Printf("Sending announce packet, length: %d bytes", len(announceData)) + + for _, iface := range c.interfaces { + if err := iface.Send(announceData, ""); err != nil { + log.Printf("Failed to send announce on interface %s: %v", iface.GetName(), err) + } else { + log.Printf("Sent announce on interface %s", iface.GetName()) + } + } +} + func (c *Client) Stop() { for _, iface := range c.interfaces { iface.Detach() diff --git a/pkg/announce/announce.go b/pkg/announce/announce.go index 485378f..94cd23b 100644 --- a/pkg/announce/announce.go +++ b/pkg/announce/announce.go @@ -2,13 +2,12 @@ package announce import ( "crypto/sha256" - "encoding/binary" "errors" "sync" "time" "github.com/Sudo-Ivan/reticulum-go/pkg/identity" - "github.com/Sudo-Ivan/reticulum-go/pkg/transport" + "github.com/Sudo-Ivan/reticulum-go/pkg/common" ) const ( @@ -64,7 +63,7 @@ func New(dest *identity.Identity, appData []byte, pathResponse bool) (*Announce, return a, nil } -func (a *Announce) Propagate(interfaces []transport.Interface) error { +func (a *Announce) Propagate(interfaces []common.NetworkInterface) error { a.mutex.Lock() defer a.mutex.Unlock() @@ -89,7 +88,7 @@ func (a *Announce) Propagate(interfaces []transport.Interface) error { // Propagate to all interfaces for _, iface := range interfaces { - if err := iface.SendAnnounce(packet, a.pathResponse); err != nil { + if err := iface.Send(packet, ""); err != nil { return err } } @@ -125,21 +124,34 @@ func (a *Announce) HandleAnnounce(data []byte) error { // Extract fields destHash := data[:16] - pubKey := data[16:48] - hops := data[48] + publicKey := data[16:48] + hopCount := data[48] + + // Validate hop count + if hopCount > MAX_HOPS { + return errors.New("announce exceeded maximum hop count") + } + + // Extract app data and signature appData := data[49 : len(data)-64] signature := data[len(data)-64:] + // Create announced identity from public key + announcedIdentity := identity.FromPublicKey(publicKey) + if announcedIdentity == nil { + return errors.New("invalid identity public key") + } + // Verify signature signData := append(destHash, appData...) - if !a.identity.Verify(signData, signature) { + if !announcedIdentity.Verify(signData, signature) { return errors.New("invalid announce signature") } // Process announce with registered handlers for _, handler := range a.handlers { if handler.ReceivePathResponses() || !a.pathResponse { - if err := handler.ReceivedAnnounce(destHash, a.identity, appData); err != nil { + if err := handler.ReceivedAnnounce(destHash, announcedIdentity, appData); err != nil { return err } } @@ -148,7 +160,7 @@ func (a *Announce) HandleAnnounce(data []byte) error { return nil } -func (a *Announce) RequestPath(destHash []byte, onInterface transport.Interface) error { +func (a *Announce) RequestPath(destHash []byte, onInterface common.NetworkInterface) error { a.mutex.Lock() defer a.mutex.Unlock() @@ -158,7 +170,7 @@ func (a *Announce) RequestPath(destHash []byte, onInterface transport.Interface) packet = append(packet, byte(0)) // Initial hop count // Send path request - if err := onInterface.SendPathRequest(packet); err != nil { + if err := onInterface.Send(packet, ""); err != nil { return err } diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index 53bf662..e425cb0 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -435,4 +435,26 @@ func (i *Identity) Hash() []byte { func (i *Identity) Hex() string { return hex.EncodeToString(i.Hash()) +} + +func FromPublicKey(publicKey []byte) *Identity { + if len(publicKey) != curve25519.PointSize { + return nil + } + + i := &Identity{ + publicKey: append([]byte{}, publicKey...), + ratchets: make(map[string][]byte), + ratchetExpiry: make(map[string]int64), + } + + // Generate Ed25519 verification key from the X25519 public key + hash := sha256.New() + hash.Write(publicKey) + seed := hash.Sum(nil) + + // Use the first 32 bytes of the hash as the verification key + i.verificationKey = ed25519.PublicKey(seed[:32]) + + return i } \ No newline at end of file