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

tox-rs / tox / 4706770020

pending completion
4706770020

push

github

GitHub
Merge pull request #470 from tox-rs/bump

10 of 10 new or added lines in 5 files covered. (100.0%)

15888 of 16785 relevant lines covered (94.66%)

1.95 hits per line

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

74.17
/tox_node/src/node_config.rs
1
use std::convert::TryInto;
2
use std::net::{SocketAddr, ToSocketAddrs};
3
use std::num::ParseIntError;
4
use std::str::FromStr;
5
use std::collections::HashMap;
6

7
use config::{Config, File as CfgFile, FileFormat as CfgFileFormat};
8
use serde::{de, Deserialize, Deserializer};
9
use serde_yaml::Value;
10
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, builder::{ArgPredicate, PossibleValue}, Command, ValueEnum, value_parser};
11
use hex::FromHex;
12
use itertools::Itertools;
13
use tox::crypto::*;
14
use tox::packet::dht::packed_node::PackedNode;
15
use tox::packet::dht::BOOSTRAP_SERVER_MAX_MOTD_LENGTH;
16

17
/// Config for threading.
18
#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize)]
19
pub enum Threads {
20
    /// Detect number of threads automatically by the number of CPU cores.
21
    Auto,
22
    /// Exact number of threads.
23
    N(u16)
24
}
25

26
impl FromStr for Threads {
27
    type Err = ParseIntError;
28

29
    fn from_str(s: &str) -> Result<Self, Self::Err> {
1✔
30
        if s == "auto" {
1✔
31
            Ok(Threads::Auto)
×
32
        } else {
33
            u16::from_str(s).map(Threads::N)
1✔
34
        }
35
    }
36
}
37

38
/// Specifies where to write logs.
39
#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize)]
40
pub enum LogType {
41
    Stderr,
42
    Stdout,
43
    #[cfg(unix)]
44
    Syslog,
45
    None,
46
}
47

48
impl ValueEnum for LogType {
49
    fn value_variants<'a>() -> &'a [Self] {
1✔
50
        use self::LogType::*;
51
        &[
×
52
            Stderr,
×
53
            Stdout,
×
54
            #[cfg(unix)]
×
55
            Syslog,
×
56
            None,
×
57
        ]
58
    }
59

60
    fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
1✔
61
        use self::LogType::*;
62
        Some(match self {
2✔
63
            Stderr => PossibleValue::new("Stderr"),
1✔
64
            Stdout => PossibleValue::new("Stdout"),
1✔
65
            #[cfg(unix)]
×
66
            Syslog => PossibleValue::new("Syslog"),
1✔
67
            None => PossibleValue::new("None"),
1✔
68
        })
69
    }
70
}
71

72
/// Bootstrap node with generic string address which might be either IP address
73
/// or DNS name.
74
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
75
pub struct BootstrapNode {
76
    /// `PublicKey` of the node.
77
    #[serde(deserialize_with = "de_from_hex")]
78
    pk: PublicKey,
79
    /// Generic string address which might be either IP address or DNS name.
80
    addr: String,
81
}
82

83
impl BootstrapNode {
84
    /// Resolve string address of the node to possible multiple `SocketAddr`s.
85
    pub fn resolve(&self) -> impl Iterator<Item = PackedNode> {
×
86
        let pk = self.pk.clone();
×
87
        let addrs = match self.addr.to_socket_addrs() {
×
88
            Ok(addrs) => addrs,
×
89
            Err(e) => {
×
90
                warn!("Failed to resolve bootstrap node address '{}': {}", self.addr, e);
×
91
                Vec::new().into_iter()
×
92
            },
93
        };
94
        addrs.map(move |addr| PackedNode::new(addr, pk.clone()))
×
95
    }
96
}
97

98
fn de_from_hex<'de, D>(deserializer: D) -> Result<PublicKey, D::Error> where D: Deserializer<'de> {
×
99
    let s = String::deserialize(deserializer)?;
×
100

101
    let bootstrap_pk_bytes: [u8; 32] = FromHex::from_hex(s)
×
102
        .map_err(|e| de::Error::custom(format!("Can't make bytes from hex string {:?}", e)))?;
×
103
    Ok(PublicKey::from(bootstrap_pk_bytes))
×
104
}
105

106
fn de_threads<'de, D>(deserializer: D) -> Result<Threads, D::Error> where D: Deserializer<'de> {
×
107
    let s = String::deserialize(deserializer)?;
×
108

109
    Threads::from_str(&s)
×
110
        .map_err(|e| de::Error::custom(format!("threads: {:?}", e)))
×
111
}
112

113
/// Config parsed from command line arguments.
114
#[derive(Clone, Debug, Deserialize)]
115
pub struct NodeConfig {
116
    /// UDP address to run DHT node
117
    #[serde(rename = "udp-address")]
118
    #[serde(default)]
119
    pub udp_addr: Option<SocketAddr>,
120
    /// TCP addresses to run TCP relay
121
    #[serde(rename = "tcp-addresses")]
122
    #[serde(default)]
123
    pub tcp_addrs: Vec<SocketAddr>,
124
    /// Maximum number of active TCP connections relay can hold.
125
    #[serde(rename = "tcp-connections-limit")]
126
    pub tcp_connections_limit: usize,
127
    /// DHT SecretKey
128
    #[serde(skip_deserializing)]
129
    pub sk: Option<SecretKey>,
130
    /// True if the SecretKey was passed as an argument instead of environment
131
    /// variable. Necessary to print a warning since the logger backend is not
132
    /// initialized when we parse arguments.
133
    #[serde(skip_deserializing)]
134
    pub sk_passed_as_arg: bool,
135
    /// Path to the file where DHT keys are stored.
136
    /// Required with config.
137
    #[serde(rename = "keys-file")]
138
    pub keys_file: Option<String>,
139
    /// List of bootstrap nodes.
140
    #[serde(rename = "bootstrap-nodes")]
141
    #[serde(default)]
142
    pub bootstrap_nodes: Vec<BootstrapNode>,
143
    /// Number of threads for execution.
144
    #[serde(deserialize_with = "de_threads")]
145
    pub threads: Threads,
146
    /// Specifies where to write logs.
147
    #[serde(rename = "log-type")]
148
    pub log_type: LogType,
149
    /// Message of the day
150
    pub motd: String,
151
    /// Whether LAN discovery is enabled
152
    #[serde(rename = "lan-discovery")]
153
    pub lan_discovery_enabled: bool,
154
    /// Unused fields while parsing config file
155
    #[serde(flatten)]
156
    pub unused: HashMap<String, Value>,
157
}
158

159
fn create_sk_arg() -> Arg {
1✔
160
    Arg::new("secret-key")
1✔
161
        .short('s')
162
        .long("secret-key")
163
        .help("DHT secret key. Note that you should not pass the key via \
164
               arguments due to security reasons. Use this argument for \
165
               test purposes only. In the real world use the environment \
166
               variable instead")
167
        .num_args(1)
168
        .conflicts_with("keys-file")
169
        .env("TOX_SECRET_KEY")
170
        .hide(true)
171
}
172

173
fn create_keys_file_arg() -> Arg {
1✔
174
    Arg::new("keys-file")
1✔
175
        .short('k')
176
        .long("keys-file")
177
        .help("Path to the file where DHT keys are stored")
178
        .num_args(1)
179
        .required_unless_present("secret-key")
180
        .conflicts_with("secret-key")
181
}
182

183
fn app() -> Command {
1✔
184
    Command::new(crate_name!())
14✔
185
        .version(crate_version!())
186
        .about(crate_description!())
187
        .args_conflicts_with_subcommands(true)
188
        .subcommand(Command::new("config")
2✔
189
            .arg(Arg::new("cfg-file")
2✔
190
                .index(1)
191
                .help("Load settings from saved config file. \
192
                    Config file format is YAML")
193
                .num_args(1)
194
                .required(true)))
2✔
195
        .subcommand(Command::new("derive-pk")
4✔
196
            .about("Derive PK from either --keys-file or from env:TOX_SECRET_KEY")
1✔
197
            .arg(create_sk_arg())
2✔
198
            .arg(create_keys_file_arg()))
2✔
199
        // here go args without subcommands
200
        .arg(create_sk_arg())
2✔
201
        .arg(create_keys_file_arg())
2✔
202
        .arg(Arg::new("udp-address")
4✔
203
            .short('u')
204
            .long("udp-address")
205
            .help("UDP address to run DHT node")
206
            .num_args(1)
1✔
207
            .value_parser(value_parser!(SocketAddr))
2✔
208
            .required_unless_present("tcp-address"))
1✔
209
        .arg(Arg::new("tcp-address")
6✔
210
            .short('t')
211
            .long("tcp-address")
212
            .help("TCP address to run TCP relay")
213
            .num_args(1..)
2✔
214
            .value_parser(value_parser!(SocketAddr))
2✔
215
            .action(clap::ArgAction::Append)
1✔
216
            .required_unless_present("udp-address"))
1✔
217
        .arg(Arg::new("tcp-connections-limit")
4✔
218
            .short('c')
219
            .long("tcp-connections-limit")
220
            .help("Maximum number of active TCP connections relay can hold. \
221
                   Defaults to 512 when tcp-address is specified")
222
            .requires("tcp-address")
223
            .num_args(1)
1✔
224
            .value_parser(value_parser!(usize))
2✔
225
            .default_value_if("tcp-address", ArgPredicate::IsPresent, Some("512")))
2✔
226
        .arg(Arg::new("bootstrap-node")
4✔
227
            .short('b')
228
            .long("bootstrap-node")
229
            .help("Node to perform initial bootstrap")
230
            .num_args(2)
231
            .action(clap::ArgAction::Append)
1✔
232
            .value_names(["public key", "address"]))
2✔
233
        .group(ArgGroup::new("req_flags")
2✔
234
            .args(["bootstrap-node", "tcp-address"])
1✔
235
            .multiple(true))
1✔
236
        .arg(Arg::new("threads")
4✔
237
            .short('j')
238
            .long("threads")
239
            .help("Number of threads to use. The value 'auto' means that the \
240
                   number of threads will be determined automatically by the \
241
                   number of CPU cores")
242
            .num_args(1)
1✔
243
            .value_parser(value_parser!(Threads))
2✔
244
            .default_value("1"))
1✔
245
        .arg(Arg::new("log-type")
4✔
246
            .short('l')
247
            .long("log-type")
248
            .help("Where to write logs")
249
            .num_args(1)
1✔
250
            .value_parser(value_parser!(LogType))
2✔
251
            .default_value("Stderr"))
1✔
252
        .arg(Arg::new("motd")
2✔
253
            .short('m')
254
            .long("motd")
255
            .help("Message of the day. Must be no longer than 256 bytes. May \
256
                   contain next variables placed in {{ }}:\n\
257
                   - start_date: time when the node was started\n\
258
                   - uptime: uptime in the format 'XX days XX hours XX minutes'\n")
259
            .num_args(1)
260
            .value_parser(|m: &str| {
1✔
261
                if m.len() > BOOSTRAP_SERVER_MAX_MOTD_LENGTH {
2✔
262
                    Err(format!("Message of the day must not be longer than {} bytes", BOOSTRAP_SERVER_MAX_MOTD_LENGTH))
1✔
263
                } else {
264
                    Ok(m.to_string())
1✔
265
                }
266
            })
267
            .default_value("This is tox-rs"))
1✔
268
        .arg(Arg::new("lan-discovery")
3✔
269
            .long("lan-discovery")
270
            .help("Enable LAN discovery (disabled by default)")
271
            .action(ArgAction::SetTrue))
2✔
272
}
273

274
/// Parse command line arguments.
275
pub fn cli_parse() -> NodeConfig {
×
276
    let matches = app().get_matches();
×
277

278
    match matches.subcommand() {
×
279
        Some(("derive-pk", m)) => run_derive_pk(m),
×
280
        Some(("config", m)) => run_config(m),
×
281
        _ => run_args(&matches),
×
282
    }
283
}
284

285
/// Parse settings from a saved file.
286
fn parse_config(config_path: &str) -> NodeConfig {
×
287
    let config_builder = Config::builder()
×
288
        .set_default("log-type", "Stderr").expect("Can't set default value for `log-type`")
289
        .set_default("motd", "This is tox-rs").expect("Can't set default value for `motd`")
290
        .set_default("lan-discovery", "False").expect("Can't set default value for `lan-discovery`")
291
        .set_default("threads", "1").expect("Can't set default value for `threads`")
292
        .set_default("tcp-connections-limit", "512").expect("Can't set default value for `tcp-connections-limit`")
293
        .add_source(CfgFile::new(config_path, CfgFileFormat::Yaml));
×
294

295
    let config_file = match config_builder.build() {
×
296
        Ok(cfg) => cfg,
×
297
        Err(e) => panic!("Can't build config file {}", e),
×
298
    };
299

300
    let config: NodeConfig = config_file.try_deserialize().expect("Can't deserialize config");
×
301

302
    if config.keys_file.is_none() {
×
303
        panic!("Can't deserialize config: 'keys-file' is not set");
×
304
    }
305

306
    config
307
}
308

309
fn run_derive_pk(matches: &ArgMatches) -> ! {
×
310
    let sk_passed_as_arg = matches.contains_id("secret-key");
×
311
    if sk_passed_as_arg {
×
312
        panic!("You should not pass the secret key via arguments due to \
×
313
               security reasons. Use the environment variable instead");
314
    }
315

316
    let pk_from_arg = matches.get_one::<String>("secret-key").map(|s| {
×
317
        let sk_bytes: [u8; 32] = FromHex::from_hex(s).expect("Invalid DHT secret key");
×
318
        SecretKey::from(sk_bytes).public_key()
×
319
    });
320
    let pk_from_file = matches.get_one::<String>("keys-file").map(|keys_file| {
×
321
        let mut file = std::fs::File::open(keys_file).expect("Failed to read the keys file");
×
322

323
        let mut buf = [0; crypto_box::KEY_SIZE * 2];
×
324
        use std::io::Read;
325
        file.read_exact(&mut buf).expect("Failed to read keys from the keys file");
×
326
        let pk_bytes: [u8; crypto_box::KEY_SIZE] = buf[..crypto_box::KEY_SIZE].try_into().expect("Failed to read public key from the keys file");
×
327
        let sk_bytes: [u8; crypto_box::KEY_SIZE] = buf[crypto_box::KEY_SIZE..].try_into().expect("Failed to read secret key from the keys file");
×
328
        let pk = PublicKey::from(pk_bytes);
×
329
        let sk = SecretKey::from(sk_bytes);
×
330
        assert!(pk == sk.public_key(), "The loaded public key does not correspond to the loaded secret key");
×
331
        pk
332
    });
333

334
    let pk = pk_from_arg.or(pk_from_file).unwrap();
×
335

336
    println!("{}", hex::encode(pk).to_uppercase());
×
337

338
    // FIXME: use ExitCode::SUCCESS when stabilized
339
    // https://doc.rust-lang.org/std/process/struct.ExitCode.html
340
    std::process::exit(0)
×
341
}
342

343
fn run_config(matches: &ArgMatches) -> NodeConfig {
×
344
    let config_path = matches.get_one::<String>("cfg-file").unwrap();
×
345

346
    parse_config(config_path)
×
347
}
348

349
fn run_args(matches: &ArgMatches) -> NodeConfig {
1✔
350
    let udp_addr = matches.get_one::<SocketAddr>("udp-address").copied();
1✔
351

352
    let tcp_addrs: Vec<SocketAddr> = matches.get_many("tcp-address").unwrap_or_default().copied().collect();
1✔
353

354
    let tcp_connections_limit = matches.get_one::<usize>("tcp-connections-limit").copied()
2✔
355
        .unwrap_or(512);
356

357
    let sk = matches.get_one::<String>("secret-key").map(|s| {
2✔
358
        let sk_bytes: [u8; 32] = FromHex::from_hex(s).expect("Invalid DHT secret key");
1✔
359
        SecretKey::from(sk_bytes)
1✔
360
    });
361

362
    let sk_passed_as_arg = matches.contains_id("secret-key");
2✔
363

364
    let keys_file = matches.get_one("keys-file").cloned();
1✔
365

366
    let bootstrap_nodes = matches
2✔
367
        .get_many::<String>("bootstrap-node")
368
        .into_iter()
369
        .flatten()
370
        .tuples()
371
        .map(|(pk, addr)| {
2✔
372
            // get PK bytes of the bootstrap node
373
            let bootstrap_pk_bytes: [u8; 32] = FromHex::from_hex(pk).expect("Invalid node key");
1✔
374
            // create PK from bytes
375
            let bootstrap_pk = PublicKey::from(bootstrap_pk_bytes);
1✔
376

377
            BootstrapNode {
1✔
378
                pk: bootstrap_pk,
1✔
379
                addr: addr.to_string(),
1✔
380
            }
381
        })
382
        .collect();
383

384
    let threads = matches.get_one("threads").copied().unwrap();
2✔
385

386
    let log_type = matches.get_one("log-type").copied().unwrap();
1✔
387

388
    let motd = matches.get_one("motd").cloned().unwrap();
1✔
389

390
    let lan_discovery_enabled = matches.get_flag("lan-discovery");
2✔
391

392
    NodeConfig {
393
        udp_addr,
394
        tcp_addrs,
395
        tcp_connections_limit,
396
        sk,
397
        sk_passed_as_arg,
398
        keys_file,
399
        bootstrap_nodes,
400
        threads,
401
        log_type,
402
        motd,
403
        lan_discovery_enabled,
404
        unused: HashMap::new(),
1✔
405
    }
406
}
407

408
#[cfg(test)]
409
mod tests {
410
    use super::*;
411

412
    #[test]
413
    fn args_udp_only() {
3✔
414
        let saddr = "127.0.0.1:33445";
1✔
415
        let matches = app().get_matches_from(vec![
2✔
416
            "tox-node",
417
            "--keys-file",
418
            "./keys",
419
            "--udp-address",
420
            saddr,
421
        ]);
422
        let config = run_args(&matches);
1✔
423
        assert_eq!(config.keys_file.unwrap(), "./keys");
2✔
424
        assert_eq!(config.udp_addr.unwrap(), saddr.parse().unwrap());
1✔
425
        assert!(config.tcp_addrs.is_empty());
2✔
426
        assert!(!config.lan_discovery_enabled);
1✔
427
    }
428

429
    #[test]
430
    fn args_tcp_only() {
3✔
431
        let saddr_1 = "127.0.0.1:33445";
1✔
432
        let saddr_2 = "127.0.0.1:33446";
1✔
433
        let matches = app().get_matches_from(vec![
2✔
434
            "tox-node",
435
            "--keys-file",
436
            "./keys",
437
            "--tcp-address",
438
            saddr_1,
439
            "--tcp-address",
440
            saddr_2,
441
        ]);
442
        let config = run_args(&matches);
1✔
443
        assert_eq!(config.keys_file.unwrap(), "./keys");
2✔
444
        assert!(config.udp_addr.is_none());
1✔
445
        assert_eq!(config.tcp_addrs, vec![
2✔
446
            saddr_1.parse().unwrap(),
2✔
447
            saddr_2.parse().unwrap()
1✔
448
        ]);
449
        assert!(!config.lan_discovery_enabled);
1✔
450
    }
451

452
    #[test]
453
    fn args_udp_tcp() {
3✔
454
        let saddr_1 = "127.0.0.1:33445";
1✔
455
        let saddr_2 = "127.0.0.1:33446";
1✔
456
        let matches = app().get_matches_from(vec![
2✔
457
            "tox-node",
458
            "--keys-file",
459
            "./keys",
460
            "--udp-address",
461
            saddr_1,
462
            "--tcp-address",
463
            saddr_2,
464
        ]);
465
        let config = run_args(&matches);
1✔
466
        assert_eq!(config.keys_file.unwrap(), "./keys");
2✔
467
        assert_eq!(config.udp_addr.unwrap(), saddr_1.parse().unwrap());
1✔
468
        assert_eq!(config.tcp_addrs, vec![saddr_2.parse().unwrap()]);
2✔
469
        assert!(!config.lan_discovery_enabled);
1✔
470
    }
471

472
    #[test]
473
    fn args_udp_tcp_with_secret_key() {
3✔
474
        let saddr_1 = "127.0.0.1:33445";
1✔
475
        let saddr_2 = "127.0.0.1:33446";
1✔
476
        let sk = "d5ff9ceafe9e1145bc807dc94b4ee911a5878705b5f9ee68f6ccc51e498f313c";
1✔
477
        let matches = app().get_matches_from(vec![
2✔
478
            "tox-node",
479
            "--secret-key",
480
            sk,
481
            "--udp-address",
482
            saddr_1,
483
            "--tcp-address",
484
            saddr_2,
485
        ]);
486
        let config = run_args(&matches);
1✔
487
        assert!(config.sk_passed_as_arg);
1✔
488
        assert_eq!(config.udp_addr.unwrap(), saddr_1.parse().unwrap());
2✔
489
        assert_eq!(config.tcp_addrs, vec![saddr_2.parse().unwrap()]);
2✔
490
        assert!(!config.lan_discovery_enabled);
1✔
491
    }
492

493
    #[test]
494
    fn args_udp_or_tcp_required() {
3✔
495
        let matches = app().try_get_matches_from(vec![
2✔
496
            "tox-node",
497
            "--keys-file",
498
            "./keys",
499
        ]);
500
        assert!(matches.is_err());
2✔
501
    }
502

503
    #[test]
504
    fn args_keys_file_or_secret_key_required() {
3✔
505
        let matches = app().try_get_matches_from(vec![
2✔
506
            "tox-node",
507
            "--udp-address",
508
            "127.0.0.1:33445",
509
        ]);
510
        assert!(matches.is_err());
2✔
511
    }
512

513
    #[test]
514
    fn args_keys_file_and_secret_key_conflicts() {
3✔
515
        let matches = app().try_get_matches_from(vec![
2✔
516
            "tox-node",
517
            "--keys-file",
518
            "./keys",
519
            "--secret-key",
520
            "d5ff9ceafe9e1145bc807dc94b4ee911a5878705b5f9ee68f6ccc51e498f313c",
521
            "--udp-address",
522
            "127.0.0.1:33445",
523
        ]);
524
        assert!(matches.is_err());
2✔
525
    }
526

527
    #[test]
528
    fn args_motd() {
3✔
529
        let motd = "abcdef";
1✔
530
        let matches = app().get_matches_from(vec![
2✔
531
            "tox-node",
532
            "--keys-file",
533
            "./keys",
534
            "--udp-address",
535
            "127.0.0.1:33445",
536
            "--motd",
537
            motd,
1✔
538
        ]);
539
        let config = run_args(&matches);
1✔
540
        assert_eq!(config.motd, motd);
2✔
541
    }
542

543
    #[test]
544
    fn args_motd_too_long() {
3✔
545
        let motd = "x".repeat(BOOSTRAP_SERVER_MAX_MOTD_LENGTH + 1);
1✔
546
        let matches = app().try_get_matches_from(vec![
3✔
547
            "tox-node",
548
            "--keys-file",
549
            "./keys",
550
            "--udp-address",
551
            "127.0.0.1:33445",
552
            "--motd",
553
            &motd,
1✔
554
        ]);
555
        assert!(matches.is_err());
2✔
556
    }
557

558
    #[test]
559
    fn args_lan_discovery() {
3✔
560
        let matches = app().get_matches_from(vec![
2✔
561
            "tox-node",
562
            "--keys-file",
563
            "./keys",
564
            "--udp-address",
565
            "127.0.0.1:33445",
566
            "--lan-discovery",
567
        ]);
568
        let config = run_args(&matches);
1✔
569
        assert!(config.lan_discovery_enabled);
1✔
570
    }
571

572
    #[test]
573
    fn args_bootstrap_nodes() {
3✔
574
        let pk_1 = "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67";
1✔
575
        let addr_1 = "node.tox.biribiri.org:33445";
1✔
576
        let pk_2 = "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832";
1✔
577
        let addr_2 = "85.172.30.117:33445";
1✔
578
        let matches = app().get_matches_from(vec![
2✔
579
            "tox-node",
580
            "--keys-file",
581
            "./keys",
582
            "--udp-address",
583
            "127.0.0.1:33445",
584
            "--bootstrap-node",
585
            pk_1,
586
            addr_1,
587
            "--bootstrap-node",
588
            pk_2,
589
            addr_2,
590
        ]);
591
        let config = run_args(&matches);
1✔
592
        let node_1 = BootstrapNode {
593
            pk: {
594
                let pk_bytes = <[u8; 32]>::from_hex(pk_1).unwrap();
595
                PublicKey::from(pk_bytes)
596
            },
597
            addr: addr_1.into(),
1✔
598
        };
599
        let node_2 = BootstrapNode {
600
            pk: {
601
                let pk_bytes = <[u8; 32]>::from_hex(pk_2).unwrap();
602
                PublicKey::from(pk_bytes)
603
            },
604
            addr: addr_2.into(),
1✔
605
        };
606
        assert_eq!(config.bootstrap_nodes, vec![node_1, node_2]);
2✔
607
    }
608

609
    #[test]
610
    fn args_log_type() {
3✔
611
        let matches = app().get_matches_from(vec![
2✔
612
            "tox-node",
613
            "--keys-file",
614
            "./keys",
615
            "--udp-address",
616
            "127.0.0.1:33445",
617
            "--log-type",
618
            "None"
619
        ]);
620
        let config = run_args(&matches);
1✔
621
        assert_eq!(config.log_type, LogType::None);
2✔
622
    }
623

624
    #[test]
625
    fn args_tcp_connections_limit() {
3✔
626
        let matches = app().get_matches_from(vec![
2✔
627
            "tox-node",
628
            "--keys-file",
629
            "./keys",
630
            "--tcp-address",
631
            "127.0.0.1:33445",
632
            "--tcp-connections-limit",
633
            "42"
634
        ]);
635
        let config = run_args(&matches);
1✔
636
        assert_eq!(config.tcp_connections_limit, 42);
1✔
637
    }
638

639
    #[test]
640
    fn args_tcp_connections_limit_requires_tcp_addr() {
3✔
641
        let matches = app().try_get_matches_from(vec![
2✔
642
            "tox-node",
643
            "--keys-file",
644
            "./keys",
645
            "--udp-address",
646
            "127.0.0.1:33445",
647
            "--tcp-connections-limit",
648
            "42"
649
        ]);
650
        assert!(matches.is_err());
2✔
651
    }
652

653
    #[test]
654
    fn args_threads() {
3✔
655
        let matches = app().get_matches_from(vec![
2✔
656
            "tox-node",
657
            "--keys-file",
658
            "./keys",
659
            "--udp-address",
660
            "127.0.0.1:33445",
661
            "--threads",
662
            "42"
663
        ]);
664
        let config = run_args(&matches);
1✔
665
        assert_eq!(config.threads, Threads::N(42));
2✔
666
    }
667

668
    #[test]
669
    fn args_derive_pk_keys_file() {
3✔
670
        let matches = app().get_matches_from(vec![
2✔
671
            "tox-node",
672
            "derive-pk",
673
            "--keys-file",
674
            "./keys",
675
        ]);
676
        let matches = matches.subcommand_matches("derive-pk").unwrap();
2✔
677
        assert_eq!("./keys", matches.get_one::<String>("keys-file").unwrap());
1✔
678
    }
679

680
    #[test]
681
    fn args_derive_pk_secret_key() {
3✔
682
        let sk_str = "d7f04a6db2c12f1eae0229c72e6bc429ca894541acc5f292da0e4d9a47827774";
1✔
683
        let matches = app().get_matches_from(vec![
2✔
684
            "tox-node",
685
            "derive-pk",
686
            "--secret-key",
687
            sk_str
1✔
688
        ]);
689
        let matches = matches.subcommand_matches("derive-pk").unwrap();
2✔
690
        assert_eq!(sk_str, matches.get_one::<String>("secret-key").unwrap());
1✔
691
    }
692
}
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

© 2026 Coveralls, Inc