Lines
90.32 %
Functions
84.62 %
Branches
100 %
//! 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 <https://gitlab.torproject.org/tpo/core/tor/-/issues/25573>.
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(
) -> 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()?;
RelayMsg::Connected(_) => {
if self.connected_ok {
self.connected_ok = false;
} 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 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"
);
fn hs_new() -> HalfStream {
HalfStream::new(StreamSendWindow::new(20), StreamRecvWindow::new(20), true)
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 {
// But one more is a protocol violation.
"circuit protocol violation: Received a data cell in violation of a window"
fn halfstream_connected() {
// 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_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);
"circuit protocol violation: Bad CONNECTED cell on a closed stream!"
fn halfstream_other() {
let m = msg::Extended2::new(Vec::new()).into();
"circuit protocol violation: Bad EXTENDED2 cell on a closed stream!"