• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tox-rs / tox / 7247588774

18 Dec 2023 11:45AM UTC coverage: 94.85% (-0.03%) from 94.876%
7247588774

Pull #477

github

web-flow
Merge 6a5a935cc into 5ab95a61f
Pull Request #477: Add functions for tox crate version information

0 of 10 new or added lines in 1 file covered. (0.0%)

13 existing lines in 7 files now uncovered.

16816 of 17729 relevant lines covered (94.85%)

1.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.47
/tox_core/src/onion/client/paths_pool.rs
1
use std::time::{Duration, Instant};
2

3
use crate::dht::server::Server as DhtServer;
4
use crate::onion::client::nodes_pool::*;
5
use crate::onion::client::onion_path::*;
6
use crate::onion::client::TIME_TO_STABLE;
7
use crate::relay::client::Connections as TcpConnections;
8
use crate::time::*;
9
use rand::{thread_rng, Rng};
10
use tox_packet::dht::packed_node::PackedNode;
11

12
/// Onion path is considered invalid after this number of unsuccessful attempts
13
/// to use it.
14
const ONION_PATH_MAX_NO_RESPONSE_USES: u32 = 4;
15

16
/// Maximum number of onion path that can be used at the same time.
17
pub const NUMBER_ONION_PATHS: usize = 6;
18

19
/// Timeout for path we haven't received any response from.
20
const ONION_PATH_FIRST_TIMEOUT: Duration = Duration::from_secs(4);
21

22
/// Timeout for path we received at least one response from.
23
const ONION_PATH_TIMEOUT: Duration = Duration::from_secs(10);
24

25
/// Maximum time for path being used.
26
const ONION_PATH_MAX_LIFETIME: Duration = Duration::from_secs(1200);
27

28
/// Onion path that is stored for later usage.
29
#[derive(Clone)]
30
pub struct StoredOnionPath {
31
    /// Onion path.
32
    pub path: OnionPath,
33
    /// Time when this path was created.
34
    pub creation_time: Instant,
35
    /// Time when this path was used to send an onion packet.
36
    pub last_used: Instant,
37
    /// Time when this path was successfully used i.e. we received response via
38
    /// it.
39
    pub last_success: Option<Instant>,
40
    /// How many times we attempted to use this path without receiving a
41
    /// response.
42
    pub attempts: u32,
43
}
44

45
impl StoredOnionPath {
46
    /// Create new `StoredOnionPath`.
47
    pub fn new(path: OnionPath) -> Self {
1✔
48
        let now = clock_now();
2✔
49
        StoredOnionPath {
50
            path,
51
            creation_time: now,
52
            last_used: now,
53
            last_success: None,
54
            attempts: ONION_PATH_MAX_NO_RESPONSE_USES / 2,
55
        }
56
    }
57

58
    /// Update last success time and attempts number.
59
    pub fn update_success(&mut self) {
×
60
        self.last_success = Some(clock_now());
×
61
        self.attempts = 0;
×
62
    }
63

64
    /// Check if we never received a response from this path.
65
    pub fn is_new(&self) -> bool {
1✔
66
        self.last_success.is_none()
1✔
67
    }
68

69
    /// Check if this path is timed out.
70
    pub fn is_timed_out(&self) -> bool {
1✔
71
        let timeout = if self.is_new() {
1✔
72
            ONION_PATH_FIRST_TIMEOUT
1✔
73
        } else {
74
            ONION_PATH_TIMEOUT
×
75
        };
76

77
        self.attempts >= ONION_PATH_MAX_NO_RESPONSE_USES && clock_elapsed(self.last_used) >= timeout
2✔
78
            || clock_elapsed(self.creation_time) >= ONION_PATH_MAX_LIFETIME
1✔
79
    }
80

81
    /// Path is considered stable after `TIME_TO_STABLE` since it was
82
    /// added to a close list if we receive responses from it.
83
    pub fn is_stable(&self) -> bool {
×
84
        clock_elapsed(self.creation_time) >= TIME_TO_STABLE
×
85
            && (self.attempts == 0 || clock_elapsed(self.last_used) < ONION_PATH_TIMEOUT)
×
86
    }
87

88
    /// Mark this path each time it was used to send request.
89
    pub fn use_path(&mut self) {
1✔
90
        self.last_used = clock_now();
1✔
91
        self.attempts += 1;
1✔
92
    }
93
}
94

95
/// Pool of random onion paths.
96
#[derive(Clone)]
97
pub struct PathsPool {
98
    /// Nodes cache for building random onion paths.
99
    pub path_nodes: NodesPool,
100
    /// List of used random onion paths for ourselves announcing.
101
    self_paths: Vec<StoredOnionPath>,
102
    /// List of used random onion paths for friends searching.
103
    friend_paths: Vec<StoredOnionPath>,
104
}
105

106
impl PathsPool {
107
    /// Create new `PathsPool`.
108
    pub fn new() -> Self {
1✔
109
        PathsPool {
110
            path_nodes: NodesPool::new(),
1✔
111
            self_paths: Vec::new(),
1✔
112
            friend_paths: Vec::new(),
1✔
113
        }
114
    }
115

116
    /// Get a random onion path. Can be either one of existent paths or newly
117
    /// generated. If we are not connected to DHT the first node from this path
118
    /// will be a TCP node.
119
    pub async fn random_path(
1✔
120
        &mut self,
121
        dht: &DhtServer,
122
        tcp_connections: &TcpConnections,
123
        friend: bool,
124
    ) -> Option<OnionPath> {
125
        let paths = if friend {
2✔
126
            &mut self.friend_paths
1✔
127
        } else {
128
            &mut self.self_paths
1✔
129
        };
130

131
        paths.retain(|stored_path| !stored_path.is_timed_out());
3✔
132

133
        let path_number = thread_rng().gen_range(0..NUMBER_ONION_PATHS);
1✔
134
        if let Some(stored_path) = paths.get_mut(path_number) {
1✔
135
            stored_path.use_path();
1✔
136
            return Some(stored_path.path.clone());
1✔
137
        }
138

139
        let path = if dht.is_connected().await {
2✔
140
            self.path_nodes.udp_path()
2✔
141
        } else if let Some(relay) = tcp_connections.get_random_relay().await {
3✔
142
            self.path_nodes.tcp_path(relay)
2✔
143
        } else {
144
            None
×
145
        };
146

147
        if let Some(path) = path {
3✔
148
            let path_id = path.id();
1✔
149
            if let Some(stored_path) = paths.iter_mut().find(|stored_path| stored_path.path.id() == path_id) {
3✔
150
                stored_path.use_path();
2✔
151
            } else {
152
                let stored_path = StoredOnionPath::new(path.clone());
2✔
153
                paths.push(stored_path);
1✔
154
            }
155
            Some(path)
1✔
156
        } else {
157
            None
×
158
        }
159
    }
160

161
    /// Get path by its `OnionPathId`. If there is no path with such id a new
162
    /// path will be generated.
163
    pub async fn get_or_random_path(
1✔
164
        &mut self,
165
        dht: &DhtServer,
166
        tcp_connections: &TcpConnections,
167
        path_id: OnionPathId,
168
        friend: bool,
169
    ) -> Option<OnionPath> {
170
        let paths = if friend {
2✔
171
            &mut self.friend_paths
1✔
172
        } else {
173
            &mut self.self_paths
1✔
174
        };
175

176
        let stored_path = paths
3✔
177
            .iter_mut()
178
            .find(|stored_path| stored_path.path.id() == path_id)
3✔
179
            .filter(|stored_path| !stored_path.is_timed_out());
2✔
180

181
        if let Some(stored_path) = stored_path {
1✔
182
            stored_path.use_path();
1✔
183
            Some(stored_path.path.clone())
1✔
184
        } else {
185
            self.random_path(dht, tcp_connections, friend).await
2✔
186
        }
187
    }
188

189
    /// Get `StoredOnionPath` by its `OnionPathId`.
190
    pub fn get_stored_path(&self, path_id: OnionPathId, friend: bool) -> Option<&StoredOnionPath> {
1✔
191
        let paths = if friend { &self.friend_paths } else { &self.self_paths };
1✔
192

193
        paths
2✔
194
            .iter()
195
            .find(|stored_path| stored_path.path.id() == path_id)
3✔
196
            .filter(|stored_path| !stored_path.is_timed_out())
2✔
197
    }
198

199
    /// Update path's timers after receiving a response via it.
200
    pub fn set_timeouts(&mut self, path_id: OnionPathId, friend: bool) {
1✔
201
        let paths = if friend {
2✔
202
            &mut self.friend_paths
1✔
203
        } else {
204
            &mut self.self_paths
1✔
205
        };
206

207
        if let Some(path) = paths.iter_mut().find(|stored_path| stored_path.path.id() == path_id) {
1✔
208
            path.update_success();
×
209
            // re-add path nodes to the cache as they are still valid
210
            for node in &path.path.nodes {
×
211
                self.path_nodes
×
212
                    .put(PackedNode::new(node.saddr, node.public_key.clone()));
×
213
            }
214
        }
215
    }
216
}
217

218
impl Default for PathsPool {
219
    fn default() -> Self {
×
220
        PathsPool::new()
×
221
    }
222
}
223

224
#[cfg(test)]
225
mod tests {
226
    use super::*;
227

228
    use crypto_box::SecretKey;
229
    use futures::channel::mpsc;
230

231
    macro_rules! paths_pool_tests {
232
        ($mod:ident, $friends:expr, $paths:ident) => {
233
            mod $mod {
234
                use super::*;
235

236
                #[tokio::test]
10✔
237
                async fn random_path_stored() {
6✔
238
                    let mut rng = thread_rng();
239
                    let dht_sk = SecretKey::generate(&mut rng);
240
                    let dht_pk = dht_sk.public_key();
241
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
242
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
243
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
244
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
245
                    let mut paths_pool = PathsPool::new();
246
                    for _ in 0..NUMBER_ONION_PATHS {
247
                        let node_1 = PackedNode::new(
248
                            "127.0.0.1:12345".parse().unwrap(),
249
                            SecretKey::generate(&mut rng).public_key(),
250
                        );
251
                        let node_2 = PackedNode::new(
252
                            "127.0.0.1:12346".parse().unwrap(),
253
                            SecretKey::generate(&mut rng).public_key(),
254
                        );
255
                        let node_3 = PackedNode::new(
256
                            "127.0.0.1:12347".parse().unwrap(),
257
                            SecretKey::generate(&mut rng).public_key(),
258
                        );
259
                        let path = OnionPath::new(
260
                            [node_1.clone(), node_2.clone(), node_3.clone()],
261
                            OnionPathType::Udp,
262
                        );
263
                        paths_pool.path_nodes.put(node_1);
264
                        paths_pool.path_nodes.put(node_2);
265
                        paths_pool.path_nodes.put(node_3);
266
                        paths_pool.$paths.push(StoredOnionPath::new(path));
267
                    }
268

269
                    assert_eq!(paths_pool.$paths.len(), NUMBER_ONION_PATHS);
270
                    let path = paths_pool
271
                        .random_path(&dht, &tcp_connections, $friends)
272
                        .await
273
                        .unwrap();
274
                    assert_eq!(paths_pool.$paths.len(), NUMBER_ONION_PATHS);
275
                    assert!(paths_pool
4✔
276
                        .$paths
277
                        .iter()
278
                        .any(|stored_path| stored_path.path.id() == path.id()));
4✔
279
                }
280

281
                #[tokio::test]
10✔
282
                async fn random_path_new_udp_random() {
6✔
283
                    let mut rng = thread_rng();
284
                    let dht_sk = SecretKey::generate(&mut rng);
285
                    let dht_pk = dht_sk.public_key();
286
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
287
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
288
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
289
                    // make DHT connected so that we will build UDP onion paths
290
                    dht.add_node(PackedNode::new(
291
                        "127.0.0.1:12345".parse().unwrap(),
292
                        SecretKey::generate(&mut rng).public_key(),
293
                    ))
294
                    .await;
295
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
296
                    let mut paths_pool = PathsPool::new();
297
                    for _ in 0..MIN_NODES_POOL_SIZE {
298
                        let node = PackedNode::new(
299
                            "127.0.0.1:12345".parse().unwrap(),
300
                            SecretKey::generate(&mut rng).public_key(),
301
                        );
302
                        paths_pool.path_nodes.put(node);
303
                    }
304

305
                    let path = paths_pool
306
                        .random_path(&dht, &tcp_connections, $friends)
307
                        .await
308
                        .unwrap();
309
                    for i in 0..3 {
4✔
310
                        assert_eq!(
311
                            path.nodes[i].public_key,
312
                            paths_pool.$paths[0].path.nodes[i].public_key
313
                        );
314
                    }
UNCOV
315
                    assert_eq!(path.path_type, OnionPathType::Udp);
×
316
                }
317

318
                #[tokio::test]
10✔
319
                async fn random_path_new_tcp_random() {
6✔
320
                    let mut rng = thread_rng();
321
                    let dht_sk = SecretKey::generate(&mut rng);
322
                    let dht_pk = dht_sk.public_key();
323
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
324
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
325
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
326
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
327
                    // add a relay that will be used as first node for onion path
328
                    let (_relay_incoming_rx, _relay_outgoing_rx, relay_pk) = tcp_connections.add_client().await;
329
                    let mut paths_pool = PathsPool::new();
330
                    for _ in 0..MIN_NODES_POOL_SIZE {
331
                        let node = PackedNode::new(
332
                            "127.0.0.1:12345".parse().unwrap(),
333
                            SecretKey::generate(&mut rng).public_key(),
334
                        );
335
                        paths_pool.path_nodes.put(node);
336
                    }
337

338
                    let path = paths_pool
339
                        .random_path(&dht, &tcp_connections, $friends)
340
                        .await
341
                        .unwrap();
342
                    for i in 0..3 {
343
                        assert_eq!(
344
                            path.nodes[i].public_key,
345
                            paths_pool.$paths[0].path.nodes[i].public_key
346
                        );
347
                    }
348
                    assert_eq!(path.nodes[0].public_key, relay_pk);
349
                    assert_eq!(path.path_type, OnionPathType::Tcp);
2✔
350
                }
351

352
                #[tokio::test]
10✔
353
                async fn get_or_random_path_stored() {
6✔
354
                    let mut rng = thread_rng();
355
                    let dht_sk = SecretKey::generate(&mut rng);
356
                    let dht_pk = dht_sk.public_key();
357
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
358
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
359
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
360
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
361
                    let mut paths_pool = PathsPool::new();
362
                    let node_1 = PackedNode::new(
363
                        "127.0.0.1:12345".parse().unwrap(),
364
                        SecretKey::generate(&mut rng).public_key(),
365
                    );
366
                    let node_2 = PackedNode::new(
367
                        "127.0.0.1:12346".parse().unwrap(),
368
                        SecretKey::generate(&mut rng).public_key(),
369
                    );
370
                    let node_3 = PackedNode::new(
371
                        "127.0.0.1:12347".parse().unwrap(),
372
                        SecretKey::generate(&mut rng).public_key(),
373
                    );
374
                    let path = OnionPath::new([node_1, node_2, node_3], OnionPathType::Udp);
375
                    paths_pool.$paths.push(StoredOnionPath::new(path.clone()));
376

377
                    let result_path = paths_pool
378
                        .get_or_random_path(&dht, &tcp_connections, path.id(), $friends)
379
                        .await
380
                        .unwrap();
381
                    for i in 0..3 {
4✔
382
                        assert_eq!(result_path.nodes[i].public_key, path.nodes[i].public_key);
383
                    }
384
                    assert_eq!(
385
                        paths_pool.$paths[0].attempts,
386
                        ONION_PATH_MAX_NO_RESPONSE_USES / 2 + 1
387
                    );
388
                }
389

390
                #[tokio::test]
10✔
391
                async fn get_or_random_path_new_udp_random() {
6✔
392
                    let mut rng = thread_rng();
393
                    let dht_sk = SecretKey::generate(&mut rng);
394
                    let dht_pk = dht_sk.public_key();
395
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
396
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
397
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
398
                    // make DHT connected so that we will build UDP onion paths
399
                    dht.add_node(PackedNode::new(
400
                        "127.0.0.1:12345".parse().unwrap(),
401
                        SecretKey::generate(&mut rng).public_key(),
402
                    ))
403
                    .await;
404
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
405
                    let mut paths_pool = PathsPool::new();
406
                    for _ in 0..MIN_NODES_POOL_SIZE {
407
                        let node = PackedNode::new(
408
                            "127.0.0.1:12345".parse().unwrap(),
409
                            SecretKey::generate(&mut rng).public_key(),
410
                        );
411
                        paths_pool.path_nodes.put(node);
412
                    }
413

414
                    let path_id = OnionPathId {
415
                        keys: [
416
                            SecretKey::generate(&mut rng).public_key(),
417
                            SecretKey::generate(&mut rng).public_key(),
418
                            SecretKey::generate(&mut rng).public_key(),
419
                        ],
420
                        path_type: OnionPathType::Udp,
421
                    };
422
                    let path = paths_pool
423
                        .get_or_random_path(&dht, &tcp_connections, path_id.clone(), $friends)
424
                        .await
425
                        .unwrap();
426
                    assert_ne!(path.id(), path_id);
427
                    for i in 0..3 {
4✔
428
                        assert_eq!(
429
                            path.nodes[i].public_key,
430
                            paths_pool.$paths[0].path.nodes[i].public_key
431
                        );
432
                    }
UNCOV
433
                    assert_eq!(path.path_type, OnionPathType::Udp);
×
434
                }
435

436
                #[tokio::test]
10✔
437
                async fn get_or_random_path_new_tcp_random() {
6✔
438
                    let mut rng = thread_rng();
439
                    let dht_sk = SecretKey::generate(&mut rng);
440
                    let dht_pk = dht_sk.public_key();
441
                    let (udp_tx, _udp_rx) = mpsc::channel(1);
442
                    let (tcp_incoming_tx, _tcp_incoming_rx) = mpsc::unbounded();
443
                    let dht = DhtServer::new(udp_tx, dht_pk.clone(), dht_sk.clone());
444
                    let tcp_connections = TcpConnections::new(dht_pk, dht_sk, tcp_incoming_tx);
445
                    // add a relay that will be used as first node for onion path
446
                    let (_relay_incoming_rx, _relay_outgoing_rx, relay_pk) = tcp_connections.add_client().await;
447
                    let mut paths_pool = PathsPool::new();
448
                    for _ in 0..MIN_NODES_POOL_SIZE {
449
                        let node = PackedNode::new(
450
                            "127.0.0.1:12345".parse().unwrap(),
451
                            SecretKey::generate(&mut rng).public_key(),
452
                        );
453
                        paths_pool.path_nodes.put(node);
454
                    }
455

456
                    let path_id = OnionPathId {
457
                        keys: [
458
                            SecretKey::generate(&mut rng).public_key(),
459
                            SecretKey::generate(&mut rng).public_key(),
460
                            SecretKey::generate(&mut rng).public_key(),
461
                        ],
462
                        path_type: OnionPathType::Tcp,
463
                    };
464
                    let path = paths_pool
465
                        .get_or_random_path(&dht, &tcp_connections, path_id.clone(), $friends)
466
                        .await
467
                        .unwrap();
468
                    assert_ne!(path.id(), path_id);
469
                    for i in 0..3 {
470
                        assert_eq!(
471
                            path.nodes[i].public_key,
472
                            paths_pool.$paths[0].path.nodes[i].public_key
473
                        );
474
                    }
475
                    assert_eq!(path.nodes[0].public_key, relay_pk);
476
                    assert_eq!(path.path_type, OnionPathType::Tcp);
2✔
477
                }
478

479
                #[test]
480
                fn get_stored_path() {
6✔
481
                    let mut rng = thread_rng();
2✔
482
                    let mut paths_pool = PathsPool::new();
2✔
483
                    let node_1 = PackedNode::new(
2✔
484
                        "127.0.0.1:12345".parse().unwrap(),
4✔
485
                        SecretKey::generate(&mut rng).public_key(),
2✔
486
                    );
487
                    let node_2 = PackedNode::new(
2✔
488
                        "127.0.0.1:12346".parse().unwrap(),
2✔
489
                        SecretKey::generate(&mut rng).public_key(),
2✔
490
                    );
491
                    let node_3 = PackedNode::new(
2✔
492
                        "127.0.0.1:12347".parse().unwrap(),
2✔
493
                        SecretKey::generate(&mut rng).public_key(),
2✔
494
                    );
495
                    let path = OnionPath::new([node_1, node_2, node_3], OnionPathType::Udp);
2✔
496
                    let path_id = path.id();
2✔
497
                    let stored_path = StoredOnionPath::new(path);
2✔
498
                    paths_pool.$paths.push(stored_path.clone());
4✔
499
                    let result_path = paths_pool.get_stored_path(path_id, $friends).unwrap();
2✔
500
                    for i in 0..3 {
4✔
501
                        assert_eq!(
6✔
502
                            result_path.path.nodes[i].public_key,
2✔
503
                            stored_path.path.nodes[i].public_key
2✔
504
                        );
505
                    }
506
                    assert_eq!(result_path.creation_time, stored_path.creation_time);
4✔
507
                }
508
            }
509
        };
510
    }
511

512
    paths_pool_tests!(self_tests, false, self_paths);
23✔
513
    paths_pool_tests!(friends_tests, true, friend_paths);
23✔
514
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc