//! Functionality for incoming streams, opened from the other side of a circuit. #![allow(dead_code, unused_variables, clippy::needless_pass_by_value)] // TODO hss remove use super::{AnyCmdChecker, DataStream, StreamReader, StreamStatus}; use crate::circuit::StreamTarget; use crate::{Error, Result}; use futures::channel::oneshot; use std::result::Result as StdResult; use tor_cell::relaycell::{msg, RelayCmd, UnparsedRelayCell}; use tor_cell::restricted_msg; use tor_error::{internal, Bug}; /// A pending request from the other end of the circuit for us to open a new /// stream. /// /// Exits, directory caches, and onion services expect to receive these; others /// do not. /// /// On receiving one of these objects, the party handling it should accept it or /// reject it. If it is dropped without being explicitly handled, a reject /// message will be sent anyway. #[derive(Debug)] pub struct IncomingStream { /// The message that the client sent us to begin the stream. request: IncomingStreamRequest, /// The inner state, which contains the reader and writer of this stream. /// /// This is an `Option` because we need to be able to "take" the reader/writer of the stream /// out of the `IncomingStream` to construct a [`DataStream`] in [`IncomingStream::accept_data`]. /// /// Note: we can't move the reader/writer out of `self` because `IncomingStream` implements /// `Drop` (so as a workaround we use [`Option::take`]). inner: Option, /// The state of the stream. state: IncomingStreamState, } /// The state of an [`IncomingStream`]. /// /// Only following transitions are allowed: /// /// ```ignore /// /// accept_data() +----------+ /// +--------------->| Accepted | /// | +----------+ /// | /// +---------+ | reject() +----------+ /// | Pending |---------+--------------->| Rejected | /// +---------+ | +----------+ /// | /// | discard() +-----------+ /// +--------------->| Discarded | /// +-----------+ /// ``` #[derive(Copy, Clone, Debug, PartialEq, Default, derive_more::Display)] enum IncomingStreamState { /// The initial state of an [`IncomingStream`]. #[default] Pending, /// The state entered after a call to [`IncomingStream::accept_data`]. Accepted, /// The state entered after a call to [`IncomingStream::reject`]. Rejected, /// The state entered after a call to [`IncomingStream::discard`]. Discarded, } /// The inner state of an [`IncomingStream`], which contains its reader and writer. #[derive(Debug)] struct IncomingStreamInner { /// The information that we'll use to wire up the stream, if it is accepted. stream: StreamTarget, /// The underlying `StreamReader`. reader: StreamReader, } impl IncomingStream { /// Create a new `IncomingStream`. pub(crate) fn new( request: IncomingStreamRequest, stream: StreamTarget, reader: StreamReader, ) -> Self { let inner = IncomingStreamInner { stream, reader }; Self { request, inner: Some(inner), state: IncomingStreamState::default(), } } /// Return the underlying message that was used to try to begin this stream. pub fn request(&self) -> &IncomingStreamRequest { &self.request } /// Whether we have rejected this `IncomingStream` using [`IncomingStream::reject`]. pub fn is_rejected(&self) -> bool { self.state == IncomingStreamState::Rejected } /// Accept this stream as a new [`DataStream`], and send the client a /// message letting them know the stream was accepted. pub async fn accept_data(mut self, message: msg::Connected) -> Result { self.update_state(IncomingStreamState::Accepted, "accept_data")?; let mut inner = self.take_inner()?; match self.request { IncomingStreamRequest::Begin(_) | IncomingStreamRequest::BeginDir(_) => { inner.stream.send(message.into()).await?; Ok(DataStream::new_connected(inner.reader, inner.stream)) } IncomingStreamRequest::Resolve(_) => { Err(internal!("Cannot accept data on a RESOLVE stream").into()) } } } /// Reject this request and send an error message to the client. pub async fn reject(&mut self, message: msg::End) -> Result<()> { let rx = self.reject_inner(message)?; rx.await.map_err(|_| Error::CircuitClosed)?.map(|_| ()) } /// Reject this request and send an error message to the client. /// /// Returns a [`oneshot::Receiver`] that can be used to await the reactor's response. /// /// This is used for implementing `Drop`. fn reject_inner(&mut self, message: msg::End) -> Result>> { self.update_state(IncomingStreamState::Rejected, "reject_inner")?; self.mut_inner()?.stream.close(message) } /// Ignore this request without replying to the client. /// /// (If you drop an [`IncomingStream`] without calling `accept_data`, /// `reject`, or this method, the drop handler will cause it to be /// rejected.) pub fn discard(mut self) -> StdResult<(), Bug> { self.update_state(IncomingStreamState::Discarded, "discard") } /// Try to update the state of this `IncomingStream` to `new_state`, returning an error if the /// requested transition is not allowed. fn update_state(&mut self, new_state: IncomingStreamState, caller: &str) -> StdResult<(), Bug> { use IncomingStreamState::*; match self.state { Pending => { self.state = new_state; Ok(()) } _ => Err(internal!( "IncomingStream::{caller}() cannot be called on a {} stream", self.state )), } } /// Take the inner state out of `IncomingStream`. /// /// Returns an error if `inner` is `None` (this should never happen unless we have a bug in our /// code). fn take_inner(&mut self) -> Result { let _: &mut _ = self.mut_inner()?; Ok(self .inner .take() .expect("inner None though we just checked it")) } /// Return a mutable reference to the inner state of `IncomingStream`. /// /// Returns an error if `inner` is `None` (this should never happen unless we have a bug in our /// code). fn mut_inner(&mut self) -> Result<&mut IncomingStreamInner> { self.inner .as_mut() .ok_or_else(|| internal!("Cannot use a stream that has already been consumed").into()) } } impl Drop for IncomingStream { fn drop(&mut self) { if self.state == IncomingStreamState::Pending { // Disregard any errors. let _: Result>> = self.reject_inner(msg::End::new_misc()); } } } restricted_msg! { /// The allowed incoming messages on an `IncomingStream`. #[derive(Clone, Debug)] #[non_exhaustive] pub enum IncomingStreamRequest: RelayMsg { /// A BEGIN message. Begin, /// A BEGIN_DIR message. BeginDir, /// A RESOLVE message. Resolve, } } /// A `CmdChecker` that enforces correctness for incoming commands on unrecognized streams that /// have a non-zero stream ID. #[derive(Debug)] pub(crate) struct IncomingCmdChecker { /// The "begin" commands that can be received on this type of circuit: /// /// * onion service circuits only accept `BEGIN` /// * all relay circuits accept `BEGIN_DIR` /// * exit relays additionally accept `BEGIN` or `RESOLVE` on relay circuits /// * once CONNECT_UDP is implemented, relays and later onion services may accept CONNECT_UDP /// as well allow_commands: Vec, } impl IncomingCmdChecker { /// Create a new boxed `IncomingCmdChecker`. pub(crate) fn new_any(allow_commands: &[RelayCmd]) -> AnyCmdChecker { // TODO HSS: avoid allocating a vec here Box::new(Self { allow_commands: allow_commands.to_vec(), }) } } impl super::CmdChecker for IncomingCmdChecker { fn check_msg(&mut self, msg: &UnparsedRelayCell) -> Result { match msg.cmd() { cmd if self.allow_commands.contains(&cmd) => Ok(StreamStatus::Open), _ => Err(Error::StreamProto(format!( "Unexpected {} on incoming stream", msg.cmd() ))), } } fn consume_checked_msg(&mut self, msg: UnparsedRelayCell) -> Result<()> { let _ = msg .decode::() .map_err(|err| Error::from_bytes_err(err, "invalid message on incoming stream"))?; Ok(()) } }