pub const CHANNEL_BUFFER_SIZE: usize = 128;
mod circmap;
mod handler;
pub(crate) mod handshake;
pub mod kist;
mod msg;
pub mod padding;
pub mod params;
mod reactor;
mod unique_id;
pub use crate::channel::params::*;
pub(crate) use crate::channel::reactor::Reactor;
use crate::channel::reactor::{BoxedChannelSink, BoxedChannelStream};
pub use crate::channel::unique_id::UniqId;
use crate::client::circuit::PendingClientTunnel;
use crate::client::circuit::padding::{PaddingController, QueuedCellPaddingInfo};
use crate::memquota::{ChannelAccount, CircuitAccount, SpecificAccount as _};
use crate::peer::PeerInfo;
use crate::util::err::ChannelClosed;
use crate::util::oneshot_broadcast;
use crate::util::timeout::TimeoutEstimator;
use crate::util::ts::AtomicOptTimestamp;
use crate::{ClockSkew, client};
use crate::{Error, Result};
use cfg_if::cfg_if;
use reactor::BoxedChannelStreamOps;
use safelog::{MaybeSensitive, sensitive as sv};
use std::future::{Future, IntoFuture};
use std::net::IpAddr;
use std::pin::Pin;
use std::sync::{Mutex, MutexGuard};
use std::time::Duration;
use tor_cell::chancell::ChanMsg;
use tor_cell::chancell::{AnyChanCell, CircId, msg::Netinfo, msg::PaddingNegotiate};
use tor_error::internal;
use tor_linkspec::{HasRelayIds, OwnedChanTarget};
use tor_memquota::mq_queue::{self, ChannelSpec as _, MpscSpec};
use tor_rtcompat::{CoarseTimeProvider, DynTimeProvider, SleepProvider};
#[cfg(feature = "circ-padding")]
use tor_async_utils::counting_streams::{self, CountingSink, CountingStream};
#[cfg(feature = "relay")]
use crate::circuit::CircuitRxReceiver;
mod testing_exports {
#![allow(unreachable_pub)]
pub use super::reactor::CtrlMsg;
pub use crate::circuit::celltypes::CreateResponse;
}
#[cfg(feature = "testing")]
pub use testing_exports::*;
#[cfg(not(feature = "testing"))]
use testing_exports::*;
use asynchronous_codec;
use futures::channel::mpsc;
use futures::io::{AsyncRead, AsyncWrite};
use oneshot_fused_workaround as oneshot;
use educe::Educe;
use futures::{FutureExt as _, Sink};
use std::result::Result as StdResult;
use std::sync::Arc;
use std::task::{Context, Poll};
use tracing::{instrument, trace};
pub use super::client::channel::handshake::ClientInitiatorHandshake;
#[cfg(feature = "relay")]
pub use super::relay::channel::handshake::RelayInitiatorHandshake;
use crate::channel::unique_id::CircUniqIdContext;
use kist::KistParams;
#[derive(Clone, Copy, Debug, derive_more::Display)]
#[non_exhaustive]
pub enum ChannelType {
ClientInitiator,
RelayInitiator,
RelayResponder {
authenticated: bool,
},
}
impl ChannelType {
pub(crate) fn set_authenticated(&mut self) {
if let Self::RelayResponder { authenticated } = self {
*authenticated = true;
}
}
}
pub(crate) type ChannelFrame<T> = asynchronous_codec::Framed<T, handler::ChannelCellHandler>;
pub(crate) type ChanCellQueueEntry = (AnyChanCell, Option<QueuedCellPaddingInfo>);
pub(crate) fn new_frame<T, I>(tls: T, ty: I) -> ChannelFrame<T>
where
T: AsyncRead + AsyncWrite,
I: Into<handler::ChannelCellHandler>,
{
let mut framed = asynchronous_codec::Framed::new(tls, ty.into());
framed.set_send_high_water_mark(32 * 1024);
framed
}
#[derive(Debug)]
pub(crate) struct Canonicity {
pub(crate) peer_is_canonical: bool,
pub(crate) canonical_to_peer: bool,
}
impl Canonicity {
pub(crate) fn from_netinfo(
netinfo: &Netinfo,
my_addrs: &[IpAddr],
peer_addr: Option<IpAddr>,
) -> Self {
Self {
canonical_to_peer: netinfo
.their_addr()
.is_some_and(|a: &IpAddr| my_addrs.contains(a)),
peer_is_canonical: peer_addr
.map(|a| netinfo.my_addrs().contains(&a))
.unwrap_or_default(),
}
}
#[cfg(any(test, feature = "testing"))]
pub(crate) fn new_canonical() -> Self {
Self {
peer_is_canonical: true,
canonical_to_peer: true,
}
}
}
pub struct Channel {
#[expect(unused)] channel_type: ChannelType,
control: mpsc::UnboundedSender<CtrlMsg>,
cell_tx: CellTx,
reactor_closed_rx: oneshot_broadcast::Receiver<Result<CloseInfo>>,
padding_ctrl: PaddingController,
unique_id: UniqId,
peer_id: OwnedChanTarget,
#[expect(unused)] peer: MaybeSensitive<PeerInfo>,
clock_skew: ClockSkew,
opened_at: coarsetime::Instant,
mutable: Mutex<MutableDetails>,
details: Arc<ChannelDetails>,
canonicity: Canonicity,
}
#[derive(Debug)]
pub(crate) struct ChannelDetails {
unused_since: AtomicOptTimestamp,
#[allow(dead_code)]
memquota: ChannelAccount,
}
#[derive(Debug, Default)]
struct MutableDetails {
padding: PaddingControlState,
}
#[derive(Debug, Educe)]
#[educe(Default)]
enum PaddingControlState {
#[educe(Default)]
UsageDoesNotImplyPadding {
padding_params: ChannelPaddingInstructionsUpdates,
},
PaddingConfigured,
}
use PaddingControlState as PCS;
cfg_if! {
if #[cfg(feature="circ-padding")] {
type CellTx = CountingSink<mq_queue::Sender<ChanCellQueueEntry, mq_queue::MpscSpec>>;
type CellRx = CountingStream<mq_queue::Receiver<ChanCellQueueEntry, mq_queue::MpscSpec>>;
} else {
type CellTx = mq_queue::Sender<ChanCellQueueEntry, mq_queue::MpscSpec>;
type CellRx = mq_queue::Receiver<ChanCellQueueEntry, mq_queue::MpscSpec>;
}
}
#[derive(Debug)]
pub(crate) struct ChannelSender {
cell_tx: CellTx,
reactor_closed_rx: oneshot_broadcast::Receiver<Result<CloseInfo>>,
unique_id: UniqId,
padding_ctrl: PaddingController,
}
impl ChannelSender {
fn check_cell(&self, cell: &AnyChanCell) -> Result<()> {
use tor_cell::chancell::msg::AnyChanMsg::*;
let msg = cell.msg();
match msg {
Created(_) | Created2(_) | CreatedFast(_) => Err(Error::from(internal!(
"Can't send {} cell on client channel",
msg.cmd()
))),
Certs(_) | Versions(_) | Authenticate(_) | AuthChallenge(_) | Netinfo(_) => {
Err(Error::from(internal!(
"Can't send {} cell after handshake is done",
msg.cmd()
)))
}
_ => Ok(()),
}
}
pub(crate) fn time_provider(&self) -> &DynTimeProvider {
cfg_if! {
if #[cfg(feature="circ-padding")] {
self.cell_tx.inner().time_provider()
} else {
self.cell_tx.time_provider()
}
}
}
#[cfg(feature = "circ-padding")]
pub(crate) fn approx_count(&self) -> usize {
self.cell_tx.approx_count()
}
pub(crate) fn note_cell_queued(&self) {
self.padding_ctrl.queued_data(crate::HopNum::from(0));
}
}
impl Sink<ChanCellQueueEntry> for ChannelSender {
type Error = Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let this = self.get_mut();
Pin::new(&mut this.cell_tx)
.poll_ready(cx)
.map_err(|_| ChannelClosed.into())
}
fn start_send(self: Pin<&mut Self>, cell: ChanCellQueueEntry) -> Result<()> {
let this = self.get_mut();
if this.reactor_closed_rx.is_ready() {
return Err(ChannelClosed.into());
}
this.check_cell(&cell.0)?;
{
use tor_cell::chancell::msg::AnyChanMsg::*;
match cell.0.msg() {
Relay(_) | Padding(_) | Vpadding(_) => {} _ => trace!(
channel_id = %this.unique_id,
"Sending {} for {}",
cell.0.msg().cmd(),
CircId::get_or_zero(cell.0.circid())
),
}
}
Pin::new(&mut this.cell_tx)
.start_send(cell)
.map_err(|_| ChannelClosed.into())
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let this = self.get_mut();
Pin::new(&mut this.cell_tx)
.poll_flush(cx)
.map_err(|_| ChannelClosed.into())
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let this = self.get_mut();
Pin::new(&mut this.cell_tx)
.poll_close(cx)
.map_err(|_| ChannelClosed.into())
}
}
impl Channel {
#[allow(clippy::too_many_arguments)] fn new<S>(
channel_type: ChannelType,
link_protocol: u16,
sink: BoxedChannelSink,
stream: BoxedChannelStream,
streamops: BoxedChannelStreamOps,
unique_id: UniqId,
peer_id: OwnedChanTarget,
peer: MaybeSensitive<PeerInfo>,
clock_skew: ClockSkew,
sleep_prov: S,
memquota: ChannelAccount,
canonicity: Canonicity,
) -> Result<(Arc<Self>, reactor::Reactor<S>)>
where
S: CoarseTimeProvider + SleepProvider,
{
use circmap::{CircIdRange, CircMap};
let circid_range = match channel_type {
ChannelType::RelayResponder { .. } => CircIdRange::Low,
ChannelType::ClientInitiator | ChannelType::RelayInitiator => CircIdRange::High,
};
let circmap = CircMap::new(circid_range);
let dyn_time = DynTimeProvider::new(sleep_prov.clone());
let (control_tx, control_rx) = mpsc::unbounded();
let (cell_tx, cell_rx) = mq_queue::MpscSpec::new(CHANNEL_BUFFER_SIZE)
.new_mq(dyn_time.clone(), memquota.as_raw_account())?;
#[cfg(feature = "circ-padding")]
let (cell_tx, cell_rx) = counting_streams::channel(cell_tx, cell_rx);
let unused_since = AtomicOptTimestamp::new();
unused_since.update();
let mutable = MutableDetails::default();
let (reactor_closed_tx, reactor_closed_rx) = oneshot_broadcast::channel();
let details = ChannelDetails {
unused_since,
memquota,
};
let details = Arc::new(details);
let (padding_ctrl, padding_event_stream) =
client::circuit::padding::new_padding(DynTimeProvider::new(sleep_prov.clone()));
let channel = Arc::new(Channel {
channel_type,
control: control_tx,
cell_tx,
reactor_closed_rx,
padding_ctrl: padding_ctrl.clone(),
unique_id,
peer_id,
peer,
clock_skew,
opened_at: coarsetime::Instant::now(),
mutable: Mutex::new(mutable),
details: Arc::clone(&details),
canonicity,
});
let padding_timer = Box::pin(padding::Timer::new_disabled(sleep_prov.clone(), None)?);
cfg_if! {
if #[cfg(feature = "circ-padding")] {
use crate::util::sink_blocker::{SinkBlocker,CountingPolicy};
let sink = SinkBlocker::new(sink, CountingPolicy::new_unlimited());
}
}
let reactor = Reactor {
runtime: sleep_prov,
control: control_rx,
cells: cell_rx,
reactor_closed_tx,
input: futures::StreamExt::fuse(stream),
output: sink,
streamops,
circs: circmap,
circ_unique_id_ctx: CircUniqIdContext::new(),
link_protocol,
unique_id,
details,
padding_timer,
padding_ctrl,
padding_event_stream,
padding_blocker: None,
special_outgoing: Default::default(),
};
Ok((channel, reactor))
}
pub fn unique_id(&self) -> UniqId {
self.unique_id
}
pub fn mq_account(&self) -> &ChannelAccount {
&self.details.memquota
}
pub fn time_provider(&self) -> &DynTimeProvider {
cfg_if! {
if #[cfg(feature="circ-padding")] {
self.cell_tx.inner().time_provider()
} else {
self.cell_tx.time_provider()
}
}
}
pub fn target(&self) -> &OwnedChanTarget {
&self.peer_id
}
pub fn age(&self) -> Duration {
self.opened_at.elapsed().into()
}
pub fn clock_skew(&self) -> ClockSkew {
self.clock_skew
}
#[instrument(level = "trace", skip_all)]
fn send_control(&self, msg: CtrlMsg) -> StdResult<(), ChannelClosed> {
self.control
.unbounded_send(msg)
.map_err(|_| ChannelClosed)?;
Ok(())
}
fn mutable(&self) -> MutexGuard<MutableDetails> {
self.mutable.lock().expect("channel details poisoned")
}
#[instrument(level = "trace", skip_all)]
pub fn engage_padding_activities(&self) {
let mut mutable = self.mutable();
match &mutable.padding {
PCS::UsageDoesNotImplyPadding {
padding_params: params,
} => {
let mut params = params.clone();
if params.padding_negotiate == Some(PaddingNegotiate::start_default()) {
params.padding_negotiate = None;
}
match self.send_control(CtrlMsg::ConfigUpdate(Arc::new(params))) {
Ok(()) => {}
Err(ChannelClosed) => return,
}
mutable.padding = PCS::PaddingConfigured;
}
PCS::PaddingConfigured => {
}
}
drop(mutable); }
#[instrument(level = "trace", skip_all)]
pub fn reparameterize(&self, params: Arc<ChannelPaddingInstructionsUpdates>) -> Result<()> {
let mut mutable = self
.mutable
.lock()
.map_err(|_| internal!("channel details poisoned"))?;
match &mut mutable.padding {
PCS::PaddingConfigured => {
self.send_control(CtrlMsg::ConfigUpdate(params))?;
}
PCS::UsageDoesNotImplyPadding { padding_params } => {
padding_params.combine(¶ms);
}
}
drop(mutable); Ok(())
}
#[instrument(level = "trace", skip_all)]
pub fn reparameterize_kist(&self, kist_params: KistParams) -> Result<()> {
Ok(self.send_control(CtrlMsg::KistConfigUpdate(kist_params))?)
}
pub fn check_match<T: HasRelayIds + ?Sized>(&self, target: &T) -> Result<()> {
check_id_match_helper(&self.peer_id, target)
}
pub fn is_closing(&self) -> bool {
self.reactor_closed_rx.is_ready()
}
pub fn is_canonical(&self) -> bool {
self.canonicity.peer_is_canonical
}
pub fn is_canonical_to_peer(&self) -> bool {
self.canonicity.canonical_to_peer
}
pub fn duration_unused(&self) -> Option<std::time::Duration> {
self.details
.unused_since
.time_since_update()
.map(Into::into)
}
pub(crate) fn sender(&self) -> ChannelSender {
ChannelSender {
cell_tx: self.cell_tx.clone(),
reactor_closed_rx: self.reactor_closed_rx.clone(),
unique_id: self.unique_id,
padding_ctrl: self.padding_ctrl.clone(),
}
}
#[instrument(level = "trace", skip_all)]
pub async fn new_tunnel(
self: &Arc<Self>,
timeouts: Arc<dyn TimeoutEstimator>,
) -> Result<(PendingClientTunnel, client::reactor::Reactor)> {
if self.is_closing() {
return Err(ChannelClosed.into());
}
let time_prov = self.time_provider().clone();
let memquota = CircuitAccount::new(&self.details.memquota)?;
let (sender, receiver) =
MpscSpec::new(128).new_mq(time_prov.clone(), memquota.as_raw_account())?;
let (createdsender, createdreceiver) = oneshot::channel::<CreateResponse>();
let (tx, rx) = oneshot::channel();
self.send_control(CtrlMsg::AllocateCircuit {
created_sender: createdsender,
sender,
tx,
})?;
let (id, circ_unique_id, padding_ctrl, padding_stream) =
rx.await.map_err(|_| ChannelClosed)??;
trace!("{}: Allocated CircId {}", circ_unique_id, id);
Ok(PendingClientTunnel::new(
id,
self.clone(),
createdreceiver,
receiver,
circ_unique_id,
time_prov,
memquota,
padding_ctrl,
padding_stream,
timeouts,
))
}
#[cfg(feature = "relay")]
pub(crate) async fn new_outbound_circ(
self: &Arc<Self>,
memquota: CircuitAccount,
) -> Result<(CircId, CircuitRxReceiver, oneshot::Receiver<CreateResponse>)> {
if self.is_closing() {
return Err(ChannelClosed.into());
}
let time_prov = self.time_provider().clone();
let (sender, receiver) =
MpscSpec::new(128).new_mq(time_prov.clone(), memquota.as_raw_account())?;
let (createdsender, createdreceiver) = oneshot::channel::<CreateResponse>();
let (tx, rx) = oneshot::channel();
self.send_control(CtrlMsg::AllocateCircuit {
created_sender: createdsender,
sender,
tx,
})?;
let (id, circ_unique_id, _padding_ctrl, _padding_stream) =
rx.await.map_err(|_| ChannelClosed)??;
trace!("{}: Allocated CircId {}", circ_unique_id, id);
Ok((id, receiver, createdreceiver))
}
#[instrument(level = "trace", skip_all)]
pub fn terminate(&self) {
let _ = self.send_control(CtrlMsg::Shutdown);
}
#[instrument(level = "trace", skip_all)]
pub fn close_circuit(&self, circid: CircId) -> Result<()> {
self.send_control(CtrlMsg::CloseCircuit(circid))?;
Ok(())
}
pub fn wait_for_close(
&self,
) -> impl Future<Output = StdResult<CloseInfo, ClosedUnexpectedly>> + Send + Sync + 'static + use<>
{
self.reactor_closed_rx
.clone()
.into_future()
.map(|recv| match recv {
Ok(Ok(info)) => Ok(info),
Ok(Err(e)) => Err(ClosedUnexpectedly::ReactorError(e)),
Err(oneshot_broadcast::SenderDropped) => Err(ClosedUnexpectedly::ReactorDropped),
})
}
#[cfg(feature = "circ-padding-manual")]
pub async fn start_padding(self: &Arc<Self>, padder: client::CircuitPadder) -> Result<()> {
self.set_padder_impl(Some(padder)).await
}
#[cfg(feature = "circ-padding-manual")]
pub async fn stop_padding(self: &Arc<Self>) -> Result<()> {
self.set_padder_impl(None).await
}
#[cfg(feature = "circ-padding-manual")]
async fn set_padder_impl(
self: &Arc<Self>,
padder: Option<client::CircuitPadder>,
) -> Result<()> {
let (tx, rx) = oneshot::channel();
let msg = CtrlMsg::SetChannelPadder { padder, sender: tx };
self.control
.unbounded_send(msg)
.map_err(|_| Error::ChannelClosed(ChannelClosed))?;
rx.await.map_err(|_| Error::ChannelClosed(ChannelClosed))?
}
#[cfg(feature = "testing")]
pub fn new_fake(
rt: impl SleepProvider + CoarseTimeProvider,
channel_type: ChannelType,
) -> (Channel, mpsc::UnboundedReceiver<CtrlMsg>) {
let (control, control_recv) = mpsc::unbounded();
let details = fake_channel_details();
let unique_id = UniqId::new();
let peer_id = OwnedChanTarget::builder()
.ed_identity([6_u8; 32].into())
.rsa_identity([10_u8; 20].into())
.build()
.expect("Couldn't construct peer id");
let (_tx, rx) = oneshot_broadcast::channel();
let (padding_ctrl, _) = client::circuit::padding::new_padding(DynTimeProvider::new(rt));
let channel = Channel {
channel_type,
control,
cell_tx: fake_mpsc().0,
reactor_closed_rx: rx,
padding_ctrl,
unique_id,
peer_id,
peer: MaybeSensitive::not_sensitive(PeerInfo::EMPTY),
clock_skew: ClockSkew::None,
opened_at: coarsetime::Instant::now(),
mutable: Default::default(),
details,
canonicity: Canonicity::new_canonical(),
};
(channel, control_recv)
}
}
fn check_id_match_helper<T, U>(my_ident: &T, wanted_ident: &U) -> Result<()>
where
T: HasRelayIds + ?Sized,
U: HasRelayIds + ?Sized,
{
for desired in wanted_ident.identities() {
let id_type = desired.id_type();
match my_ident.identity(id_type) {
Some(actual) if actual == desired => {}
Some(actual) => {
return Err(Error::ChanMismatch(format!(
"Identity {} does not match target {}",
sv(actual),
sv(desired)
)));
}
None => {
return Err(Error::ChanMismatch(format!(
"Peer does not have {} identity",
id_type
)));
}
}
}
Ok(())
}
impl HasRelayIds for Channel {
fn identity(
&self,
key_type: tor_linkspec::RelayIdType,
) -> Option<tor_linkspec::RelayIdRef<'_>> {
self.peer_id.identity(key_type)
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CloseInfo;
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ClosedUnexpectedly {
#[error("channel reactor was dropped or panicked before completing")]
ReactorDropped,
#[error("channel reactor had an internal error")]
ReactorError(Error),
}
#[cfg(any(test, feature = "testing"))]
fn fake_channel_details() -> Arc<ChannelDetails> {
let unused_since = AtomicOptTimestamp::new();
Arc::new(ChannelDetails {
unused_since,
memquota: crate::util::fake_mq(),
})
}
#[cfg(any(test, feature = "testing"))] pub(crate) fn fake_mpsc() -> (CellTx, CellRx) {
let (tx, rx) = crate::fake_mpsc(CHANNEL_BUFFER_SIZE);
#[cfg(feature = "circ-padding")]
let (tx, rx) = counting_streams::channel(tx, rx);
(tx, rx)
}
#[cfg(test)]
pub(crate) mod test {
#![allow(clippy::unwrap_used)]
use super::*;
pub(crate) use crate::channel::reactor::test::{CodecResult, new_reactor};
use tor_cell::chancell::msg::HandshakeType;
use tor_cell::chancell::{AnyChanCell, msg};
use tor_rtcompat::test_with_one_runtime;
pub(crate) fn fake_channel(
rt: impl SleepProvider + CoarseTimeProvider,
channel_type: ChannelType,
) -> Channel {
let unique_id = UniqId::new();
let peer_id = OwnedChanTarget::builder()
.ed_identity([6_u8; 32].into())
.rsa_identity([10_u8; 20].into())
.build()
.expect("Couldn't construct peer id");
let (_tx, rx) = oneshot_broadcast::channel();
let (padding_ctrl, _) = client::circuit::padding::new_padding(DynTimeProvider::new(rt));
Channel {
channel_type,
control: mpsc::unbounded().0,
cell_tx: fake_mpsc().0,
reactor_closed_rx: rx,
padding_ctrl,
unique_id,
peer_id,
peer: MaybeSensitive::not_sensitive(PeerInfo::EMPTY),
clock_skew: ClockSkew::None,
opened_at: coarsetime::Instant::now(),
mutable: Default::default(),
details: fake_channel_details(),
canonicity: Canonicity::new_canonical(),
}
}
#[test]
fn send_bad() {
tor_rtcompat::test_with_all_runtimes!(|rt| async move {
use std::error::Error;
let chan = fake_channel(rt, ChannelType::ClientInitiator);
let cell = AnyChanCell::new(CircId::new(7), msg::Created2::new(&b"hihi"[..]).into());
let e = chan.sender().check_cell(&cell);
assert!(e.is_err());
assert!(
format!("{}", e.unwrap_err().source().unwrap())
.contains("Can't send CREATED2 cell on client channel")
);
let cell = AnyChanCell::new(None, msg::Certs::new_empty().into());
let e = chan.sender().check_cell(&cell);
assert!(e.is_err());
assert!(
format!("{}", e.unwrap_err().source().unwrap())
.contains("Can't send CERTS cell after handshake is done")
);
let cell = AnyChanCell::new(
CircId::new(5),
msg::Create2::new(HandshakeType::NTOR, &b"abc"[..]).into(),
);
let e = chan.sender().check_cell(&cell);
assert!(e.is_ok());
});
}
#[test]
fn check_match() {
test_with_one_runtime!(|rt| async move {
let chan = fake_channel(rt, ChannelType::ClientInitiator);
let t1 = OwnedChanTarget::builder()
.ed_identity([6; 32].into())
.rsa_identity([10; 20].into())
.build()
.unwrap();
let t2 = OwnedChanTarget::builder()
.ed_identity([1; 32].into())
.rsa_identity([3; 20].into())
.build()
.unwrap();
let t3 = OwnedChanTarget::builder()
.ed_identity([3; 32].into())
.rsa_identity([2; 20].into())
.build()
.unwrap();
assert!(chan.check_match(&t1).is_ok());
assert!(chan.check_match(&t2).is_err());
assert!(chan.check_match(&t3).is_err());
});
}
#[test]
fn unique_id() {
test_with_one_runtime!(|rt| async move {
let ch1 = fake_channel(rt.clone(), ChannelType::ClientInitiator);
let ch2 = fake_channel(rt, ChannelType::ClientInitiator);
assert_ne!(ch1.unique_id(), ch2.unique_id());
});
}
#[test]
fn duration_unused_at() {
test_with_one_runtime!(|rt| async move {
let details = fake_channel_details();
let mut ch = fake_channel(rt, ChannelType::ClientInitiator);
ch.details = details.clone();
details.unused_since.update();
assert!(ch.duration_unused().is_some());
});
}
}