#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
use super::*;
use std::iter;
use async_trait::async_trait;
use futures::channel::mpsc;
use futures_await_test::async_test;
use itertools::{zip_eq, Itertools};
use tor_cell::chancell::msg::PaddingNegotiateCmd;
use tor_config::PaddingLevel;
use tor_linkspec::{HasRelayIds, RelayIds};
use tor_netdir::NetDir;
use tor_proto::channel::{Channel, CtrlMsg};
use crate::mgr::{AbstractChanMgr, AbstractChannelFactory};
use crate::ChannelUsage;
use crate::factory::BootstrapReporter;
use PaddingLevel as PL;
const DEF_MS: [u32; 2] = [1500, 9500];
const REDUCED_MS: [u32; 2] = [9000, 14000];
const ADJ_MS: [u32; 2] = [1499, 9499];
const ADJ_REDUCED_MS: [u32; 2] = [8999, 13999];
fn some_interesting_netdir<'v, V>(values: V) -> Arc<NetDir>
where
V: IntoIterator<Item = (&'v str, i32)>,
{
tor_netdir::testnet::construct_custom_netdir_with_params(|_, _| {}, values)
.unwrap()
.unwrap_if_sufficient()
.unwrap()
.into()
}
fn interesting_netdir() -> Arc<NetDir> {
some_interesting_netdir(
[
("nf_ito_low", ADJ_MS[0]),
("nf_ito_high", ADJ_MS[1]),
("nf_ito_low_reduced", ADJ_REDUCED_MS[0]),
("nf_ito_high_reduced", ADJ_REDUCED_MS[1]),
]
.into_iter()
.map(|(k, v)| (k, v as _)),
)
}
#[test]
fn padding_parameters_calculation() {
fn one(pconfig: PaddingLevel, netparams: &NetParamsExtract, exp: Option<[u32; 2]>) {
eprintln!(
"### {:?} {:?}",
&pconfig,
netparams.nf_ito.map(|l| l.map(|v| v.as_millis().get())),
);
let got = padding_parameters(pconfig, netparams).unwrap();
let exp = exp.map(|exp| {
PaddingParameters::builder()
.low(exp[0].into())
.high(exp[1].into())
.build()
.unwrap()
});
assert_eq!(got, exp);
}
one(
PL::default(),
&NetParamsExtract::from(interesting_netdir().params()),
Some(ADJ_MS),
);
one(
PL::Reduced,
&NetParamsExtract::from(interesting_netdir().params()),
Some(ADJ_REDUCED_MS),
);
let make_bogus_netdir = |values: &[(&str, i32)]| {
NetParamsExtract::from(
tor_netdir::testnet::construct_custom_netdir_with_params(
|_, _| {},
values.iter().cloned(),
)
.unwrap()
.unwrap_if_sufficient()
.unwrap()
.params(),
)
};
let bogus_netdir = make_bogus_netdir(&[
("nf_ito_low", ADJ_REDUCED_MS[1] as _),
("nf_ito_high", ADJ_REDUCED_MS[0] as _),
]);
one(PL::default(), &bogus_netdir, Some(DEF_MS));
}
#[derive(Clone)]
struct FakeChannelFactory {
channel: Channel,
}
#[async_trait]
impl AbstractChannelFactory for FakeChannelFactory {
type Channel = Channel;
type BuildSpec = tor_linkspec::RelayIds;
async fn build_channel(
&self,
_target: &Self::BuildSpec,
_reporter: BootstrapReporter,
) -> Result<Self::Channel> {
Ok(self.channel.clone())
}
}
struct CaseContext {
channel: Channel,
recv: mpsc::UnboundedReceiver<CtrlMsg>,
chanmgr: AbstractChanMgr<FakeChannelFactory>,
netparams: Arc<dyn AsRef<NetParameters>>,
}
#[derive(Debug, Clone, Default)]
struct Expected {
enabled: Option<bool>,
timing: Option<[u32; 2]>,
nego: Option<(PaddingNegotiateCmd, [u32; 2])>,
}
async fn case(level: PaddingLevel, dormancy: Dormancy, usage: ChannelUsage) -> CaseContext {
let mut cconfig = ChannelConfig::builder();
cconfig.padding(level);
let cconfig = cconfig.build().unwrap();
eprintln!("\n---- {:?} {:?} {:?} ----", &cconfig, &dormancy, &usage);
let (channel, recv) = Channel::new_fake();
let peer_id = channel.target().ed_identity().unwrap().clone();
let relay_ids = RelayIds::builder()
.ed_identity(peer_id.clone())
.build()
.unwrap();
let factory = FakeChannelFactory { channel };
let netparams = Arc::new(NetParameters::default());
let chanmgr = AbstractChanMgr::new(
factory,
&cconfig,
dormancy,
&netparams,
BootstrapReporter::fake(),
);
let (channel, _prov) = chanmgr.get_or_launch(relay_ids, usage).await.unwrap();
CaseContext {
channel,
recv,
chanmgr,
netparams,
}
}
impl CaseContext {
fn netparams(&self) -> Arc<dyn AsRef<NetParameters>> {
self.netparams.clone()
}
fn expect_1(&mut self, exp: Expected) {
self.expect(vec![exp]);
}
fn expect_0(&mut self) {
self.expect(vec![]);
}
fn expect(&mut self, expected: Vec<Expected>) {
let messages = iter::from_fn(|| match self.recv.try_next() {
Ok(Some(t)) => Some(Ok(t)),
Ok(None) => Some(Err(())),
Err(_) => None,
})
.collect_vec();
eprintln!("{:#?}", &messages);
for (i, (got, exp)) in zip_eq(messages, expected).enumerate() {
eprintln!("{} {:?} {:?}", i, got, exp);
let got: ChannelPaddingInstructionsUpdates = match got {
Ok(CtrlMsg::ConfigUpdate(u)) => (*u).clone(),
_ => panic!("wrong message {:?}", got),
};
let Expected {
enabled,
timing,
nego,
} = exp;
let nego =
nego.map(|(cmd, [low, high])| PaddingNegotiate::from_raw(cmd, low as _, high as _));
let timing = timing.map(|[low, high]| {
PaddingParameters::builder()
.low(low.into())
.high(high.into())
.build()
.unwrap()
});
assert_eq!(got.padding_enable(), enabled.as_ref());
assert_eq!(got.padding_parameters(), timing.as_ref());
assert_eq!(got.padding_negotiate(), nego.as_ref());
}
}
}
#[async_test]
async fn padding_control_through_layers() {
const STOP_MSG: (PaddingNegotiateCmd, [u32; 2]) = (PaddingNegotiateCmd::STOP, [0, 0]);
const START_CMD: PaddingNegotiateCmd = PaddingNegotiateCmd::START;
let mut c = case(PL::default(), Dormancy::Active, ChannelUsage::UserTraffic).await;
c.expect_1(Expected {
enabled: Some(true),
timing: Some(DEF_MS),
nego: None,
});
let mut c = case(PL::Reduced, Dormancy::Active, ChannelUsage::UserTraffic).await;
c.expect_1(Expected {
enabled: Some(true),
timing: Some(REDUCED_MS),
nego: Some(STOP_MSG),
});
let mut c = case(PL::default(), Dormancy::Dormant, ChannelUsage::UserTraffic).await;
c.expect_1(Expected {
enabled: None,
timing: None,
nego: Some(STOP_MSG),
});
let cconfig_reduced = {
let mut cconfig = ChannelConfig::builder();
cconfig.padding(PL::Reduced);
cconfig.build().unwrap()
};
let mut c = case(PL::default(), Dormancy::Active, ChannelUsage::Dir).await;
c.expect_0();
eprintln!("### UserTraffic ###");
c.channel.engage_padding_activities();
c.expect_1(Expected {
enabled: Some(true), timing: Some(DEF_MS), nego: None, });
eprintln!("### set_dormancy - Dormant ###");
c.chanmgr
.set_dormancy(Dormancy::Dormant, c.netparams())
.unwrap();
c.expect_1(Expected {
enabled: Some(false), timing: None,
nego: Some(STOP_MSG), });
eprintln!("### change to reduced padding while dormant ###");
c.chanmgr
.reconfigure(&cconfig_reduced, c.netparams())
.unwrap();
c.expect_0();
eprintln!("### set_dormancy - Active ###");
c.chanmgr
.set_dormancy(Dormancy::Active, c.netparams())
.unwrap();
c.expect_1(Expected {
enabled: Some(true),
timing: Some(REDUCED_MS),
nego: None, });
eprintln!("### imagine a netdir turns up, with some different parameters ###");
c.netparams = interesting_netdir();
c.chanmgr.update_netparams(c.netparams()).unwrap();
c.expect_1(Expected {
enabled: None, timing: Some(ADJ_REDUCED_MS), nego: None, });
eprintln!("### change back to normal padding ###");
c.chanmgr
.reconfigure(&ChannelConfig::default(), c.netparams())
.unwrap();
c.expect_1(Expected {
enabled: None, timing: Some(ADJ_MS), nego: Some((START_CMD, [0, 0])), });
eprintln!("### consensus changes to no padding ###");
c.netparams = some_interesting_netdir(
[
"nf_ito_low",
"nf_ito_high",
"nf_ito_low_reduced",
"nf_ito_high_reduced",
]
.into_iter()
.map(|k| (k, 0)),
);
c.chanmgr.update_netparams(c.netparams()).unwrap();
c.expect_1(Expected {
enabled: Some(false),
timing: None,
nego: None,
});
}