Compare commits
2 Commits
478ce597a7
...
a16aea2d11
Author | SHA1 | Date | |
---|---|---|---|
a16aea2d11 | |||
636ba8d7f5 |
@ -8,6 +8,14 @@ pub struct InfoHash {
|
|||||||
pub bytes: [u8; INFO_HASH_SIZE],
|
pub bytes: [u8; INFO_HASH_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InfoHash {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Option<InfoHash> {
|
||||||
|
Some(InfoHash {
|
||||||
|
bytes: buf.try_into().ok()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for InfoHash {
|
impl fmt::Display for InfoHash {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
const INFO_HASH_STR_LEN: usize = INFO_HASH_SIZE * 2;
|
const INFO_HASH_STR_LEN: usize = INFO_HASH_SIZE * 2;
|
||||||
|
@ -8,6 +8,12 @@ pub struct PeerId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PeerId {
|
impl PeerId {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Option<PeerId> {
|
||||||
|
Some(PeerId {
|
||||||
|
bytes: buf.try_into().ok()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_string_lossy(&self) -> String {
|
pub fn to_string_lossy(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.bytes).into()
|
String::from_utf8_lossy(&self.bytes).into()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Cursor, Write},
|
io::{Cursor, Write},
|
||||||
net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
|
net::{Ipv4Addr, SocketAddrV4, SocketAddrV6},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::bittorrent::{
|
use crate::bittorrent::{
|
||||||
@ -9,18 +9,9 @@ use crate::bittorrent::{
|
|||||||
protocol::{Action, Event},
|
protocol::{Action, Event},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CONNECT_REQUEST_SIZE: usize = 16;
|
||||||
pub const MIN_ANNOUNCE_REQUEST_SIZE: usize = 98;
|
pub const MIN_ANNOUNCE_REQUEST_SIZE: usize = 98;
|
||||||
pub const MIN_SCRAPE_REQUEST_SIZE: usize = 36;
|
pub const MIN_SCRAPE_REQUEST_SIZE: usize = 36;
|
||||||
pub const MIN_CONNECTION_RESPONSE_SIZE: usize = 16;
|
|
||||||
pub const MIN_ANNOUNCE_RESPONSE_SIZE: usize = 20;
|
|
||||||
pub const MIN_SCRAPE_RESPONSE_SIZE: usize = 8;
|
|
||||||
pub const SCRAPE_RESPONSE_ENTRY_SIZE: usize = 12;
|
|
||||||
|
|
||||||
pub const IPV4_SIZE: usize = Ipv4Addr::BITS as usize / 8;
|
|
||||||
pub const IPV6_SIZE: usize = Ipv6Addr::BITS as usize / 8;
|
|
||||||
pub const PORT_SIZE: usize = size_of::<u16>();
|
|
||||||
pub const IPV4_ADDR_PAIR_SIZE: usize = IPV4_SIZE + PORT_SIZE;
|
|
||||||
pub const IPV6_ADDR_PAIR_SIZE: usize = IPV6_SIZE + PORT_SIZE;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectRequest {
|
pub struct ConnectRequest {
|
||||||
@ -28,6 +19,19 @@ pub struct ConnectRequest {
|
|||||||
pub transaction_id: i32,
|
pub transaction_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConnectRequest {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Option<ConnectRequest> {
|
||||||
|
if buf.len() != CONNECT_REQUEST_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ConnectRequest {
|
||||||
|
protocol_id: i64::from_be_bytes(buf[0..8].try_into().ok()?),
|
||||||
|
transaction_id: i32::from_be_bytes(buf[12..16].try_into().ok()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectResponse {
|
pub struct ConnectResponse {
|
||||||
pub transaction_id: i32,
|
pub transaction_id: i32,
|
||||||
@ -75,6 +79,38 @@ pub struct AnnounceRequest {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AnnounceRequest {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Option<AnnounceRequest> {
|
||||||
|
if buf.len() < MIN_ANNOUNCE_REQUEST_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(AnnounceRequest {
|
||||||
|
connection_id: i64::from_be_bytes(buf[0..8].try_into().ok()?),
|
||||||
|
transaction_id: i32::from_be_bytes(buf[12..16].try_into().ok()?),
|
||||||
|
info_hash: InfoHash::from_bytes(buf[16..36].try_into().ok()?)?,
|
||||||
|
peer_id: PeerId::from_bytes(buf[36..56].try_into().ok()?)?,
|
||||||
|
downloaded: i64::from_be_bytes(buf[56..64].try_into().ok()?),
|
||||||
|
left: i64::from_be_bytes(buf[64..72].try_into().ok()?),
|
||||||
|
uploaded: i64::from_be_bytes(buf[72..80].try_into().ok()?),
|
||||||
|
event: Event::from_i32(i32::from_be_bytes(buf[80..84].try_into().ok()?))?,
|
||||||
|
ipv4_address: if buf[84..88].iter().all(|b| *b == 0x00) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Ipv4Addr::from_bits(u32::from_be_bytes(
|
||||||
|
buf[84..88].try_into().ok()?,
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
key: u32::from_be_bytes(buf[88..92].try_into().ok()?),
|
||||||
|
num_want: {
|
||||||
|
let want = i32::from_be_bytes(buf[92..96].try_into().ok()?);
|
||||||
|
if want < 0 { None } else { Some(want as u32) }
|
||||||
|
},
|
||||||
|
port: u16::from_be_bytes(buf[96..98].try_into().ok()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AnnounceResponse {
|
pub enum AnnounceResponse {
|
||||||
V4(AnnounceResponseV4),
|
V4(AnnounceResponseV4),
|
||||||
@ -106,48 +142,48 @@ pub struct AnnounceResponseV6 {
|
|||||||
|
|
||||||
impl AnnounceResponseCommon {
|
impl AnnounceResponseCommon {
|
||||||
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
||||||
const ACTION: i32 = Action::Announce as i32;
|
let mut c = Cursor::new(buf);
|
||||||
buf[0..4].copy_from_slice(&ACTION.to_be_bytes());
|
let mut written: usize = 0;
|
||||||
buf[4..8].copy_from_slice(&self.transaction_id.to_be_bytes());
|
|
||||||
buf[8..12].copy_from_slice(&self.interval.to_be_bytes());
|
|
||||||
buf[12..16].copy_from_slice(&self.leechers.to_be_bytes());
|
|
||||||
buf[16..20].copy_from_slice(&self.seeders.to_be_bytes());
|
|
||||||
|
|
||||||
return MIN_ANNOUNCE_RESPONSE_SIZE;
|
const ACTION: i32 = Action::Announce as i32;
|
||||||
|
|
||||||
|
written += c.write(&ACTION.to_be_bytes()).unwrap();
|
||||||
|
written += c.write(&self.transaction_id.to_be_bytes()).unwrap();
|
||||||
|
written += c.write(&self.interval.to_be_bytes()).unwrap();
|
||||||
|
written += c.write(&self.leechers.to_be_bytes()).unwrap();
|
||||||
|
written += c.write(&self.seeders.to_be_bytes()).unwrap();
|
||||||
|
|
||||||
|
return written;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnounceResponseV4 {
|
impl AnnounceResponseV4 {
|
||||||
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
||||||
let written = self.common.write_to_buf(buf);
|
let mut written = self.common.write_to_buf(buf);
|
||||||
|
let mut c = Cursor::new(buf);
|
||||||
|
c.set_position(written as u64);
|
||||||
|
|
||||||
for (offset, addr) in (written..buf.len())
|
for peer in &self.peers {
|
||||||
.step_by(IPV4_ADDR_PAIR_SIZE)
|
written += c.write(&peer.ip().octets()).unwrap();
|
||||||
.zip(&self.peers)
|
written += c.write(&peer.port().to_be_bytes()).unwrap();
|
||||||
{
|
|
||||||
buf[offset..offset + IPV4_SIZE].copy_from_slice(&addr.ip().octets());
|
|
||||||
buf[offset + IPV4_SIZE..offset + IPV4_ADDR_PAIR_SIZE]
|
|
||||||
.copy_from_slice(&addr.port().to_be_bytes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return written + self.peers.len() * IPV4_ADDR_PAIR_SIZE;
|
return written;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnounceResponseV6 {
|
impl AnnounceResponseV6 {
|
||||||
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
pub fn write_to_buf(&self, buf: &mut [u8]) -> usize {
|
||||||
let written = self.common.write_to_buf(buf);
|
let mut written = self.common.write_to_buf(buf);
|
||||||
|
let mut c = Cursor::new(buf);
|
||||||
|
c.set_position(written as u64);
|
||||||
|
|
||||||
for (offset, addr) in (written..buf.len())
|
for peer in &self.peers {
|
||||||
.step_by(IPV6_ADDR_PAIR_SIZE)
|
written += c.write(&peer.ip().octets()).unwrap();
|
||||||
.zip(&self.peers)
|
written += c.write(&peer.port().to_be_bytes()).unwrap();
|
||||||
{
|
|
||||||
buf[offset..offset + IPV6_SIZE].copy_from_slice(&addr.ip().octets());
|
|
||||||
buf[offset + IPV6_SIZE..offset + IPV6_ADDR_PAIR_SIZE]
|
|
||||||
.copy_from_slice(&addr.port().to_be_bytes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return written + self.peers.len() * IPV6_ADDR_PAIR_SIZE;
|
return written;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +194,25 @@ pub struct ScrapeRequest {
|
|||||||
pub info_hashes: Vec<InfoHash>,
|
pub info_hashes: Vec<InfoHash>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ScrapeRequest {
|
||||||
|
pub fn from_bytes(buf: &[u8]) -> Option<ScrapeRequest> {
|
||||||
|
if buf.len() < MIN_SCRAPE_REQUEST_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ScrapeRequest {
|
||||||
|
connection_id: i64::from_be_bytes(buf[0..8].try_into().ok()?),
|
||||||
|
transaction_id: i32::from_be_bytes(buf[12..16].try_into().ok()?),
|
||||||
|
info_hashes: buf[16..]
|
||||||
|
.chunks_exact(20)
|
||||||
|
.map(|b| InfoHash {
|
||||||
|
bytes: b.try_into().unwrap(), // unwrap: `chunks_exact` guarantees the size
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ScrapeStats {
|
pub struct ScrapeStats {
|
||||||
/// Number of connected peers who have completed the download and are seeding
|
/// Number of connected peers who have completed the download and are seeding
|
||||||
|
@ -131,84 +131,20 @@ fn try_parse_packet(buf: &[u8]) -> Option<UdpRequest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_parse_connect(buf: &[u8]) -> Option<ConnectRequest> {
|
fn try_parse_connect(buf: &[u8]) -> Option<ConnectRequest> {
|
||||||
// Buffer length is checked to be at least 16 in `try_parse_packet`
|
let conn = ConnectRequest::from_bytes(buf)?;
|
||||||
|
|
||||||
let protocol_id: i64 = i64::from_be_bytes(buf[0..8].try_into().ok()?);
|
// Disregard the request immediately if `protocol_id` is not the valid magic number
|
||||||
|
if conn.protocol_id != UDP_MAGIC {
|
||||||
if protocol_id != UDP_MAGIC {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ConnectRequest {
|
Some(conn)
|
||||||
protocol_id: protocol_id,
|
|
||||||
transaction_id: i32::from_be_bytes(buf[12..16].try_into().ok()?),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_parse_announce(buf: &[u8]) -> Option<AnnounceRequest> {
|
fn try_parse_announce(buf: &[u8]) -> Option<AnnounceRequest> {
|
||||||
if buf.len() < MIN_ANNOUNCE_REQUEST_SIZE {
|
AnnounceRequest::from_bytes(buf)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection_id: i64 = i64::from_be_bytes(buf[0..8].try_into().ok()?);
|
|
||||||
let transaction_id: i32 = i32::from_be_bytes(buf[12..16].try_into().ok()?);
|
|
||||||
let info_hash: InfoHash = InfoHash {
|
|
||||||
bytes: buf[16..36].try_into().ok()?,
|
|
||||||
};
|
|
||||||
let peer_id: PeerId = PeerId {
|
|
||||||
bytes: buf[36..56].try_into().unwrap_or_default(),
|
|
||||||
};
|
|
||||||
let downloaded: i64 = i64::from_be_bytes(buf[56..64].try_into().ok()?);
|
|
||||||
let left: i64 = i64::from_be_bytes(buf[64..72].try_into().ok()?);
|
|
||||||
let uploaded: i64 = i64::from_be_bytes(buf[72..80].try_into().ok()?);
|
|
||||||
let event: Event = Event::from_i32(i32::from_be_bytes(buf[80..84].try_into().ok()?))?;
|
|
||||||
let ip_addr: Option<Ipv4Addr> = if buf[84..88].iter().all(|b| *b == 0x00) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Ipv4Addr::from_bits(u32::from_be_bytes(
|
|
||||||
buf[84..88].try_into().ok()?,
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
let key: u32 = u32::from_be_bytes(buf[88..92].try_into().ok()?);
|
|
||||||
let num_want: Option<u32> = {
|
|
||||||
let want = i32::from_be_bytes(buf[92..96].try_into().ok()?);
|
|
||||||
if want < 0 { None } else { Some(want as u32) }
|
|
||||||
};
|
|
||||||
let port: u16 = u16::from_be_bytes(buf[96..98].try_into().ok()?);
|
|
||||||
|
|
||||||
Some(AnnounceRequest {
|
|
||||||
connection_id: connection_id,
|
|
||||||
transaction_id: transaction_id,
|
|
||||||
info_hash: info_hash,
|
|
||||||
peer_id: peer_id,
|
|
||||||
downloaded: downloaded,
|
|
||||||
left: left,
|
|
||||||
uploaded: uploaded,
|
|
||||||
event: event,
|
|
||||||
ipv4_address: ip_addr,
|
|
||||||
key: key,
|
|
||||||
num_want: num_want,
|
|
||||||
port: port,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_parse_scrape(buf: &[u8]) -> Option<ScrapeRequest> {
|
fn try_parse_scrape(buf: &[u8]) -> Option<ScrapeRequest> {
|
||||||
if buf.len() < MIN_SCRAPE_REQUEST_SIZE {
|
ScrapeRequest::from_bytes(buf)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection_id: i64 = i64::from_be_bytes(buf[0..8].try_into().ok()?);
|
|
||||||
let transaction_id: i32 = i32::from_be_bytes(buf[12..16].try_into().ok()?);
|
|
||||||
let info_hashes: Vec<InfoHash> = buf[16..]
|
|
||||||
.chunks_exact(20)
|
|
||||||
.map(|b| InfoHash {
|
|
||||||
bytes: b.try_into().unwrap(), // unwrap: `chunks_exact` guarantees the size
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some(ScrapeRequest {
|
|
||||||
connection_id: connection_id,
|
|
||||||
transaction_id: transaction_id,
|
|
||||||
info_hashes: info_hashes,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user