aquatic_http protocol: refactor into more submodules, other fixes

This commit is contained in:
Joakim Frostegård 2020-07-03 17:24:56 +02:00
parent 52cc7d8acb
commit 7419c51434
9 changed files with 303 additions and 290 deletions

View file

@ -10,7 +10,9 @@ use parking_lot::Mutex;
pub use aquatic_common::ValidUntil;
use crate::protocol::*;
use crate::protocol::common::*;
use crate::protocol::request::Request;
use crate::protocol::response::Response;
// identical to ws version

View file

@ -9,7 +9,9 @@ use aquatic_common::extract_response_peers;
use crate::common::*;
use crate::config::Config;
use crate::protocol::*;
use crate::protocol::request::*;
use crate::protocol::response::*;
// almost identical to ws version

View file

@ -13,7 +13,7 @@ use native_tls::{TlsAcceptor, MidHandshakeTlsStream};
use aquatic_common_tcp::network::stream::Stream;
use crate::common::*;
use crate::protocol::Request;
use crate::protocol::request::Request;
#[derive(Debug)]

View file

@ -14,7 +14,7 @@ use aquatic_common_tcp::network::utils::create_listener;
use crate::common::*;
use crate::config::Config;
use crate::protocol::*;
use crate::protocol::response::*;
use connection::*;

View file

@ -0,0 +1,56 @@
use std::str::FromStr;
use serde::Serialize;
use super::utils::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct PeerId(
#[serde(
serialize_with = "serialize_20_bytes",
)]
pub [u8; 20]
);
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct InfoHash(
#[serde(
serialize_with = "serialize_20_bytes",
)]
pub [u8; 20]
);
#[derive(Debug, Clone)]
pub enum AnnounceEvent {
Started,
Stopped,
Completed,
Empty
}
impl Default for AnnounceEvent {
fn default() -> Self {
Self::Empty
}
}
impl FromStr for AnnounceEvent {
type Err = String;
fn from_str(value: &str) -> std::result::Result<Self, String> {
match value {
"started" => Ok(Self::Started),
"stopped" => Ok(Self::Stopped),
"completed" => Ok(Self::Completed),
"empty" => Ok(Self::Empty),
value => Err(format!("Unknown value: {}", value))
}
}
}

View file

@ -1,284 +1,4 @@
use std::net::IpAddr;
use std::str::FromStr;
use anyhow::Context;
use hashbrown::HashMap;
use serde::Serialize;
use crate::common::Peer;
mod serde_helpers;
use serde_helpers::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct PeerId(
#[serde(
serialize_with = "serialize_20_bytes",
)]
pub [u8; 20]
);
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct InfoHash(
#[serde(
serialize_with = "serialize_20_bytes",
)]
pub [u8; 20]
);
#[derive(Clone, Copy, Debug, Serialize)]
pub struct ResponsePeer {
pub ip_address: IpAddr,
pub port: u16
}
impl ResponsePeer {
pub fn from_peer(peer: &Peer) -> Self {
let ip_address = peer.connection_meta.peer_addr.ip();
Self {
ip_address,
port: peer.port
}
}
}
#[derive(Debug, Clone)]
pub enum AnnounceEvent {
Started,
Stopped,
Completed,
Empty
}
impl Default for AnnounceEvent {
fn default() -> Self {
Self::Empty
}
}
impl FromStr for AnnounceEvent {
type Err = String;
fn from_str(value: &str) -> std::result::Result<Self, String> {
let event = match value {
"started" => Self::Started,
"stopped" => Self::Stopped,
"completed" => Self::Completed,
_ => Self::default(),
};
Ok(event)
}
}
#[derive(Debug, Clone)]
pub struct AnnounceRequest {
pub info_hash: InfoHash,
pub peer_id: PeerId,
pub port: u16,
pub bytes_left: usize,
pub event: AnnounceEvent,
pub compact: bool,
/// Requested number of peers to return
pub numwant: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnnounceResponseSuccess {
#[serde(rename = "interval")]
pub announce_interval: usize,
pub tracker_id: String, // Optional??
pub complete: usize,
pub incomplete: usize,
#[serde(
serialize_with = "serialize_response_peers_compact"
)]
pub peers: Vec<ResponsePeer>,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnnounceResponseFailure {
pub failure_reason: String,
}
#[derive(Debug, Clone)]
pub struct ScrapeRequest {
pub info_hashes: Vec<InfoHash>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ScrapeStatistics {
pub complete: usize,
pub incomplete: usize,
pub downloaded: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct ScrapeResponse {
pub files: HashMap<InfoHash, ScrapeStatistics>,
}
#[derive(Debug, Clone)]
pub enum Request {
Announce(AnnounceRequest),
Scrape(ScrapeRequest),
}
impl Request {
/// Parse Request from http path (GET `/announce?info_hash=...`)
///
/// Existing serde-url decode crates were insufficient, so the decision was
/// made to create a custom parser. serde_urlencoded doesn't support multiple
/// values with same key, and serde_qs pulls in lots of dependencies. Both
/// would need preprocessing for the binary format used for info_hash and
/// peer_id.
pub fn from_http_get_path(path: &str) -> anyhow::Result<Self> {
let mut split_parts= path.splitn(2, '?');
let location = split_parts.next()
.with_context(|| "no location")?;
let query_string = split_parts.next()
.with_context(|| "no query string")?;
let mut info_hashes = Vec::new();
let mut data = HashMap::new();
for part in query_string.split('&'){
let mut key_and_value = part.splitn(2, '=');
let key = key_and_value.next()
.with_context(|| format!("no key in {}", part))?;
let value = key_and_value.next()
.with_context(|| format!("no value in {}", part))?;
let value = Self::urldecode(value).to_string();
if key == "info_hash" {
info_hashes.push(value);
} else {
data.insert(key, value);
}
}
if location == "/announce" {
let request = AnnounceRequest {
info_hash: info_hashes.get(0)
.with_context(|| "no info_hash")
.and_then(|s| deserialize_20_bytes(s))
.map(InfoHash)?,
peer_id: data.get("peer_id")
.with_context(|| "no peer_id")
.and_then(|s| deserialize_20_bytes(s))
.map(PeerId)?,
port: data.get("port")
.with_context(|| "no port")
.and_then(|s| s.parse()
.map_err(|err| anyhow::anyhow!("parse 'port': {}", err)))?,
bytes_left: data.get("left")
.with_context(|| "no left")
.and_then(|s| s.parse()
.map_err(|err| anyhow::anyhow!("parse 'left': {}", err)))?,
event: data.get("event")
.and_then(|s| s.parse().ok())
.unwrap_or_default(),
compact: data.get("compact")
.map(|s| s == "1")
.unwrap_or(true),
numwant: data.get("numwant")
.with_context(|| "no numwant")
.and_then(|s| s.parse()
.map_err(|err|
anyhow::anyhow!("parse 'numwant': {}", err)
))?,
};
Ok(Request::Announce(request))
} else {
let mut parsed_info_hashes = Vec::with_capacity(info_hashes.len());
for info_hash in info_hashes {
parsed_info_hashes.push(InfoHash(deserialize_20_bytes(&info_hash)?));
}
let request = ScrapeRequest {
info_hashes: parsed_info_hashes,
};
Ok(Request::Scrape(request))
}
}
/// The info hashes and peer id's that are received are url-encoded byte
/// by byte, e.g., %fa for byte 0xfa. However, they need to be parsed as
/// UTF-8 string, meaning that non-ascii bytes are invalid characters.
/// Therefore, these bytes must be converted to their equivalent multi-byte
/// UTF-8 encodings.
fn urldecode(value: &str) -> String {
let mut processed = String::new();
for (i, part) in value.split('%').enumerate(){
if i == 0 {
processed.push_str(part);
} else if part.len() >= 2 {
let mut two_first = String::with_capacity(2);
let mut rest = String::new();
for (j, c) in part.chars().enumerate(){
if j < 2 {
two_first.push(c);
} else {
rest.push(c);
}
}
let byte = u8::from_str_radix(&two_first, 16).unwrap();
processed.push(byte as char);
processed.push_str(&rest);
}
}
processed
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum Response {
AnnounceSuccess(AnnounceResponseSuccess),
AnnounceFailure(AnnounceResponseFailure),
Scrape(ScrapeResponse)
}
impl Response {
pub fn to_bytes(self) -> Vec<u8> {
match bendy::serde::to_bytes(&self){
Ok(bytes) => bytes,
Err(err) => {
log::error!("error encoding response: {}", err);
Vec::new()
}
}
}
}
pub mod common;
pub mod request;
pub mod response;
mod utils;

View file

@ -0,0 +1,149 @@
use anyhow::Context;
use hashbrown::HashMap;
use super::common::*;
use super::utils::*;
#[derive(Debug, Clone)]
pub struct AnnounceRequest {
pub info_hash: InfoHash,
pub peer_id: PeerId,
pub port: u16,
pub bytes_left: usize,
pub event: AnnounceEvent,
pub compact: bool,
/// Requested number of peers to return
pub numwant: usize,
}
#[derive(Debug, Clone)]
pub struct ScrapeRequest {
pub info_hashes: Vec<InfoHash>,
}
#[derive(Debug, Clone)]
pub enum Request {
Announce(AnnounceRequest),
Scrape(ScrapeRequest),
}
impl Request {
/// Parse Request from http path (GET `/announce?info_hash=...`)
///
/// Existing serde-url decode crates were insufficient, so the decision was
/// made to create a custom parser. serde_urlencoded doesn't support multiple
/// values with same key, and serde_qs pulls in lots of dependencies. Both
/// would need preprocessing for the binary format used for info_hash and
/// peer_id.
pub fn from_http_get_path(path: &str) -> anyhow::Result<Self> {
let mut split_parts= path.splitn(2, '?');
let location = split_parts.next()
.with_context(|| "no location")?;
let query_string = split_parts.next()
.with_context(|| "no query string")?;
let mut info_hashes = Vec::new();
let mut data = HashMap::new();
for part in query_string.split('&'){
let mut key_and_value = part.splitn(2, '=');
let key = key_and_value.next()
.with_context(|| format!("no key in {}", part))?;
let value = key_and_value.next()
.with_context(|| format!("no value in {}", part))?;
let value = Self::urldecode(value).to_string();
if key == "info_hash" {
info_hashes.push(value);
} else {
data.insert(key, value);
}
}
if location == "/announce" {
let request = AnnounceRequest {
info_hash: info_hashes.get(0)
.with_context(|| "no info_hash")
.and_then(|s| deserialize_20_bytes(s))
.map(InfoHash)?,
peer_id: data.get("peer_id")
.with_context(|| "no peer_id")
.and_then(|s| deserialize_20_bytes(s))
.map(PeerId)?,
port: data.get("port")
.with_context(|| "no port")
.and_then(|s| s.parse()
.map_err(|err| anyhow::anyhow!("parse 'port': {}", err)))?,
bytes_left: data.get("left")
.with_context(|| "no left")
.and_then(|s| s.parse()
.map_err(|err| anyhow::anyhow!("parse 'left': {}", err)))?,
event: data.get("event")
.and_then(|s| s.parse().ok())
.unwrap_or_default(),
compact: data.get("compact")
.map(|s| s == "1")
.unwrap_or(true),
numwant: data.get("numwant")
.with_context(|| "no numwant")
.and_then(|s| s.parse()
.map_err(|err|
anyhow::anyhow!("parse 'numwant': {}", err)
))?,
};
Ok(Request::Announce(request))
} else {
let mut parsed_info_hashes = Vec::with_capacity(info_hashes.len());
for info_hash in info_hashes {
parsed_info_hashes.push(InfoHash(deserialize_20_bytes(&info_hash)?));
}
let request = ScrapeRequest {
info_hashes: parsed_info_hashes,
};
Ok(Request::Scrape(request))
}
}
/// The info hashes and peer id's that are received are url-encoded byte
/// by byte, e.g., %fa for byte 0xfa. However, they need to be parsed as
/// UTF-8 string, meaning that non-ascii bytes are invalid characters.
/// Therefore, these bytes must be converted to their equivalent multi-byte
/// UTF-8 encodings.
fn urldecode(value: &str) -> String {
let mut processed = String::new();
for (i, part) in value.split('%').enumerate(){
if i == 0 {
processed.push_str(part);
} else if part.len() >= 2 {
let mut two_first = String::with_capacity(2);
let mut rest = String::new();
for (j, c) in part.chars().enumerate(){
if j < 2 {
two_first.push(c);
} else {
rest.push(c);
}
}
let byte = u8::from_str_radix(&two_first, 16).unwrap();
processed.push(byte as char);
processed.push_str(&rest);
}
}
processed
}
}

View file

@ -0,0 +1,85 @@
use std::net::IpAddr;
use hashbrown::HashMap;
use serde::Serialize;
use crate::common::Peer;
use super::common::*;
use super::utils::*;
#[derive(Clone, Copy, Debug, Serialize)]
pub struct ResponsePeer {
pub ip_address: IpAddr,
pub port: u16
}
impl ResponsePeer {
pub fn from_peer(peer: &Peer) -> Self {
let ip_address = peer.connection_meta.peer_addr.ip();
Self {
ip_address,
port: peer.port
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ScrapeStatistics {
pub complete: usize,
pub incomplete: usize,
pub downloaded: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnnounceResponseSuccess {
#[serde(rename = "interval")]
pub announce_interval: usize,
pub tracker_id: String, // Optional??
pub complete: usize,
pub incomplete: usize,
#[serde(
serialize_with = "serialize_response_peers_compact"
)]
pub peers: Vec<ResponsePeer>,
}
#[derive(Debug, Clone, Serialize)]
pub struct AnnounceResponseFailure {
pub failure_reason: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ScrapeResponse {
pub files: HashMap<InfoHash, ScrapeStatistics>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum Response {
AnnounceSuccess(AnnounceResponseSuccess),
AnnounceFailure(AnnounceResponseFailure),
Scrape(ScrapeResponse)
}
impl Response {
pub fn to_bytes(self) -> Vec<u8> {
match bendy::serde::to_bytes(&self){
Ok(bytes) => bytes,
Err(err) => {
log::error!("error encoding response: {}", err);
Vec::new()
}
}
}
}

View file

@ -2,8 +2,7 @@ use std::net::IpAddr;
use serde::Serializer;
use super::ResponsePeer;
use super::response::ResponsePeer;
/// Not for serde