use anyhow::{Context, Result};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tracing::{debug, trace};
use super::circuit::StreamId;
#[derive(Debug)]
pub enum StreamState {
Connecting,
Active {
reader: Arc<Mutex<tokio::net::tcp::OwnedReadHalf>>,
writer: Arc<Mutex<tokio::net::tcp::OwnedWriteHalf>>,
bytes_sent: u64,
bytes_received: u64,
package_window: u16,
deliver_window: u16,
data_cells_received: u16,
},
Directory {
request_data: Vec<u8>,
package_window: u16,
deliver_window: u16,
data_cells_received: u16,
},
Closing,
Closed,
}
pub const STREAM_WINDOW_START: u16 = 500;
pub const STREAM_WINDOW_INCREMENT: u16 = 50;
pub const MAX_STREAMS_PER_CIRCUIT: usize = 64;
#[derive(Debug)]
pub struct Stream {
pub id: StreamId,
pub target: String,
pub state: StreamState,
pub created_at: crate::utils::clock::Instant,
}
impl Stream {
pub fn new(id: StreamId, target: String) -> Self {
Self {
id,
target,
state: StreamState::Connecting,
created_at: crate::utils::clock::Instant::now(),
}
}
pub fn set_active(&mut self, connection: TcpStream) {
let (reader, writer) = connection.into_split();
self.state = StreamState::Active {
reader: Arc::new(Mutex::new(reader)),
writer: Arc::new(Mutex::new(writer)),
bytes_sent: 0,
bytes_received: 0,
package_window: STREAM_WINDOW_START,
deliver_window: STREAM_WINDOW_START,
data_cells_received: 0,
};
}
pub fn set_directory(&mut self) {
self.state = StreamState::Directory {
request_data: Vec::new(),
package_window: STREAM_WINDOW_START,
deliver_window: STREAM_WINDOW_START,
data_cells_received: 0,
};
}
pub fn is_active(&self) -> bool {
matches!(self.state, StreamState::Active { .. })
}
pub fn is_directory(&self) -> bool {
matches!(self.state, StreamState::Directory { .. })
}
pub fn writer(&self) -> Option<Arc<Mutex<tokio::net::tcp::OwnedWriteHalf>>> {
match &self.state {
StreamState::Active { writer, .. } => Some(writer.clone()),
_ => None,
}
}
pub fn reader(&self) -> Option<Arc<Mutex<tokio::net::tcp::OwnedReadHalf>>> {
match &self.state {
StreamState::Active { reader, .. } => Some(reader.clone()),
_ => None,
}
}
pub fn record_sent(&mut self, bytes: u64) {
if let StreamState::Active { bytes_sent, .. } = &mut self.state {
*bytes_sent += bytes;
}
}
pub fn record_received(&mut self, bytes: u64) {
if let StreamState::Active { bytes_received, .. } = &mut self.state {
*bytes_received += bytes;
}
}
pub fn close(&mut self) {
self.state = StreamState::Closed;
}
pub fn record_data_received(&mut self) -> bool {
match &mut self.state {
StreamState::Active {
deliver_window,
data_cells_received,
..
}
| StreamState::Directory {
deliver_window,
data_cells_received,
..
} => {
*deliver_window = deliver_window.saturating_sub(1);
*data_cells_received += 1;
if *data_cells_received >= STREAM_WINDOW_INCREMENT {
*data_cells_received = 0;
*deliver_window += STREAM_WINDOW_INCREMENT;
return true;
}
}
_ => {}
}
false
}
pub fn process_sendme(&mut self) {
match &mut self.state {
StreamState::Active { package_window, .. }
| StreamState::Directory { package_window, .. } => {
*package_window += STREAM_WINDOW_INCREMENT;
trace!(
"Stream {} package window increased to {}",
self.id.as_u16(),
package_window
);
}
_ => {}
}
}
pub fn can_send_data(&self) -> bool {
match &self.state {
StreamState::Active { package_window, .. }
| StreamState::Directory { package_window, .. } => *package_window > 0,
_ => false,
}
}
pub fn consume_package_window(&mut self) {
match &mut self.state {
StreamState::Active { package_window, .. }
| StreamState::Directory { package_window, .. } => {
*package_window = package_window.saturating_sub(1);
}
_ => {}
}
}
pub fn append_request_data(&mut self, data: &[u8]) {
if let StreamState::Directory { request_data, .. } = &mut self.state {
request_data.extend_from_slice(data);
}
}
pub fn get_request_data(&self) -> Option<&[u8]> {
if let StreamState::Directory { request_data, .. } = &self.state {
Some(request_data)
} else {
None
}
}
}
#[derive(Debug)]
pub struct StreamManager {
streams: HashMap<StreamId, Stream>,
next_stream_id: u16,
}
impl StreamManager {
pub fn new() -> Self {
Self {
streams: HashMap::new(),
next_stream_id: 1,
}
}
pub fn allocate_stream_id(&mut self) -> StreamId {
let id = StreamId::new(self.next_stream_id);
self.next_stream_id = self.next_stream_id.wrapping_add(1);
if self.next_stream_id == 0 {
self.next_stream_id = 1; }
id
}
pub fn create_stream(&mut self, id: StreamId, target: String) -> Result<()> {
if self.streams.contains_key(&id) {
return Err(anyhow::anyhow!("Stream ID already exists"));
}
if self.streams.len() >= MAX_STREAMS_PER_CIRCUIT {
return Err(anyhow::anyhow!(
"circuit already holds {} streams, the per-circuit limit",
MAX_STREAMS_PER_CIRCUIT
));
}
let stream = Stream::new(id, target);
self.streams.insert(id, stream);
debug!("Created stream {} in circuit", id.as_u16());
Ok(())
}
pub fn get_mut(&mut self, id: StreamId) -> Option<&mut Stream> {
self.streams.get_mut(&id)
}
pub fn get(&self, id: StreamId) -> Option<&Stream> {
self.streams.get(&id)
}
pub fn remove(&mut self, id: StreamId) -> Option<Stream> {
self.streams.remove(&id)
}
pub fn active_streams(&self) -> Vec<StreamId> {
self.streams
.iter()
.filter(|(_, s)| s.is_active())
.map(|(id, _)| *id)
.collect()
}
pub fn count(&self) -> usize {
self.streams.len()
}
}
pub fn parse_begin_target(data: &[u8]) -> Result<String> {
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
let target_str = std::str::from_utf8(&data[..end]).context("Invalid UTF-8 in BEGIN target")?;
if !target_str.contains(':') {
return Err(anyhow::anyhow!(
"Invalid BEGIN target format: {}",
target_str
));
}
Ok(target_str.to_string())
}
pub const TARGET_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
pub async fn connect_to_target(target: &str) -> Result<TcpStream> {
debug!("Connecting to target: {}", target);
let connect = async {
if let Ok(addr) = target.parse::<SocketAddr>() {
TcpStream::connect(addr).await
} else {
TcpStream::connect(target).await
}
};
let stream = tokio::time::timeout(TARGET_CONNECT_TIMEOUT, connect)
.await
.map_err(|_| {
anyhow::anyhow!(
"timed out after {}s connecting to {}",
TARGET_CONNECT_TIMEOUT.as_secs(),
target
)
})??;
debug!("Connected to {}", target);
Ok(stream)
}
pub fn build_relay_cell(circuit_id: u32, stream_id: u16, command: u8, data: &[u8]) -> Vec<u8> {
let mut cell = Vec::with_capacity(514);
cell.extend_from_slice(&circuit_id.to_be_bytes());
cell.push(3);
cell.push(command); cell.extend_from_slice(&0u16.to_be_bytes()); cell.extend_from_slice(&stream_id.to_be_bytes()); cell.extend_from_slice(&[0u8; 4]); cell.extend_from_slice(&(data.len() as u16).to_be_bytes());
cell.extend_from_slice(data);
cell.resize(514, 0);
cell
}
pub mod relay_command {
pub const BEGIN: u8 = 1;
pub const DATA: u8 = 2;
pub const END: u8 = 3;
pub const CONNECTED: u8 = 4;
pub const SENDME: u8 = 5;
pub const EXTEND: u8 = 6;
pub const EXTENDED: u8 = 7;
pub const TRUNCATE: u8 = 8;
pub const TRUNCATED: u8 = 9;
pub const DROP: u8 = 10;
pub const RESOLVE: u8 = 11;
pub const RESOLVED: u8 = 12;
pub const BEGIN_DIR: u8 = 13;
pub const EXTEND2: u8 = 14;
pub const EXTENDED2: u8 = 15;
}
pub mod end_reason {
pub const MISC: u8 = 1;
pub const RESOLVE_FAILED: u8 = 2;
pub const CONNECT_REFUSED: u8 = 3;
pub const EXIT_POLICY: u8 = 4;
pub const DESTROY: u8 = 5;
pub const DONE: u8 = 6;
pub const TIMEOUT: u8 = 7;
pub const NO_ROUTE: u8 = 8;
pub const HIBERNATING: u8 = 9;
pub const INTERNAL: u8 = 10;
pub const RESOURCE_LIMIT: u8 = 11;
pub const CONNECT_RESET: u8 = 12;
pub const TOR_PROTOCOL: u8 = 13;
}