//! Type and code for handling a "half-closed" stream. //! //! A half-closed stream is one that we've sent an END on, but where //! we might still receive some cells. use crate::circuit::sendme::{StreamRecvWindow, StreamSendWindow}; use crate::{Error, Result}; use tor_cell::relaycell::msg::RelayMsg; use tor_error::internal; /// Type to track state of half-closed streams. /// /// A half-closed stream is one where we've sent an END cell, but where /// the other side might still send us data. /// /// We need to track these streams instead of forgetting about them entirely, /// since otherwise we'd be vulnerable to a class of "DropMark" attacks; /// see . pub(super) struct HalfStream { /// Send window for this stream. Used to detect whether we get too many /// SENDME cells. sendw: StreamSendWindow, /// Receive window for this stream. Used to detect whether we get too /// many data cells. recvw: StreamRecvWindow, /// If true, accept a connected cell on this stream. connected_ok: bool, } impl HalfStream { /// Create a new half-closed stream. pub(super) fn new( sendw: StreamSendWindow, recvw: StreamRecvWindow, connected_ok: bool, ) -> Self { HalfStream { sendw, recvw, connected_ok, } } /// Process an incoming message and adjust this HalfStream accordingly. /// Give an error if the protocol has been violated. /// /// The caller must handle END cells; it is an internal error to pass /// END cells to this method. /// no ends here. pub(super) fn handle_msg(&mut self, msg: &RelayMsg) -> Result<()> { match msg { RelayMsg::Sendme(_) => { self.sendw.put(Some(()))?; Ok(()) } RelayMsg::Data(_) => { self.recvw.take()?; Ok(()) } RelayMsg::Connected(_) => { if self.connected_ok { self.connected_ok = false; Ok(()) } else { Err(Error::CircProto( "Bad CONNECTED cell on a closed stream!".into(), )) } } RelayMsg::End(_) => Err(Error::from(internal!( "END cell in HalfStream::handle_msg()" ))), _ => Err(Error::CircProto(format!( "Bad {} cell on a closed stream!", msg.cmd() ))), } } } #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] use super::*; use crate::circuit::sendme::{StreamRecvWindow, StreamSendWindow}; use tor_cell::relaycell::msg; #[test] fn halfstream_sendme() -> Result<()> { let mut sendw = StreamSendWindow::new(101); sendw.take(&())?; // Make sure that it will accept one sendme. let mut hs = HalfStream::new(sendw, StreamRecvWindow::new(20), true); // one sendme is fine let m = msg::Sendme::new_empty().into(); assert!(hs.handle_msg(&m).is_ok()); // but no more were expected! let e = hs.handle_msg(&m).err().unwrap(); assert_eq!( format!("{}", e), "circuit protocol violation: Received a SENDME when none was expected" ); Ok(()) } fn hs_new() -> HalfStream { HalfStream::new(StreamSendWindow::new(20), StreamRecvWindow::new(20), true) } #[test] fn halfstream_data() { let mut hs = hs_new(); // 20 data cells are okay. let m = msg::Data::new(&b"this offer is unrepeatable"[..]) .unwrap() .into(); for _ in 0_u8..20 { assert!(hs.handle_msg(&m).is_ok()); } // But one more is a protocol violation. let e = hs.handle_msg(&m).err().unwrap(); assert_eq!( format!("{}", e), "circuit protocol violation: Received a data cell in violation of a window" ); } #[test] fn halfstream_connected() { let mut hs = hs_new(); // We were told to accept a connected, so we'll accept one // and no more. let m = msg::Connected::new_empty().into(); assert!(hs.handle_msg(&m).is_ok()); assert!(hs.handle_msg(&m).is_err()); // If we try that again with connected_ok == false, we won't // accept any. let mut hs = HalfStream::new(StreamSendWindow::new(20), StreamRecvWindow::new(20), false); let e = hs.handle_msg(&m).err().unwrap(); assert_eq!( format!("{}", e), "circuit protocol violation: Bad CONNECTED cell on a closed stream!" ); } #[test] fn halfstream_other() { let mut hs = hs_new(); let m = msg::Extended2::new(Vec::new()).into(); let e = hs.handle_msg(&m).err().unwrap(); assert_eq!( format!("{}", e), "circuit protocol violation: Bad EXTENDED2 cell on a closed stream!" ); } }