use super::channels::{IncomingChannelReceiver, OutgoingChannelSender};
use super::{close, peer_addr, piece_manager_request};
use crate::{
peer_connection::error::{PeerConnectionError, PeerConnectionResult},
peer_explorer::Peer,
piece_manager::{
BLOCK_SIZE,
channel::{PieceEvent, PieceEventReceiver, PieceManagerChannelSender, PieceManagerMessage},
},
status::DownloadStats,
wire_protocol::{Bitfield, Message, WireItem},
};
use std::sync::Arc;
use tokio::{
select,
sync::{broadcast, oneshot},
time,
};
use tracing::{debug, trace, warn};
const WINDOW_TARGET: &str = "request_window";
const SERVE_TARGET: &str = "serve_latency";
struct ActiveBlock {
piece_index: u32,
index: u32,
requested_at: time::Instant,
}
const IDLE_TIMEOUT: time::Duration = time::Duration::from_secs(150);
const KEEP_ALIVE_INTERVAL: time::Duration = time::Duration::from_secs(100);
const REQUEST_TIMEOUT: time::Duration = time::Duration::from_secs(30);
const AVAILABILITY_TICK: time::Duration = time::Duration::from_secs(5);
const MAX_REQUESTS: u32 = 50;
const MAX_PIECES_IN_FLIGHT: usize = 4;
struct PieceHold {
piece_index: u32,
peer: Peer,
released: bool,
piece_manager_channel_sender: PieceManagerChannelSender,
}
impl PieceHold {
fn new(
piece_index: u32,
peer: Peer,
piece_manager_channel_sender: PieceManagerChannelSender,
) -> Self {
Self {
piece_index,
peer,
released: false,
piece_manager_channel_sender,
}
}
async fn release(&mut self) {
if self.released {
return;
}
self.released = true;
if let Err(e) = self
.piece_manager_channel_sender
.send(PieceManagerMessage::Release {
piece_index: self.piece_index,
peer: self.peer,
})
.await
{
debug!("piece manager unreachable while releasing: {}", e);
}
}
fn disarm(&mut self) {
self.released = true;
}
}
impl Drop for PieceHold {
fn drop(&mut self) {
if self.released {
return;
}
if let Err(e) = self
.piece_manager_channel_sender
.try_send(PieceManagerMessage::Release {
piece_index: self.piece_index,
peer: self.peer,
})
{
warn!(
"{}: piece {} stranded, release could not be sent: {}",
self.peer.address, self.piece_index, e
);
return;
}
warn!(
"{}: piece {} released without teardown",
self.peer.address, self.piece_index
);
}
}
pub struct RequestManager<W>
where
W: crate::store::Store + Send + Sync + 'static,
W::Error: std::error::Error + Send + Sync + 'static,
{
pub peer: Option<Peer>,
pub info_hash: [u8; 20],
pub peer_id: [u8; 20],
pub am_choking: bool,
pub am_interested: bool,
pub peer_choking: bool,
pub peer_interested: bool,
pub peer_bitfield: Bitfield,
in_flight: Vec<PieceInFlight>,
active_blocks: Vec<ActiveBlock>,
pub piece_manager_channel_sender: PieceManagerChannelSender,
pub incoming_channel_receiver: IncomingChannelReceiver,
pub outgoing_channel_sender: OutgoingChannelSender,
piece_events: PieceEventReceiver,
stats: Arc<DownloadStats>,
last_sent: time::Instant,
store: Arc<W>,
}
struct PieceBuffer {
piece_index: u32,
hash: [u8; 20],
bytes: Vec<u8>,
hasher: Option<sha1::Sha1>,
next_hashed_block: u32,
have: Vec<bool>,
}
impl PieceBuffer {
fn new(piece_index: u32, hash: [u8; 20], piece_length: u64) -> Self {
Self {
piece_index,
hash,
bytes: vec![0u8; piece_length as usize],
hasher: Some(<sha1::Sha1 as sha1::Digest>::new()),
next_hashed_block: 0,
have: vec![false; piece_length.div_ceil(BLOCK_SIZE) as usize],
}
}
fn is_complete(&self) -> bool {
self.have.iter().all(|had| *had)
}
fn accept(&mut self, block_index: u32, data: &[u8]) -> bool {
let Some(had) = self.have.get_mut(block_index as usize) else {
return false;
};
if *had {
return false;
}
*had = true;
if self.next_hashed_block == block_index {
if let Some(hasher) = self.hasher.as_mut() {
sha1::Digest::update(hasher, data);
}
self.next_hashed_block += 1;
} else {
self.hasher = None;
}
let offset = (block_index as u64 * BLOCK_SIZE) as usize;
if offset < self.bytes.len() {
let end = (offset + data.len()).min(self.bytes.len());
self.bytes[offset..end].copy_from_slice(&data[..end - offset]);
}
true
}
fn digest(&mut self) -> [u8; 20] {
let blocks_total = self.have.len() as u32;
match self.hasher.take() {
Some(hasher) if self.next_hashed_block == blocks_total => {
sha1::Digest::finalize(hasher).into()
}
_ => <sha1::Sha1 as sha1::Digest>::digest(&self.bytes).into(),
}
}
}
struct PieceInFlight {
hold: PieceHold,
buffer: PieceBuffer,
piece_length: u64,
pending: std::collections::VecDeque<u32>,
}
impl PieceInFlight {
fn piece_index(&self) -> u32 {
self.buffer.piece_index
}
fn block_bounds(&self, block_index: u32) -> (u32, u32) {
let begin = block_index as u64 * BLOCK_SIZE;
let length = BLOCK_SIZE.min(self.piece_length.saturating_sub(begin));
(begin as u32, length as u32)
}
}
impl<W> RequestManager<W>
where
W: crate::store::Store + Send + Sync + 'static,
W::Error: std::error::Error + Send + Sync + 'static,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
peer: Option<Peer>,
info_hash: [u8; 20],
peer_id: [u8; 20],
peer_bitfield: Bitfield,
piece_manager_channel_sender: PieceManagerChannelSender,
incoming_channel_receiver: IncomingChannelReceiver,
outgoing_channel_sender: OutgoingChannelSender,
piece_events: PieceEventReceiver,
stats: Arc<DownloadStats>,
store: Arc<W>,
) -> Self {
Self {
peer,
info_hash,
peer_id,
am_choking: true,
am_interested: false,
peer_choking: true,
peer_interested: false,
peer_bitfield,
in_flight: Vec::new(),
active_blocks: Vec::new(),
piece_manager_channel_sender,
incoming_channel_receiver,
outgoing_channel_sender,
piece_events,
stats,
last_sent: time::Instant::now(),
store,
}
}
fn slot_of(&self, piece_index: u32) -> Option<usize> {
self.in_flight
.iter()
.position(|piece| piece.piece_index() == piece_index)
}
pub async fn start(mut self) {
match self.run().await {
Ok(()) | Err(PeerConnectionError::PeerDisconnected) => {
debug!("{}: connection ended", peer_addr(&self.peer));
}
Err(e) => warn!("{}: connection ended: {}", peer_addr(&self.peer), e),
}
self.release_all_pieces().await;
close(&self.peer);
}
async fn run(&mut self) -> PeerConnectionResult<()> {
let mut idle_deadline = time::Instant::now() + IDLE_TIMEOUT;
let mut request_deadline: Option<time::Instant> = None;
let mut availability_tick = time::interval(AVAILABILITY_TICK);
debug!("{}: request loop started", peer_addr(&self.peer));
self.update_interest().await?;
loop {
select! {
_ = time::sleep_until(idle_deadline) => {
debug!("{}: silent for {:?}", peer_addr(&self.peer), IDLE_TIMEOUT);
return Err(PeerConnectionError::PeerDisconnected);
},
_ = async {
match request_deadline {
Some(deadline) => time::sleep_until(deadline).await,
None => std::future::pending().await,
}
} => {
warn!("{}: requests timed out", peer_addr(&self.peer));
self.release_all_pieces().await;
request_deadline = None;
},
_ = availability_tick.tick() => {
self.availability_tick().await?;
},
_ = time::sleep_until(self.last_sent + KEEP_ALIVE_INTERVAL) => {
debug!("{}: keep-alive", peer_addr(&self.peer));
self.send_message(Message::KeepAlive).await?;
},
event = self.piece_events.recv() => {
match event {
Ok(event) => self.handle_piece_event(event).await?,
Err(broadcast::error::RecvError::Lagged(missed)) => {
warn!(
"{}: missed {} piece event(s)",
peer_addr(&self.peer),
missed
);
}
Err(broadcast::error::RecvError::Closed) => {
return Err(PeerConnectionError::PeerDisconnected);
}
}
},
item = self.incoming_channel_receiver.recv() => {
let Some(item) = item else {
return Err(PeerConnectionError::PeerDisconnected);
};
let carried_block =
matches!(item, WireItem::Message(Message::Piece { .. }));
self.handle_incoming_message(item).await?;
idle_deadline = time::Instant::now() + IDLE_TIMEOUT;
if self.active_blocks.is_empty() {
request_deadline = None;
} else if carried_block || request_deadline.is_none() {
request_deadline = Some(time::Instant::now() + REQUEST_TIMEOUT);
}
},
}
}
}
async fn handle_incoming_message(&mut self, item: WireItem) -> PeerConnectionResult<()> {
match item {
WireItem::Message(Message::Choke) => {
debug!(
"{}: choked us, {} request(s) dropped",
peer_addr(&self.peer),
self.active_blocks.len()
);
self.peer_choking = true;
self.release_all_pieces().await;
}
WireItem::Message(Message::Unchoke) => {
debug!("{}: unchoked us", peer_addr(&self.peer));
self.peer_choking = false;
self.fill_pipeline().await?;
}
WireItem::Message(Message::Interested) => {
debug!("{}: interested in us", peer_addr(&self.peer));
self.peer_interested = true;
if self.am_choking {
debug!("{}: unchoking", peer_addr(&self.peer));
self.am_choking = false;
self.send_message(Message::Unchoke).await?;
}
}
WireItem::Message(Message::NotInterested) => {
debug!("{}: no longer interested in us", peer_addr(&self.peer));
self.peer_interested = false;
}
WireItem::Message(Message::Have(index)) => {
if !self.mark_peer_has(index) {
warn!(
"{}: have {} is outside the bitfield",
peer_addr(&self.peer),
index
);
return Ok(());
}
self.update_interest().await?;
self.fill_pipeline().await?;
}
WireItem::Message(Message::Bitfield(_)) => {
warn!("{}: sent a second bitfield", peer_addr(&self.peer));
return Err(PeerConnectionError::UnexpectedMessage);
}
WireItem::Message(Message::Request {
index,
begin,
length,
}) => {
self.serve_block(index, begin, length).await?;
}
WireItem::Message(Message::Piece {
index,
begin,
block,
}) => {
self.receive_block(index, begin, block).await?;
}
WireItem::Message(Message::Cancel {
index,
begin,
length,
}) => {
debug!(
"{}: cancelled its request for piece {} at {} ({} bytes), already served",
peer_addr(&self.peer),
index,
begin,
length
);
}
WireItem::Message(Message::KeepAlive) => {}
WireItem::Message(Message::Port(_port)) => {}
_ => {}
}
Ok(())
}
async fn handle_piece_event(&mut self, event: PieceEvent) -> PeerConnectionResult<()> {
match event {
PieceEvent::PieceComplete { piece_index } => {
self.send_message(Message::Have(piece_index)).await?;
self.update_interest().await?;
}
}
Ok(())
}
fn mark_peer_has(&mut self, index: u32) -> bool {
if (index / 8) as usize >= self.peer_bitfield.0.len() {
return false;
}
self.peer_bitfield.set_piece(index, true);
true
}
async fn update_interest(&mut self) -> PeerConnectionResult<()> {
let bitfield = self.peer_bitfield.clone();
let interested = self
.ask(|response_sender| PieceManagerMessage::IsInteresting {
bitfield,
response_sender,
})
.await?;
if interested == self.am_interested {
return Ok(());
}
self.am_interested = interested;
debug!(
"{}: we are now {}",
peer_addr(&self.peer),
if interested {
"interested"
} else {
"not interested"
}
);
self.send_message(if interested {
Message::Interested
} else {
Message::NotInterested
})
.await
}
async fn fill_pipeline(&mut self) -> PeerConnectionResult<()> {
if self.peer_choking || !self.am_interested {
return Ok(());
}
let mut sent = 0;
while self.active_blocks.len() < MAX_REQUESTS as usize {
let next = self
.in_flight
.iter_mut()
.enumerate()
.find_map(|(slot, piece)| piece.pending.pop_front().map(|block| (slot, block)));
let (slot, block_index) = match next {
Some(found) => found,
None => {
if self.in_flight.len() >= MAX_PIECES_IN_FLIGHT
|| !self.claim_another_piece().await?
{
break;
}
continue;
}
};
let piece_index = self.in_flight[slot].piece_index();
let (begin, length) = self.in_flight[slot].block_bounds(block_index);
self.send_message(Message::Request {
index: piece_index,
begin,
length,
})
.await?;
self.active_blocks.push(ActiveBlock {
piece_index,
index: block_index,
requested_at: time::Instant::now(),
});
sent += 1;
}
if sent > 0 {
debug!(
"{}: requested {} block(s) across {} piece(s), {} in flight",
peer_addr(&self.peer),
sent,
self.in_flight.len(),
self.active_blocks.len()
);
trace!(
target: WINDOW_TARGET,
peer = %peer_addr(&self.peer),
depth = self.active_blocks.len(),
pieces = self.in_flight.len(),
granted = sent,
);
}
Ok(())
}
async fn claim_another_piece(&mut self) -> PeerConnectionResult<bool> {
let bitfield = self.peer_bitfield.clone();
let peer = self.peer.unwrap();
let Some(claim) = self
.ask(|response_sender| PieceManagerMessage::ClaimPiece {
bitfield,
peer,
response_sender,
})
.await?
else {
return Ok(false);
};
debug!(
"{}: claimed piece {} ({} bytes)",
peer_addr(&self.peer),
claim.piece_index,
claim.piece_length
);
self.in_flight.push(PieceInFlight {
hold: PieceHold::new(
claim.piece_index,
peer,
self.piece_manager_channel_sender.clone(),
),
buffer: PieceBuffer::new(claim.piece_index, claim.hash, claim.piece_length),
piece_length: claim.piece_length,
pending: (0..claim.piece_length.div_ceil(BLOCK_SIZE) as u32).collect(),
});
Ok(true)
}
async fn release_all_pieces(&mut self) {
let held: Vec<u32> = self.in_flight.iter().map(|p| p.piece_index()).collect();
if !held.is_empty() {
debug!(
"{}: releasing piece(s) {:?} with {} request(s) outstanding",
peer_addr(&self.peer),
held,
self.active_blocks.len()
);
}
self.active_blocks.clear();
for mut piece in std::mem::take(&mut self.in_flight) {
piece.hold.release().await;
}
}
async fn receive_block(
&mut self,
index: u32,
begin: u32,
block: Vec<u8>,
) -> PeerConnectionResult<()> {
let Some(slot) = self.slot_of(index) else {
debug!(
"{}: block for piece {}, which we are not working",
peer_addr(&self.peer),
index
);
self.stats.add_wasted(block.len() as u64);
return Ok(());
};
if !(begin as u64).is_multiple_of(BLOCK_SIZE) {
debug!(
"{}: block {}+{} is not on a block boundary",
peer_addr(&self.peer),
index,
begin
);
self.stats.add_wasted(block.len() as u64);
return Ok(());
}
let piece_index = index;
let block_index = (begin as u64 / BLOCK_SIZE) as u32;
let Some(position) = self
.active_blocks
.iter()
.position(|active| active.piece_index == piece_index && active.index == block_index)
else {
debug!(
"{}: block {} of piece {} was not requested",
peer_addr(&self.peer),
block_index,
piece_index
);
self.stats.add_wasted(block.len() as u64);
return Ok(());
};
let active = self.active_blocks.swap_remove(position);
trace!(
target: WINDOW_TARGET,
peer = %peer_addr(&self.peer),
depth = self.active_blocks.len(),
piece = piece_index,
block = block_index,
latency_us = active.requested_at.elapsed().as_micros() as u64,
mailbox = self.piece_manager_channel_sender.max_capacity()
- self.piece_manager_channel_sender.capacity(),
);
self.stats.add_downloaded(block.len() as u64);
let accepted = self.in_flight[slot].buffer.accept(block_index, &block);
if !accepted {
self.stats.add_wasted(block.len() as u64);
return self.fill_pipeline().await;
}
drop(block);
if self.in_flight[slot].buffer.is_complete() {
self.complete_piece(slot).await?;
}
self.fill_pipeline().await
}
async fn complete_piece(&mut self, slot: usize) -> PeerConnectionResult<()> {
let mut piece = self.in_flight.swap_remove(slot);
let peer = self.peer.unwrap();
let piece_index = piece.piece_index();
let expected = piece.buffer.hash;
let digest = piece.buffer.digest();
self.active_blocks
.retain(|active| active.piece_index != piece_index);
piece.hold.disarm();
if digest != expected {
self.send_to_piece_manager(PieceManagerMessage::PieceFailed { piece_index, peer })
.await;
return Ok(());
}
if let Err(e) = self.store.write(piece_index, 0, piece.buffer.bytes).await {
warn!(
"{}: piece {} could not be written: {}",
peer_addr(&self.peer),
piece_index,
e
);
self.send_to_piece_manager(PieceManagerMessage::PieceFailed { piece_index, peer })
.await;
return Ok(());
}
if let Err(e) = self.store.record_piece(piece_index).await {
warn!(
"{}: piece {} written but not claimed on disk: {}",
peer_addr(&self.peer),
piece_index,
e
);
}
self.send_to_piece_manager(PieceManagerMessage::PieceVerified { piece_index, peer })
.await;
Ok(())
}
async fn serve_block(
&mut self,
index: u32,
begin: u32,
length: u32,
) -> PeerConnectionResult<()> {
if self.am_choking {
return Ok(());
}
if !(begin as u64).is_multiple_of(BLOCK_SIZE) || length as u64 > BLOCK_SIZE {
debug!(
"{}: unserviceable request for piece {} at {} ({} bytes)",
peer_addr(&self.peer),
index,
begin,
length
);
return Ok(());
}
let has_piece = self
.ask(|response_sender| PieceManagerMessage::HasPiece {
piece_index: index,
response_sender,
})
.await?;
if !has_piece {
debug!(
"{}: asked for piece {}, which we do not have",
peer_addr(&self.peer),
index
);
return Ok(());
}
let block_index = (begin as u64 / BLOCK_SIZE) as u32;
let read_started = time::Instant::now();
let block = self
.store
.read(index, begin as u64, length as u64)
.await
.unwrap_or_default();
trace!(
target: SERVE_TARGET,
peer = %peer_addr(&self.peer),
piece = index,
block = block_index,
read_us = read_started.elapsed().as_micros() as u64,
empty = block.is_empty(),
);
if block.is_empty() {
debug!(
"{}: storage returned nothing for piece {} block {}",
peer_addr(&self.peer),
index,
block_index
);
return Ok(());
}
self.stats.add_uploaded(block.len() as u64);
debug!(
"{}: serving block {} of piece {} ({} bytes)",
peer_addr(&self.peer),
block_index,
index,
length
);
self.send_message(Message::Piece {
index,
begin,
block,
})
.await
}
async fn send_to_piece_manager(&self, message: PieceManagerMessage) {
if let Err(e) = self.piece_manager_channel_sender.send(message).await {
debug!(
"{}: piece manager unreachable: {}",
peer_addr(&self.peer),
e
);
}
}
async fn availability_tick(&mut self) -> PeerConnectionResult<()> {
if self.peer_choking || self.active_blocks.len() >= MAX_REQUESTS as usize {
return Ok(());
}
self.update_interest().await?;
self.fill_pipeline().await
}
async fn send_message(&mut self, message: Message) -> PeerConnectionResult<()> {
self.outgoing_channel_sender
.send(WireItem::Message(message))
.await
.map_err(|_| PeerConnectionError::PeerDisconnected)?;
self.last_sent = time::Instant::now();
Ok(())
}
async fn ask<T>(
&mut self,
build: impl FnOnce(oneshot::Sender<T>) -> PieceManagerMessage,
) -> PeerConnectionResult<T> {
piece_manager_request(&mut self.piece_manager_channel_sender, build).await
}
}