1use crate::{
2 args::{Args, Opts},
3 debug,
4};
5
6#[cfg(unix)]
7use std::os::unix::fs::DirBuilderExt;
8use std::{env, fs::DirBuilder, io::Error, net::SocketAddr, str::FromStr};
9
10use itertools::Itertools;
11use tokio::net::TcpStream;
12use url::Url;
13
14pub mod constants {
16 pub const CURRENT_TRANSPORT_VER: &str = "1";
18
19 pub const MANAGED_VER: &str = "TOR_PT_MANAGED_TRANSPORT_VER";
21 pub const STATE_LOCATION: &str = "TOR_PT_STATE_LOCATION";
23 pub const CLIENT_TRANSPORTS: &str = "TOR_PT_CLIENT_TRANSPORTS";
25 pub const PROXY: &str = "TOR_PT_PROXY";
27 pub const SERVER_TRANSPORTS: &str = "TOR_PT_SERVER_TRANSPORTS";
29 pub const SERVER_TRANSPORT_OPTIONS: &str = "TOR_PT_SERVER_TRANSPORT_OPTIONS";
31 pub const SERVER_BINDADDR: &str = "TOR_PT_SERVER_BINDADDR";
33 pub const AUTH_COOKIE_FILE: &str = "TOR_PT_AUTH_COOKIE_FILE";
35 pub const ORPORT: &str = "TOR_PT_ORPORT";
37 pub const EXTENDED_SERVER_PORT: &str = "TOR_PT_EXTENDED_SERVER_PORT";
39 pub const EXIT_ON_STDIN_CLOSE: &str = "TOR_PT_EXIT_ON_STDIN_CLOSE";
41}
42
43pub(crate) fn get_managed_transport_ver() -> Result<String, Error> {
47 let managed_transport_ver = env::var(constants::MANAGED_VER).map_err(to_io_other)?;
48 for segment in managed_transport_ver.split(',') {
49 if segment == constants::CURRENT_TRANSPORT_VER {
50 return Ok(segment.into());
51 }
52 }
53
54 Err(to_io_other("no-version"))
55}
56
57pub fn is_client() -> Result<bool, Error> {
61 let is_client = env::var_os(constants::CLIENT_TRANSPORTS);
62 let is_server = env::var_os(constants::SERVER_TRANSPORTS);
63
64 match (is_client, is_server) {
65 (Some(_), Some(_)) => Err(to_io_other(
66 "ENV-ERROR TOR_PT_[CLIENT,SERVER]_TRANSPORTS both set",
67 )),
68 (Some(_), None) => Ok(true),
69 (None, Some(_)) => Ok(false),
70 (None, None) => Err(to_io_other("not launched as a managed transport")),
71 }
72}
73
74pub fn make_state_dir() -> Result<String, Error> {
80 let path = env::var(constants::STATE_LOCATION)
81 .map_err(|_| to_io_other("missing required TOR_PT_STATE_LOCATION env var"))?;
82
83 let mut builder = DirBuilder::new();
84 builder.recursive(true);
85 #[cfg(unix)]
86 builder.mode(0o700);
87 builder.create(&path)?;
88 Ok(path)
89}
90
91pub fn pt_should_exit_on_stdin_close() -> bool {
94 if let Ok(v) = env::var(constants::EXIT_ON_STDIN_CLOSE) {
95 v == "1"
96 } else {
97 false
98 }
99}
100
101pub struct ClientInfo {
107 pub methods: Vec<String>,
109 pub uri: Option<Url>,
111}
112
113impl ClientInfo {
114 pub fn new() -> Result<Self, Error> {
116 let _ver = get_managed_transport_ver()?;
117 debug!("VERSION {_ver}");
118
119 Ok(Self {
120 methods: get_client_transports()?,
121 uri: get_proxy_url()?,
122 })
123 }
124}
125
126pub(crate) fn get_client_transports() -> Result<Vec<String>, Error> {
127 let client_transports = env::var(constants::CLIENT_TRANSPORTS).map_err(to_io_other)?;
128 Ok(client_transports.split(',').map(String::from).collect_vec())
129}
130
131pub(crate) fn get_proxy_url() -> Result<Option<Url>, Error> {
132 let url_str = match env::var(constants::PROXY) {
133 Ok(s) => s,
134 Err(env::VarError::NotPresent) => return Ok(None),
135 Err(e) => return Err(to_io_other(format!("failed to parse proxy config: {e}"))),
136 };
137
138 let uri = Url::parse(&url_str)
140 .map_err(|e| to_io_other(format!("failed to parse proxy config \"{url_str}\": {e}")))?;
141
142 validate_proxy_url(&uri)?;
143
144 Ok(Some(uri))
145}
146
147#[allow(clippy::collapsible_if)]
181pub(crate) fn validate_proxy_url(spec: &Url) -> Result<(), Error> {
182 const SCHEMES: [&str; 3] = ["socks5", "socks4a", "http"];
183 if !SCHEMES.contains(&spec.scheme()) {
184 return Err(to_io_other(format!(
185 "proxy URI has invalid scheme: {}",
186 spec.scheme()
187 )));
188 }
189
190 if !spec.path().is_empty() {
192 if !(spec.scheme() == "http" && spec.path() == "/") {
193 return Err(to_io_other("proxy URI has a path defined "));
194 }
195 }
196 if spec.query().is_some() {
197 if !spec.query().unwrap().is_empty() {
198 return Err(to_io_other("proxy URI has a query defined"));
199 }
200 }
201 if spec.fragment().is_some() {
202 if !spec.fragment().unwrap().is_empty() {
203 return Err(to_io_other("proxy URI has a fragment defined"));
204 }
205 }
206 if spec.port().is_none() {
207 return Err(to_io_other("proxy URI lacks a port"));
208 }
209
210 match spec.scheme() {
211 "socks5" => {
212 let username = spec.username();
213 let passwd = spec.password();
214
215 if !username.is_empty() || passwd.is_some() {
217 if username.is_empty() || username.len() > 255 {
218 return Err(to_io_other("proxy URI specified a invalid SOCKS5 username"));
219 }
220 if passwd.is_none() {
221 return Err(to_io_other("proxy URI specified a invalid SOCKS5 password"));
222 } else if let Some(p) = passwd {
223 if p.is_empty() || p.len() > 255 {
224 return Err(to_io_other("proxy URI specified a invalid SOCKS5 password"));
225 }
226 }
227 }
228 }
229 "socks4a" => {
230 if spec.password().is_some() {
231 return Err(to_io_other("proxy URI specified SOCKS4a and a password"));
232 }
233 }
234 "http" => {}
235 _ => {
236 return Err(to_io_other(format!(
237 "proxy URI has invalid scheme: {}",
238 spec.scheme()
239 )));
240 }
241 }
242
243 if spec.host_str().is_none() {
244 return Err(to_io_other("proxy URI has missing host"));
245 }
246
247 let mut sockaddr_string = String::from(spec.host_str().unwrap());
249 sockaddr_string.push(':');
250 sockaddr_string.push_str(&format!("{}", spec.port().unwrap()));
251 let _ = resolve_addr(&sockaddr_string)
252 .map_err(|e| to_io_other(format!("proxy URI has invalid host: {e}")))?;
253
254 Ok(())
255}
256
257#[derive(Clone, Debug, Default, PartialEq)]
283pub struct ServerInfo {
284 pub bind_addrs: Vec<Bindaddr>,
286 pub or_addr: Option<SocketAddr>,
288 pub extended_or_addr: Option<SocketAddr>,
290 pub auth_cookie_path: Option<String>,
292}
293
294impl ServerInfo {
295 pub async fn connect_to_or(&self) -> Result<TcpStream, Error> {
297 let conn = match self.or_addr {
298 Some(addr) => TcpStream::connect(addr).await?,
299 None => {
300 let addr = self
303 .extended_or_addr
304 .ok_or_else(|| to_io_other("no OR addr provided"))?;
305 TcpStream::connect(addr).await?
306 }
307 };
308
309 Ok(conn)
310 }
311}
312
313impl ServerInfo {
314 pub fn new() -> Result<Self, Error> {
316 let _ver = get_managed_transport_ver()?;
317 debug!("VERSION {_ver}");
318
319 let bind_addrs = Bindaddr::get_server_bindaddrs()?;
320
321 let or_addr = match env::var(constants::ORPORT) {
322 Ok(or_add_env) => Some(
323 resolve_addr(or_add_env)
324 .map_err(|e| to_io_other(format!("cannot resolve TOR_PT_ORPORT: {e}")))?,
325 ),
326 Err(_) => None, };
328
329 let auth_cookie_path = env::var(constants::AUTH_COOKIE_FILE).ok();
330
331 let extended_or_addr = match env::var(constants::EXTENDED_SERVER_PORT) {
332 Ok(ext_or_addr_env) => Some(resolve_addr(ext_or_addr_env).map_err(|e| {
333 to_io_other(format!("cannot resolve TOR_PT_EXTENDED_SERVER_PORT: {e}"))
334 })?),
335 Err(_) => None, };
337
338 if extended_or_addr.is_some() && auth_cookie_path.is_none() {
339 return Err(to_io_other("need TOR_PT_AUTH_COOKIE_FILE environment variable with TOR_PT_EXTENDED_SERVER_PORT"));
340 }
341
342 if or_addr.is_none() && extended_or_addr.is_none() {
344 return Err(to_io_other(
345 "need TOR_PT_ORPORT or TOR_PT_EXTENDED_SERVER_PORT environment variable",
346 ));
347 }
348
349 Ok(Self {
350 bind_addrs,
351 or_addr,
352 extended_or_addr,
353 auth_cookie_path,
354 })
355 }
356}
357
358#[derive(Clone, Debug, PartialEq)]
360pub struct Bindaddr {
361 pub method_name: String,
363 pub addr: SocketAddr,
365 pub options: Args,
367}
368
369impl Bindaddr {
370 pub fn new(method: &str, addr: SocketAddr, options: Args) -> Self {
372 Self {
373 method_name: method.into(),
374 addr,
375 options,
376 }
377 }
378
379 pub(crate) fn get_server_bindaddrs() -> Result<Vec<Self>, Error> {
383 let server_transport_opts =
385 env::var(constants::SERVER_TRANSPORT_OPTIONS).unwrap_or_default();
386
387 let mut options_map = Opts::parse_server_transport_options(&server_transport_opts)
388 .map_err(|e| {
389 to_io_other(format!(
390 "TOR_PT_SERVER_TRANSPORT_OPTIONS: {server_transport_opts}: \"{e}\""
391 ))
392 })?;
393
394 let server_bindaddr = env::var(constants::SERVER_BINDADDR).map_err(to_io_other)?;
396 if server_bindaddr.is_empty() {
397 return Err(to_io_other(format!(
398 "no \"{}\" environment variable value",
399 constants::SERVER_BINDADDR
400 )));
401 }
402
403 let mut results = Vec::new();
404 let mut seen_methods = Vec::new();
405 for spec in server_bindaddr.split(',') {
406 let parts = spec.split_once('-');
407 if parts.is_none() {
408 return Err(to_io_other(format!(
409 "TPR_PT_SERVER_BINDADDR: {spec} doesn't contain \"-\""
410 )));
411 }
412 let (method_name, addr) = parts.unwrap();
413
414 if seen_methods.contains(&method_name) {
417 return Err(to_io_other(format!(
418 "TPR_PT_SERVER_BINDADDR: {spec} duplicate method name {method_name}"
419 )));
420 }
421 seen_methods.push(method_name);
422 let address = resolve_addr(addr)
423 .map_err(|e| to_io_other(format!("TOR_PT_SERVER_BINDADDR: {spec}: {e}")))?;
424
425 results.push(Bindaddr::new(
426 method_name,
427 address,
428 options_map.remove(method_name).unwrap_or_default(),
429 ));
430 }
431 let server_transports = env::var(constants::SERVER_TRANSPORTS).map_err(to_io_other)?;
432 if server_transports.is_empty() {
433 return Err(to_io_other(format!(
434 "no \"{}\" environment variable value",
435 constants::SERVER_TRANSPORTS
436 )));
437 }
438
439 let result = filter_bindaddrs(results, &server_transports.split(',').collect_vec());
440 Ok(result)
441 }
442}
443
444fn filter_bindaddrs(addrs: Vec<Bindaddr>, methods: &[&str]) -> Vec<Bindaddr> {
445 if methods.is_empty() {
446 return Vec::new();
447 }
448 addrs
449 .into_iter()
450 .filter(|b| methods.contains(&b.method_name.as_str()))
451 .collect()
452}
453
454pub fn resolve_addr(addr: impl AsRef<str>) -> Result<SocketAddr, Error> {
456 let a = addr.as_ref();
457 match SocketAddr::from_str(a) {
458 Ok(sock_addr) => {
459 if sock_addr.ip().is_unspecified() {
460 return Err(to_io_other(format!("address string {a} lacks a host")));
461 }
462
463 if sock_addr.port() == 0 {
464 return Err(to_io_other(format!("address string {a} lacks a port")));
465 }
466 Ok(sock_addr)
467 }
468 Err(e) => Err(to_io_other(format!("\"{a}\" - {e}"))),
469 }
470}
471
472fn to_io_other(e: impl std::fmt::Display) -> Error {
473 Error::other(format!("{e}"))
474}
475
476#[cfg(test)]
477#[serial_test::serial]
478mod test {
479 use super::*;
480
481 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
482
483 #[test]
484 fn is_client_from_env() -> Result<(), Error> {
485 env::remove_var(constants::CLIENT_TRANSPORTS);
486 env::remove_var(constants::SERVER_TRANSPORTS);
487 assert!(is_client().is_err());
488
489 env::set_var(constants::CLIENT_TRANSPORTS, "trebuchet");
490 env::remove_var(constants::SERVER_TRANSPORTS);
491 let c = is_client();
492 assert!(c.is_ok());
493 assert!(c.unwrap());
494
495 env::remove_var(constants::CLIENT_TRANSPORTS);
496 env::set_var(constants::SERVER_TRANSPORTS, "trebuchet1");
497 let c = is_client();
498 assert!(c.is_ok());
499 assert!(!c.unwrap());
500
501 env::set_var(constants::CLIENT_TRANSPORTS, "trebuchet2");
502 env::set_var(constants::SERVER_TRANSPORTS, "trebuchet2");
503 assert!(is_client().is_err());
504
505 Ok(())
506 }
507
508 #[test]
509 fn statedir() -> Result<(), Error> {
510 env::remove_var(constants::STATE_LOCATION);
512 if make_state_dir().is_ok() {
513 panic!("empty environment unexpectedly succeeded");
514 }
515
516 let temp_dir = tempfile::tempdir()?;
518 let good = vec![
525 temp_dir.path().to_path_buf(),
527 temp_dir.path().join("parentExists"),
529 temp_dir.path().join("missingParent").join("parentMissing"),
531 ];
532 for trial in good {
533 env::set_var("TOR_PT_STATE_LOCATION", trial.to_str().unwrap());
534 let dir = make_state_dir()?;
535 if dir != trial.to_str().unwrap() {
536 panic!("make_state_dir returned an unexpected path {dir} (expecting {trial:?})");
537 }
538 }
539
540 let temp_file = temp_dir.path().join("file");
542 let _ = std::fs::File::create(&temp_file)?;
543
544 env::set_var("TOR_PT_STATE_LOCATION", &temp_file);
545 assert!(
546 make_state_dir().is_err(),
547 "make_state_dir with a file unexpectedly succeeded"
548 );
549
550 env::set_var("TOR_PT_STATE_LOCATION", temp_file.join("subDir"));
552 assert!(
553 make_state_dir().is_err(),
554 "make_state_dir with a subdirectory of a file unexpectedly succeeded"
555 );
556
557 Ok(())
558 }
559
560 #[test]
561 fn server_bindaddrs() -> Result<(), Error> {
562 assert!(Bindaddr::get_server_bindaddrs().is_err());
565
566 let bad = vec![
567 ("alpha", "alpha", ""),
569 ("alpha-1.2.3.4", "alpha", ""),
570 ("alpha-1.2.3.4:1111", "", "alpha:key=value"),
572 ("alpha-1.2.3.4:1111", "alpha", "key=value"),
574 (r"alpha\,beta-1.2.3.4:1111", r"alpha\,beta", ""),
577 (r"alpha-0.0.0.0:1234,alpha-[::]:1234", r"alpha", ""),
580 (r"alpha-0.0.0.0:1234,alpha-0.0.0.0:1234", r"alpha", ""),
581 ];
582
583 for trial in bad {
584 env::set_var(constants::SERVER_BINDADDR, trial.0);
585 env::set_var(constants::SERVER_TRANSPORTS, trial.1);
586 env::set_var(constants::SERVER_TRANSPORT_OPTIONS, trial.2);
587 assert!(
588 Bindaddr::get_server_bindaddrs().is_err(),
589 "{:?} unexpectedly succeeded",
590 trial
591 );
592 }
593
594 let good = vec![
595 (
596 "alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222",
597 "alpha,beta,gamma",
598 "alpha:k1=v1;beta:k2=v2;gamma:k3=v3",
599 vec![
600 Bindaddr::new(
601 "alpha",
602 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 1111),
603 args! {"k1"=>["v1"]},
604 ),
605 Bindaddr::new(
606 "beta",
607 SocketAddr::new(IpAddr::V6(Ipv6Addr::new(1, 2, 0, 0, 0, 0, 3, 4)), 2222),
608 args! {"k2"=>["v2"]},
609 ),
610 ],
611 ),
612 ("alpha-1.2.3.4:1111", "xxx", "", vec![]),
613 (
614 "alpha-1.2.3.4:1111",
615 "alpha,beta,gamma",
616 "",
617 vec![Bindaddr::new(
618 "alpha",
619 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 1111),
620 Args::default(),
621 )],
622 ),
623 (
624 "trebuchet-127.0.0.1:1984,ballista-127.0.0.1:4891",
625 "trebuchet,ballista",
626 "trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes",
627 vec![
628 Bindaddr::new(
629 "trebuchet",
630 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1984),
631 args! {"secret"=>["nou"], "cache"=>["/tmp/cache"]},
632 ),
633 Bindaddr::new(
634 "ballista",
635 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 4891),
636 args!("secret"=>["yes"]),
637 ),
638 ],
639 ),
640 ("alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222", "*", "", vec![]),
644 ];
645
646 for trial in good {
647 env::set_var(constants::SERVER_BINDADDR, trial.0);
648 env::set_var(constants::SERVER_TRANSPORTS, trial.1);
649 env::set_var(constants::SERVER_TRANSPORT_OPTIONS, trial.2);
650
651 let out = Bindaddr::get_server_bindaddrs();
652 assert!(out.is_ok(), "{:?} unexpectedly failed: {out:?}", trial);
653
654 assert_eq!(out.unwrap(), trial.3);
655 }
656 Ok(())
657 }
658
659 #[test]
660 fn validate_url() -> Result<(), Error> {
661 env::remove_var(constants::PROXY);
662 let url = get_proxy_url();
663 assert!(url.is_ok());
664 assert!(url.unwrap().is_none());
665
666 let bad_url = vec![
667 "asdals;kdmma",
668 "http/example.com",
669 "127.0.0.1:8080",
670 "socks5://admin:admin@:9000", ];
672
673 let bad = vec![
674 "socks5://admin:admin@1.2.3.4:",
675 "ftp://127.0.0.1:8000", "socks5://aaa:bbb@1.2.3.4:80/a/b/c", "socks5://aaa:bbb@1.2.3.4:80/?labels=E-easy&state=open", "socks5://aaa:bbb@1.2.3.4:80#row=4", "socks5://myhost", "socks5://myproxy:8080", "socks5://aaa:bbb@myhost.com:8888", "socks5://aaa:bbb@myhost", "socks4a://:admin@1.2.3.4:8080", "http://admin:admin@example.com",
685 "socks5://1.2.3.4", "socks5://[1:2::3:4]",
687 "socks5://admin:admin@1.2.3.4",
688 "socks4a://1.2.3.4",
689 "socks4a://[1:2::3:4]",
690 "socks5://admin:admin@[1:2::3:4]",
691 "socks4a://admin:admin@1.2.3.4:8080", "socks4a://:admin@1.2.3.4:8080", "socks5://admin@[1:2::3:4]:9000", "socks5://:admin@[1:2::3:4]:9000", ];
696
697 let good = vec![
698 "socks5://127.0.0.1:8080",
699 "socks5://1.2.3.4:8080",
700 "socks5://[1:2::3:4]:8080",
701 "socks5://admin:admin@1.2.3.4:8080",
702 "socks5://admin:admin@1.2.3.4:8080",
703 "socks5://admin:admin@[1:2::3:4]:9000",
704 "socks4a://1.2.3.4:8080",
705 "socks4a://[1:2::3:4]:8080",
706 "socks4a://admin@1.2.3.4:8080",
707 "http://1.2.3.4:8080",
708 "http://[1:2::3:4]:8080",
709 "http://admin@1.2.3.4:8080",
710 "http://admin:admin@1.2.3.4:8080",
711 ];
712
713 for trial in bad_url {
714 let url = Url::parse(trial);
715 assert!(
716 url.is_err(),
717 "\"{trial}\" unexpectedly succeeded in parsing: {url:?}"
718 );
719 }
720
721 for trial in bad {
722 let url = Url::parse(trial).unwrap();
723 assert!(
724 validate_proxy_url(&url).is_err(),
725 "\"{trial}\" unexpectedly succeeded validation: {url:?}"
726 );
727 }
728
729 for trial in good {
730 env::set_var(constants::PROXY, trial);
731
732 let res = get_proxy_url();
733 assert!(
734 res.is_ok(),
735 "\"{trial}\" unexpectedly failed to validate: {res:?}"
736 );
737 }
738
739 Ok(())
740 }
741
742 #[test]
743 fn client_transports() -> Result<(), Error> {
744 let tests: Vec<(&str, Vec<&str>)> = vec![
745 ("alpha", vec!["alpha"]),
746 ("alpha,beta", vec!["alpha", "beta"]),
747 ("alpha,beta,gamma", vec!["alpha", "beta", "gamma"]),
748 ("*", vec!["*"]),
752 ("alpha,*,gamma", vec!["alpha", "*", "gamma"]),
753 ("alpha\\,beta", vec!["alpha\\", "beta"]),
755 ];
756
757 for trial in tests {
758 env::set_var(constants::CLIENT_TRANSPORTS, trial.0);
759 let result = get_client_transports()?;
760 assert_eq!(result, trial.1);
761 }
762
763 Ok(())
764 }
765
766 #[test]
767 fn resolve() -> Result<(), Error> {
768 let bad = vec![
769 "",
770 "1.2.3.4",
771 "1.2.3.4:",
772 "9999",
773 ":9999",
774 "[1:2::3:4]",
775 "[1:2::3:4]:",
776 "[1::2::3:4]",
777 "1:2::3:4::9999",
778 "1:2::3:4:9999", "1:2:3:4::9999",
780 "localhost:9999",
781 "[localhost]:9999",
782 "1.2.3.4:http",
783 "1.2.3.4:0x50",
784 "1.2.3.4:-65456",
785 "1.2.3.4:65536",
786 "1.2.3.4:80\x00",
787 "1.2.3.4:80 ",
788 " 1.2.3.4:80",
789 "1.2.3.4 : 80",
790 "www.google.com", "google.com:443", "0.0.0.0:9000", "[0::0000]:9000", "127.0.0.1:0", "[1234::cdef]:0", "127.0.0", ];
798 let good: Vec<(&str, SocketAddr)> = vec![
799 (
800 "1.2.3.4:9999",
801 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 9999),
802 ),
803 (
804 "[1:2::3:4]:9999",
805 SocketAddr::new(IpAddr::V6(Ipv6Addr::new(1, 2, 0, 0, 0, 0, 3, 4)), 9999),
806 ),
807 ];
810
811 for trial in good {
812 let res = resolve_addr(trial.0).unwrap();
813 assert_eq!(res, trial.1);
814 }
815
816 for trial in bad {
817 assert!(resolve_addr(trial).is_err());
818 }
819 Ok(())
820 }
821
822 #[test]
823 fn managed_ver() -> Result<(), Error> {
824 let good = vec!["1", "1,1", "1,2", "2,1", "3,2,1", "3,1,2"];
825
826 for trial in good {
827 env::set_var(constants::MANAGED_VER, trial);
828 assert_eq!(
829 get_managed_transport_ver()?,
830 constants::CURRENT_TRANSPORT_VER
831 );
832 }
833
834 env::set_var(constants::MANAGED_VER, "");
835 assert!(get_managed_transport_ver().is_err());
836
837 env::set_var(constants::MANAGED_VER, "3,2");
838 assert!(get_managed_transport_ver().is_err());
839
840 Ok(())
841 }
842
843 #[test]
846 fn exit_on_stdin_close_returns_true_when_set() {
847 env::set_var(constants::EXIT_ON_STDIN_CLOSE, "1");
848 assert!(pt_should_exit_on_stdin_close());
849 }
850
851 #[test]
852 fn exit_on_stdin_close_returns_false_when_not_one() {
853 env::set_var(constants::EXIT_ON_STDIN_CLOSE, "0");
854 assert!(!pt_should_exit_on_stdin_close());
855
856 env::set_var(constants::EXIT_ON_STDIN_CLOSE, "yes");
857 assert!(!pt_should_exit_on_stdin_close());
858 }
859
860 #[test]
861 fn exit_on_stdin_close_returns_false_when_unset() {
862 env::remove_var(constants::EXIT_ON_STDIN_CLOSE);
863 assert!(!pt_should_exit_on_stdin_close());
864 }
865}