use std::io::{self, Write};
use byteorder::{BigEndian, WriteBytesExt};
use nom::{be_u64, be_u32, IResult};
use announce::AnnounceRequest;
use scrape::ScrapeRequest;
pub const CONNECT_ID_PROTOCOL_ID: u64 = 0x41727101980;
pub enum RequestType<'a> {
Connect,
Announce(AnnounceRequest<'a>),
Scrape(ScrapeRequest<'a>),
}
impl<'a> RequestType<'a> {
pub fn to_owned(&self) -> RequestType<'static> {
match self {
&RequestType::Connect => RequestType::Connect,
&RequestType::Announce(ref req) => RequestType::Announce(req.to_owned()),
&RequestType::Scrape(ref req) => RequestType::Scrape(req.to_owned()),
}
}
}
pub struct TrackerRequest<'a> {
connection_id: u64,
transaction_id: u32,
request_type: RequestType<'a>,
}
impl<'a> TrackerRequest<'a> {
pub fn new(conn_id: u64, trans_id: u32, req_type: RequestType<'a>) -> TrackerRequest<'a> {
TrackerRequest {
connection_id: conn_id,
transaction_id: trans_id,
request_type: req_type,
}
}
pub fn from_bytes(bytes: &'a [u8]) -> IResult<&'a [u8], TrackerRequest<'a>> {
parse_request(bytes)
}
pub fn write_bytes<W>(&self, mut writer: W) -> io::Result<()>
where W: Write
{
try!(writer.write_u64::<BigEndian>(self.connection_id()));
match self.request_type() {
&RequestType::Connect => {
try!(writer.write_u32::<BigEndian>(::CONNECT_ACTION_ID));
try!(writer.write_u32::<BigEndian>(self.transaction_id()));
}
&RequestType::Announce(ref req) => {
let action_id = if req.source_ip().is_ipv4() {
::ANNOUNCE_IPV4_ACTION_ID
} else {
::ANNOUNCE_IPV6_ACTION_ID
};
try!(writer.write_u32::<BigEndian>(action_id));
try!(writer.write_u32::<BigEndian>(self.transaction_id()));
try!(req.write_bytes(writer));
}
&RequestType::Scrape(ref req) => {
try!(writer.write_u32::<BigEndian>(::SCRAPE_ACTION_ID));
try!(writer.write_u32::<BigEndian>(self.transaction_id()));
try!(req.write_bytes(writer));
}
};
Ok(())
}
pub fn connection_id(&self) -> u64 {
self.connection_id
}
pub fn transaction_id(&self) -> u32 {
self.transaction_id
}
pub fn request_type(&self) -> &RequestType {
&self.request_type
}
pub fn to_owned(&self) -> TrackerRequest<'static> {
TrackerRequest {
connection_id: self.connection_id,
transaction_id: self.transaction_id,
request_type: self.request_type.to_owned(),
}
}
}
fn parse_request<'a>(bytes: &'a [u8]) -> IResult<&'a [u8], TrackerRequest<'a>> {
switch!(bytes, tuple!(be_u64, be_u32, be_u32),
(CONNECT_ID_PROTOCOL_ID, ::CONNECT_ACTION_ID, tid) => value!(
TrackerRequest::new(CONNECT_ID_PROTOCOL_ID, tid, RequestType::Connect)
) |
(cid, ::ANNOUNCE_IPV4_ACTION_ID, tid) => map!(call!(AnnounceRequest::from_bytes_v4), |ann_req| {
TrackerRequest::new(cid, tid, RequestType::Announce(ann_req))
}) |
(cid, ::SCRAPE_ACTION_ID, tid) => map!(call!(ScrapeRequest::from_bytes), |scr_req| {
TrackerRequest::new(cid, tid, RequestType::Scrape(scr_req))
}) |
(cid, ::ANNOUNCE_IPV6_ACTION_ID, tid) => map!(call!(AnnounceRequest::from_bytes_v6), |ann_req| {
TrackerRequest::new(cid, tid, RequestType::Announce(ann_req))
})
)
}