Skip to main content

rns_net/common/
destination.rs

1//! Application-facing Destination and AnnouncedIdentity types.
2//!
3//! `Destination` is a pure data struct representing a network endpoint.
4//! `AnnouncedIdentity` captures the result of a received announce.
5
6use rns_core::destination::destination_hash;
7use rns_core::transport::types::InterfaceId;
8use rns_core::types::{DestHash, DestinationType, Direction, IdentityHash, ProofStrategy};
9use rns_crypto::token::Token;
10use rns_crypto::OsRng;
11use rns_crypto::Rng;
12
13/// Errors related to GROUP destination key operations.
14#[derive(Debug, PartialEq)]
15pub enum GroupKeyError {
16    /// No symmetric key has been loaded or generated.
17    NoKey,
18    /// Key must be 32 bytes (AES-128) or 64 bytes (AES-256).
19    InvalidKeyLength,
20    /// Encryption failed.
21    EncryptionFailed,
22    /// Decryption failed (wrong key, tampered data, or invalid format).
23    DecryptionFailed,
24}
25
26impl core::fmt::Display for GroupKeyError {
27    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28        match self {
29            GroupKeyError::NoKey => write!(f, "No GROUP key loaded"),
30            GroupKeyError::InvalidKeyLength => write!(f, "Key must be 32 or 64 bytes"),
31            GroupKeyError::EncryptionFailed => write!(f, "Encryption failed"),
32            GroupKeyError::DecryptionFailed => write!(f, "Decryption failed"),
33        }
34    }
35}
36
37/// A network destination (endpoint) for sending or receiving packets.
38///
39/// This is a pure data struct with no behavior — all operations
40/// (register, announce, send) are methods on `RnsNode`.
41#[derive(Debug, Clone)]
42pub struct Destination {
43    /// Computed destination hash.
44    pub hash: DestHash,
45    /// Type: Single, Group, or Plain.
46    pub dest_type: DestinationType,
47    /// Direction: In (receiving) or Out (sending).
48    pub direction: Direction,
49    /// Application name (e.g. "echo_app").
50    pub app_name: String,
51    /// Aspects (e.g. ["echo", "request"]).
52    pub aspects: Vec<String>,
53    /// Identity hash of the owner (for SINGLE destinations).
54    pub identity_hash: Option<IdentityHash>,
55    /// Full public key (64 bytes) of the remote peer (for OUT SINGLE destinations).
56    pub public_key: Option<[u8; 64]>,
57    /// Symmetric key for GROUP destinations (32 or 64 bytes).
58    pub group_key: Option<Vec<u8>>,
59    /// How to handle proofs for incoming packets.
60    pub proof_strategy: ProofStrategy,
61}
62
63impl Destination {
64    /// Create an inbound SINGLE destination (for receiving encrypted packets).
65    ///
66    /// `identity_hash` is the local identity that owns this destination.
67    pub fn single_in(app_name: &str, aspects: &[&str], identity_hash: IdentityHash) -> Self {
68        let dh = destination_hash(app_name, aspects, Some(&identity_hash.0));
69        Destination {
70            hash: DestHash(dh),
71            dest_type: DestinationType::Single,
72            direction: Direction::In,
73            app_name: app_name.into(),
74            aspects: aspects.iter().map(|s| s.to_string()).collect(),
75            identity_hash: Some(identity_hash),
76            public_key: None,
77            group_key: None,
78            proof_strategy: ProofStrategy::ProveNone,
79        }
80    }
81
82    /// Create an outbound SINGLE destination (for sending encrypted packets).
83    ///
84    /// `recalled` contains the remote peer's identity data (from announce/recall).
85    pub fn single_out(app_name: &str, aspects: &[&str], recalled: &AnnouncedIdentity) -> Self {
86        let dh = destination_hash(app_name, aspects, Some(&recalled.identity_hash.0));
87        Destination {
88            hash: DestHash(dh),
89            dest_type: DestinationType::Single,
90            direction: Direction::Out,
91            app_name: app_name.into(),
92            aspects: aspects.iter().map(|s| s.to_string()).collect(),
93            identity_hash: Some(recalled.identity_hash),
94            public_key: Some(recalled.public_key),
95            group_key: None,
96            proof_strategy: ProofStrategy::ProveNone,
97        }
98    }
99
100    /// Create a PLAIN destination (unencrypted, no identity).
101    pub fn plain(app_name: &str, aspects: &[&str]) -> Self {
102        let dh = destination_hash(app_name, aspects, None);
103        Destination {
104            hash: DestHash(dh),
105            dest_type: DestinationType::Plain,
106            direction: Direction::In,
107            app_name: app_name.into(),
108            aspects: aspects.iter().map(|s| s.to_string()).collect(),
109            identity_hash: None,
110            public_key: None,
111            group_key: None,
112            proof_strategy: ProofStrategy::ProveNone,
113        }
114    }
115
116    /// Create a GROUP destination (symmetric encryption with pre-shared key).
117    ///
118    /// No identity needed — the hash is based only on app_name + aspects,
119    /// same as PLAIN. All members sharing the same key can encrypt/decrypt.
120    pub fn group(app_name: &str, aspects: &[&str]) -> Self {
121        let dh = destination_hash(app_name, aspects, None);
122        Destination {
123            hash: DestHash(dh),
124            dest_type: DestinationType::Group,
125            direction: Direction::In,
126            app_name: app_name.into(),
127            aspects: aspects.iter().map(|s| s.to_string()).collect(),
128            identity_hash: None,
129            public_key: None,
130            group_key: None,
131            proof_strategy: ProofStrategy::ProveNone,
132        }
133    }
134
135    /// Generate a new random 64-byte symmetric key (AES-256) for this GROUP destination.
136    pub fn create_keys(&mut self) {
137        let mut key = vec![0u8; 64];
138        OsRng.fill_bytes(&mut key);
139        self.group_key = Some(key);
140    }
141
142    /// Load an existing symmetric key for this GROUP destination.
143    ///
144    /// Key must be 32 bytes (AES-128) or 64 bytes (AES-256).
145    pub fn load_private_key(&mut self, key: Vec<u8>) -> Result<(), GroupKeyError> {
146        if key.len() != 32 && key.len() != 64 {
147            return Err(GroupKeyError::InvalidKeyLength);
148        }
149        self.group_key = Some(key);
150        Ok(())
151    }
152
153    /// Retrieve the symmetric key bytes, if set.
154    pub fn get_private_key(&self) -> Option<&[u8]> {
155        self.group_key.as_deref()
156    }
157
158    /// Encrypt plaintext using this destination's GROUP key.
159    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
160        let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
161        let token = Token::new(key).map_err(|_| GroupKeyError::EncryptionFailed)?;
162        Ok(token.encrypt(plaintext, &mut OsRng))
163    }
164
165    /// Decrypt ciphertext using this destination's GROUP key.
166    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, GroupKeyError> {
167        let key = self.group_key.as_ref().ok_or(GroupKeyError::NoKey)?;
168        let token = Token::new(key).map_err(|_| GroupKeyError::DecryptionFailed)?;
169        token
170            .decrypt(ciphertext)
171            .map_err(|_| GroupKeyError::DecryptionFailed)
172    }
173
174    /// Set the proof strategy for this destination.
175    pub fn set_proof_strategy(mut self, strategy: ProofStrategy) -> Self {
176        self.proof_strategy = strategy;
177        self
178    }
179}
180
181/// Information about an announced identity, received via announce or recalled from cache.
182#[derive(Debug, Clone)]
183pub struct AnnouncedIdentity {
184    /// Destination hash that was announced.
185    pub dest_hash: DestHash,
186    /// Identity hash (truncated SHA-256 of public key).
187    pub identity_hash: IdentityHash,
188    /// Full public key (X25519 32 bytes + Ed25519 32 bytes).
189    pub public_key: [u8; 64],
190    /// Optional application data included in the announce.
191    pub app_data: Option<Vec<u8>>,
192    /// Number of hops this announce has traveled.
193    pub hops: u8,
194    /// Timestamp when this announce was received.
195    pub received_at: f64,
196    /// The interface on which this announce was received.
197    pub receiving_interface: InterfaceId,
198    /// RSSI when this announce was received.
199    pub rssi: Option<i16>,
200    /// SNR when this announce was received.
201    pub snr: Option<f32>,
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    fn test_identity_hash() -> IdentityHash {
209        IdentityHash([0x42; 16])
210    }
211
212    fn test_announced() -> AnnouncedIdentity {
213        AnnouncedIdentity {
214            dest_hash: DestHash([0xAA; 16]),
215            identity_hash: IdentityHash([0x42; 16]),
216            public_key: [0xBB; 64],
217            app_data: Some(b"test_data".to_vec()),
218            hops: 3,
219            received_at: 1234567890.0,
220            receiving_interface: InterfaceId(0),
221            rssi: Some(-100),
222            snr: Some(10.5),
223        }
224    }
225
226    #[test]
227    fn single_in_hash_matches_raw() {
228        let ih = test_identity_hash();
229        let dest = Destination::single_in("echo", &["app"], ih);
230
231        let raw = destination_hash("echo", &["app"], Some(&ih.0));
232        assert_eq!(dest.hash.0, raw);
233        assert_eq!(dest.dest_type, DestinationType::Single);
234        assert_eq!(dest.direction, Direction::In);
235        assert_eq!(dest.app_name, "echo");
236        assert_eq!(dest.aspects, vec!["app".to_string()]);
237        assert_eq!(dest.identity_hash, Some(ih));
238        assert!(dest.public_key.is_none());
239    }
240
241    #[test]
242    fn single_out_from_recalled() {
243        let recalled = test_announced();
244        let dest = Destination::single_out("echo", &["app"], &recalled);
245
246        let raw = destination_hash("echo", &["app"], Some(&recalled.identity_hash.0));
247        assert_eq!(dest.hash.0, raw);
248        assert_eq!(dest.dest_type, DestinationType::Single);
249        assert_eq!(dest.direction, Direction::Out);
250        assert_eq!(dest.public_key, Some([0xBB; 64]));
251    }
252
253    #[test]
254    fn plain_destination() {
255        let dest = Destination::plain("broadcast", &["test"]);
256
257        let raw = destination_hash("broadcast", &["test"], None);
258        assert_eq!(dest.hash.0, raw);
259        assert_eq!(dest.dest_type, DestinationType::Plain);
260        assert!(dest.identity_hash.is_none());
261        assert!(dest.public_key.is_none());
262    }
263
264    #[test]
265    fn destination_deterministic() {
266        let ih = test_identity_hash();
267        let d1 = Destination::single_in("app", &["a", "b"], ih);
268        let d2 = Destination::single_in("app", &["a", "b"], ih);
269        assert_eq!(d1.hash, d2.hash);
270    }
271
272    #[test]
273    fn different_identity_different_hash() {
274        let d1 = Destination::single_in("app", &["a"], IdentityHash([1; 16]));
275        let d2 = Destination::single_in("app", &["a"], IdentityHash([2; 16]));
276        assert_ne!(d1.hash, d2.hash);
277    }
278
279    #[test]
280    fn proof_strategy_builder() {
281        let dest = Destination::plain("app", &["a"]).set_proof_strategy(ProofStrategy::ProveAll);
282        assert_eq!(dest.proof_strategy, ProofStrategy::ProveAll);
283    }
284
285    #[test]
286    fn announced_identity_fields() {
287        let ai = test_announced();
288        assert_eq!(ai.dest_hash, DestHash([0xAA; 16]));
289        assert_eq!(ai.identity_hash, IdentityHash([0x42; 16]));
290        assert_eq!(ai.public_key, [0xBB; 64]);
291        assert_eq!(ai.app_data, Some(b"test_data".to_vec()));
292        assert_eq!(ai.hops, 3);
293        assert_eq!(ai.received_at, 1234567890.0);
294        assert_eq!(ai.receiving_interface, InterfaceId(0));
295    }
296
297    #[test]
298    fn announced_identity_receiving_interface_nonzero() {
299        let ai = AnnouncedIdentity {
300            receiving_interface: InterfaceId(42),
301            ..test_announced()
302        };
303        assert_eq!(ai.receiving_interface, InterfaceId(42));
304    }
305
306    #[test]
307    fn announced_identity_clone_preserves_receiving_interface() {
308        let ai = AnnouncedIdentity {
309            receiving_interface: InterfaceId(7),
310            ..test_announced()
311        };
312        let cloned = ai.clone();
313        assert_eq!(cloned.receiving_interface, ai.receiving_interface);
314    }
315
316    #[test]
317    fn single_out_from_recalled_with_interface() {
318        let recalled = AnnouncedIdentity {
319            receiving_interface: InterfaceId(5),
320            ..test_announced()
321        };
322        // Destination::single_out should work regardless of receiving_interface value
323        let dest = Destination::single_out("echo", &["app"], &recalled);
324        assert_eq!(dest.dest_type, DestinationType::Single);
325        assert_eq!(dest.direction, Direction::Out);
326        assert_eq!(dest.public_key, Some([0xBB; 64]));
327    }
328
329    #[test]
330    fn multiple_aspects() {
331        let dest = Destination::plain("app", &["one", "two", "three"]);
332        assert_eq!(dest.aspects, vec!["one", "two", "three"]);
333    }
334
335    // --- GROUP destination tests ---
336
337    #[test]
338    fn group_destination_hash_deterministic() {
339        let d1 = Destination::group("myapp", &["chat", "room"]);
340        let d2 = Destination::group("myapp", &["chat", "room"]);
341        assert_eq!(d1.hash, d2.hash);
342        assert_eq!(d1.dest_type, DestinationType::Group);
343        assert_eq!(d1.direction, Direction::In);
344        assert!(d1.identity_hash.is_none());
345        assert!(d1.public_key.is_none());
346        assert!(d1.group_key.is_none());
347    }
348
349    #[test]
350    fn group_destination_hash_matches_plain_hash() {
351        let group = Destination::group("broadcast", &["test"]);
352        let plain = Destination::plain("broadcast", &["test"]);
353        // GROUP and PLAIN with same name produce the same hash (no identity component)
354        assert_eq!(group.hash, plain.hash);
355    }
356
357    #[test]
358    fn group_create_keys() {
359        let mut dest = Destination::group("app", &["g"]);
360        assert!(dest.group_key.is_none());
361        dest.create_keys();
362        let key = dest.group_key.as_ref().unwrap();
363        assert_eq!(key.len(), 64);
364        // Key should not be all zeros (astronomically unlikely with real RNG)
365        assert!(key.iter().any(|&b| b != 0));
366    }
367
368    #[test]
369    fn group_load_private_key_64() {
370        let mut dest = Destination::group("app", &["g"]);
371        let key = vec![0x42u8; 64];
372        assert!(dest.load_private_key(key.clone()).is_ok());
373        assert_eq!(dest.get_private_key(), Some(key.as_slice()));
374    }
375
376    #[test]
377    fn group_load_private_key_32() {
378        let mut dest = Destination::group("app", &["g"]);
379        let key = vec![0xAB; 32];
380        assert!(dest.load_private_key(key.clone()).is_ok());
381        assert_eq!(dest.get_private_key(), Some(key.as_slice()));
382    }
383
384    #[test]
385    fn group_load_private_key_invalid_length() {
386        let mut dest = Destination::group("app", &["g"]);
387        assert_eq!(
388            dest.load_private_key(vec![0; 48]),
389            Err(GroupKeyError::InvalidKeyLength)
390        );
391        assert_eq!(
392            dest.load_private_key(vec![0; 16]),
393            Err(GroupKeyError::InvalidKeyLength)
394        );
395    }
396
397    #[test]
398    fn group_encrypt_decrypt_roundtrip() {
399        let mut dest = Destination::group("app", &["secure"]);
400        dest.load_private_key(vec![0x42u8; 64]).unwrap();
401
402        let plaintext = b"Hello, GROUP destination!";
403        let ciphertext = dest.encrypt(plaintext).unwrap();
404        assert_ne!(ciphertext.as_slice(), plaintext);
405        assert!(ciphertext.len() > plaintext.len()); // includes IV + HMAC overhead
406
407        let decrypted = dest.decrypt(&ciphertext).unwrap();
408        assert_eq!(decrypted, plaintext);
409    }
410
411    #[test]
412    fn group_decrypt_wrong_key_fails() {
413        let mut dest1 = Destination::group("app", &["a"]);
414        dest1.load_private_key(vec![0x42u8; 64]).unwrap();
415
416        let mut dest2 = Destination::group("app", &["a"]);
417        dest2.load_private_key(vec![0xBBu8; 64]).unwrap();
418
419        let ciphertext = dest1.encrypt(b"secret").unwrap();
420        assert_eq!(
421            dest2.decrypt(&ciphertext),
422            Err(GroupKeyError::DecryptionFailed)
423        );
424    }
425
426    #[test]
427    fn group_encrypt_without_key_fails() {
428        let dest = Destination::group("app", &["a"]);
429        assert_eq!(dest.encrypt(b"test"), Err(GroupKeyError::NoKey));
430        assert_eq!(dest.decrypt(b"test"), Err(GroupKeyError::NoKey));
431    }
432
433    #[test]
434    fn group_key_interop_with_token() {
435        // Encrypt with Token directly, decrypt with Destination (and vice versa)
436        let key = vec![0x42u8; 64];
437
438        let token = Token::new(&key).unwrap();
439        let ciphertext = token.encrypt(b"from token", &mut OsRng);
440
441        let mut dest = Destination::group("app", &["a"]);
442        dest.load_private_key(key.clone()).unwrap();
443        let decrypted = dest.decrypt(&ciphertext).unwrap();
444        assert_eq!(decrypted, b"from token");
445
446        // And the other direction
447        let ciphertext2 = dest.encrypt(b"from dest").unwrap();
448        let decrypted2 = token.decrypt(&ciphertext2).unwrap();
449        assert_eq!(decrypted2, b"from dest");
450    }
451
452    #[test]
453    fn group_encrypt_decrypt_32byte_key() {
454        let mut dest = Destination::group("app", &["aes128"]);
455        dest.load_private_key(vec![0xABu8; 32]).unwrap();
456
457        let plaintext = b"AES-128 mode";
458        let ciphertext = dest.encrypt(plaintext).unwrap();
459        let decrypted = dest.decrypt(&ciphertext).unwrap();
460        assert_eq!(decrypted, plaintext);
461    }
462}