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