diff --git a/Cargo.toml b/Cargo.toml index df3322e..b33c33f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ repository = "https://github.com/PocketRelay/PocketRelayClientShared" [dependencies] # Logging log = "0.4" -env_logger = "0.10" # HTTP client (native-tls is required for client auth) reqwest = { version = "0.11", default-features = false, features = [ @@ -22,7 +21,6 @@ reqwest = { version = "0.11", default-features = false, features = [ # Serialization serde = { version = "1", features = ["derive"] } -serde_json = "1" # SSLv3 implementation for the game communications blaze-ssl-async = "^0.3" diff --git a/src/api.rs b/src/api.rs index 32863c0..f6cc015 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,3 +1,5 @@ +//! API logic for HTTP requests that are sent to the Pocket Relay server + use crate::{servers::HTTP_PORT, MIN_SERVER_VERSION}; use hyper::{ header::{self, HeaderName, HeaderValue}, @@ -41,7 +43,7 @@ mod headers { /// if one is provided /// /// ## Arguments -/// * identity - Optional identity for the client to use +/// * `identity` - Optional identity for the client to use pub fn create_http_client(identity: Option) -> Result { let mut builder = Client::builder().user_agent(USER_AGENT); @@ -52,10 +54,13 @@ pub fn create_http_client(identity: Option) -> Result Result { // Read the identity file bytes let bytes = std::fs::read(path).map_err(ClientIdentityError::Read)?; @@ -121,6 +126,10 @@ pub enum LookupError { /// Attempts to lookup a server at the provided url to see if /// its a Pocket Relay server +/// +/// ## Arguments +/// * `http_client` - The HTTP client to connect with +/// * `base_url` - The server base URL (Connection URL) pub async fn lookup_server( http_client: reqwest::Client, host: String, @@ -128,13 +137,13 @@ pub async fn lookup_server( let mut url = String::new(); // Whether a scheme was inferred - let mut inserted_scheme = false; + let mut inferred_scheme = false; // Fill in missing scheme portion if !host.starts_with("http://") && !host.starts_with("https://") { url.push_str("http://"); - inserted_scheme = true; + inferred_scheme = true; } url.push_str(&host); @@ -147,7 +156,7 @@ pub async fn lookup_server( let mut url = Url::from_str(&url)?; // Update scheme to be https if the 443 port was specified and the scheme was inferred as http:// - if url.port().is_some_and(|port| port == 443) && inserted_scheme { + if url.port().is_some_and(|port| port == 443) && inferred_scheme { let _ = url.set_scheme("https"); } @@ -155,6 +164,7 @@ pub async fn lookup_server( .join(DETAILS_ENDPOINT) .expect("Failed to create server details URL"); + // Send the HTTP request and get its response let response = http_client .get(info_url) .header(header::ACCEPT, "application/json") @@ -162,6 +172,7 @@ pub async fn lookup_server( .await .map_err(LookupError::ConnectionFailed)?; + // Debug printing of response details for debug builds #[cfg(debug_assertions)] { use log::debug; @@ -172,10 +183,12 @@ pub async fn lookup_server( debug!("HTTP Headers: {:?}", response.headers()); } + // Ensure the response wasn't a non 200 response let response = response .error_for_status() .map_err(LookupError::ErrorResponse)?; + // Parse the JSON serialized server details let details = response .json::() .await @@ -218,8 +231,8 @@ pub enum ServerStreamError { /// with the Pocket Relay server /// /// ## Arguments -/// * http_client - The HTTP client to connect with -/// * base_url - The server base URL (Connection URL) +/// * `http_client` - The HTTP client to connect with +/// * `base_url` - The server base URL (Connection URL) pub async fn create_server_stream( http_client: reqwest::Client, base_url: &Url, @@ -284,15 +297,15 @@ pub struct TelemetryEvent { /// Publishes a new telemetry event to the Pocket Relay server /// /// ## Arguments -/// * http_client - The HTTP client to connect with -/// * base_url - The server base URL (Connection URL) -/// * event - The event to publish +/// * `http_client` - The HTTP client to connect with +/// * `base_url` - The server base URL (Connection URL) +/// * `event` - The event to publish pub async fn publish_telemetry_event( http_client: &reqwest::Client, base_url: &Url, event: TelemetryEvent, ) -> Result<(), reqwest::Error> { - // Create the upgrade endpoint URL + // Create the telemetry endpoint URL let endpoint_url: Url = base_url .join(TELEMETRY_ENDPOINT) .expect("Failed to create telemetry endpoint"); @@ -321,8 +334,8 @@ pub enum ProxyError { /// hyper response that can be served /// /// ## Arguments -/// * http_client - The HTTP client to connect with -/// * url - The server URL to request +/// * `http_client` - The HTTP client to connect with +/// * `url` - The server URL to request pub async fn proxy_http_request( http_client: &reqwest::Client, url: Url, diff --git a/src/blaze/mod.rs b/src/fire.rs similarity index 72% rename from src/blaze/mod.rs rename to src/fire.rs index e5591ac..8358639 100644 --- a/src/blaze/mod.rs +++ b/src/fire.rs @@ -1,4 +1,4 @@ -//! Minimal blaze packet parsing and creation implementation +//! Minimal fire packet parsing and creation implementation //! this supports the very minimal required features use bytes::{Buf, BufMut, Bytes}; @@ -23,7 +23,6 @@ pub enum FrameType { impl From for FrameType { fn from(value: u8) -> Self { match value { - 0x0 => FrameType::Request, 0x1 => FrameType::Response, 0x2 => FrameType::Notify, 0x3 => FrameType::Error, @@ -32,8 +31,8 @@ impl From for FrameType { } } -/// Structure of the header for a frame -pub struct FireFrameHeader { +/// The header for a fire frame +pub struct FrameHeader { /// The length of the frame contents pub length: usize, /// The component that should handle this frame @@ -51,22 +50,23 @@ pub struct FireFrameHeader { } /// Packet framing structure -pub struct FireFrame { +pub struct Frame { /// Header for the frame - pub header: FireFrameHeader, + pub header: FrameHeader, /// The encoded byte contents of the packet pub contents: Bytes, } -impl FireFrame { - pub fn response(header: &FireFrameHeader, value: V) -> FireFrame - where - V: TdfSerialize, - { - let contents = Bytes::from(serialize_vec(&value)); - - FireFrame { - header: FireFrameHeader { +impl Frame { + /// Creates a new response frame responding to the provided `header` + /// with the bytes contents of `value` + /// + /// ## Arguments + /// * `header` - The header of the frame to respond to + /// * `contents` - The bytes of the frame + pub fn response_raw(header: &FrameHeader, contents: Bytes) -> Frame { + Frame { + header: FrameHeader { length: contents.len(), component: header.component, command: header.command, @@ -79,21 +79,27 @@ impl FireFrame { } } - pub fn response_empty(header: &FireFrameHeader) -> FireFrame { - let contents = Bytes::new(); + /// Creates a new response frame responding to the provided `header` + /// with the encoded contents of the `value` + /// + /// ## Arguments + /// * `header` - The header of the frame to respond to + /// * `value` - The value to encode as the frame bytes + #[inline] + pub fn response(header: &FrameHeader, value: V) -> Frame + where + V: TdfSerialize, + { + Self::response_raw(header, Bytes::from(serialize_vec(&value))) + } - FireFrame { - header: FireFrameHeader { - length: contents.len(), - component: header.component, - command: header.command, - error: 0, - ty: FrameType::Response, - options: 0, - seq: header.seq, - }, - contents, - } + /// Creates a new response frame responding to the provided `header` + /// with empty contents + /// + /// ## Arguments + /// * `header` - The header of the frame to respond to + pub fn response_empty(header: &FrameHeader) -> Frame { + Self::response_raw(header, Bytes::new()) } } @@ -101,7 +107,7 @@ impl FireFrame { #[derive(Default)] pub struct FireCodec { /// Incomplete frame thats currently being read - current_frame: Option, + current_frame: Option, } impl FireCodec { @@ -112,7 +118,7 @@ impl Decoder for FireCodec { // The codec doesn't have any errors of its own so IO error is used type Error = io::Error; // The decoder provides fire frames - type Item = FireFrame; + type Item = Frame; fn decode(&mut self, src: &mut bytes::BytesMut) -> Result, Self::Error> { let current_frame = if let Some(current_frame) = self.current_frame.as_mut() { @@ -133,7 +139,7 @@ impl Decoder for FireCodec { let options: u8 = src.get_u8() >> 4; let seq: u16 = src.get_u16(); - let header = FireFrameHeader { + let header = FrameHeader { length, component, command, @@ -156,17 +162,17 @@ impl Decoder for FireCodec { // Take all the frame bytes let buffer = src.split_to(header.length); - Ok(Some(FireFrame { + Ok(Some(Frame { header, contents: buffer.freeze(), })) } } -impl Encoder for FireCodec { +impl Encoder for FireCodec { type Error = io::Error; - fn encode(&mut self, item: FireFrame, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> { + fn encode(&mut self, item: Frame, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> { let header = item.header; dst.put_u16(header.length as u16); dst.put_u16(header.component); diff --git a/src/lib.rs b/src/lib.rs index 53b3c3c..7d7f746 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,22 @@ +#![warn(missing_docs, unused_variables, unused_crate_dependencies)] + +//! Shared core for Pocket Relay client +//! +//! This library handles creating and running the local servers required +//! for connecting to Pocket Relay servers. +//! +//! It provides shared backend for the different variants to make it easier +//! to keep feature parody across versions +//! +//! [`PocketRelay`]: https://pocket-relay.pages.dev/ + +// Re-exports for dependencies pub use reqwest; pub use semver::Version; pub use url::Url; pub mod api; -pub mod blaze; +pub mod fire; pub mod servers; pub mod update; diff --git a/src/servers/blaze.rs b/src/servers/blaze.rs index 01364a5..d952684 100644 --- a/src/servers/blaze.rs +++ b/src/servers/blaze.rs @@ -13,8 +13,8 @@ use url::Url; /// Starts the blaze server /// /// ## Arguments -/// * http_client - The HTTP client passed around for connection upgrades -/// * base_url - The server base URL to connect clients to +/// * `http_client` - The HTTP client passed around for connection upgrades +/// * `base_url` - The server base URL to connect clients to pub async fn start_blaze_server( http_client: reqwest::Client, base_url: Arc, @@ -26,16 +26,16 @@ pub async fn start_blaze_server( loop { let (client_stream, _) = listener.accept().await?; - spawn_server_task(handle(client_stream, http_client.clone(), base_url.clone())) + spawn_server_task(handle(client_stream, http_client.clone(), base_url.clone())); } } /// Handler for processing BlazeSDK client connections /// /// ## Arguments -/// * client_stream - The client stream to read and write from -/// * http_client - The HTTP client passed around for connection upgrades -/// * base_url - The server base URL to connect clients to +/// * `client_stream` - The client stream to read and write from +/// * `http_client` - The HTTP client passed around for connection upgrades +/// * `base_url` - The server base URL to connect clients to async fn handle(mut client_stream: TcpStream, http_client: reqwest::Client, base_url: Arc) { debug!("Starting blaze connection"); diff --git a/src/servers/http.rs b/src/servers/http.rs index abfe6ab..175aa80 100644 --- a/src/servers/http.rs +++ b/src/servers/http.rs @@ -5,6 +5,7 @@ use super::HTTP_PORT; use crate::api::proxy_http_request; use hyper::{ + http::uri::PathAndQuery, service::{make_service_fn, service_fn}, Body, Request, Response, Server, StatusCode, }; @@ -20,8 +21,8 @@ use url::Url; /// Starts the HTTP proxy server /// /// ## Arguments -/// * http_client - The HTTP client passed around for sending the requests -/// * base_url - The server base URL to proxy requests to +/// * `http_client` - The HTTP client passed around for sending the requests +/// * `base_url` - The server base URL to proxy requests to pub async fn start_http_server( http_client: reqwest::Client, base_url: Arc, @@ -49,6 +50,13 @@ pub async fn start_http_server( .map_err(|err| std::io::Error::new(ErrorKind::Other, err)) } +/// Handles an HTTP request from the HTTP server proxying it along +/// to the Pocket Relay server +/// +/// ## Arguments +/// * `request` - The HTTP request +/// * `http_client` - The HTTP client to proxy the request with +/// * `base_url` - The server base URL (Connection URL) async fn handle( request: Request, http_client: reqwest::Client, @@ -58,7 +66,8 @@ async fn handle( .uri() // Extract the path and query portion of the url .path_and_query() - .map(|value| value.as_str()) + // Convert the path to a &str + .map(PathAndQuery::as_str) // Fallback to empty path if none is provided .unwrap_or_default(); diff --git a/src/servers/mod.rs b/src/servers/mod.rs index 338fca7..776e558 100644 --- a/src/servers/mod.rs +++ b/src/servers/mod.rs @@ -10,10 +10,15 @@ pub mod qos; pub mod redirector; pub mod telemetry; +/// The port the Redirector server will bind to pub const REDIRECTOR_PORT: u16 = 42127; +/// The port the Blaze server will bind to pub const BLAZE_PORT: u16 = 42128; +/// The port the Telemetry server will bind to pub const TELEMETRY_PORT: u16 = 42129; +/// The port the Quality of Service server will bind to pub const QOS_PORT: u16 = 42130; +/// The port the HTTP server will bind to pub const HTTP_PORT: u16 = 42131; // Shared set of abort handles to server tasks @@ -29,7 +34,7 @@ pub fn has_server_tasks() -> bool { /// adds the abort handle for the task to the task collection /// /// ## Arguments -/// * task - The task future to spawn +/// * `task` - The task future to spawn #[inline] pub fn spawn_server_task(task: F) where @@ -42,7 +47,7 @@ where /// Append an abort handle to the server task collection /// /// ## Arguments -/// * handle - The abort handle for the task +/// * `handle` - The abort handle for the task pub fn add_server_task(handle: AbortHandle) { let values = &mut *SERVER_TASK_COLLECTION.lock(); values.push(handle); diff --git a/src/servers/qos.rs b/src/servers/qos.rs index 0a7e5a9..4efec0c 100644 --- a/src/servers/qos.rs +++ b/src/servers/qos.rs @@ -27,11 +27,16 @@ pub async fn start_qos_server() -> std::io::Result<()> { // Create an array from the data that was recieved let buffer: Box<[u8]> = Box::from(&buffer[..count]); - spawn_server_task(handle(socket.clone(), addr, buffer)) + spawn_server_task(handle(socket.clone(), addr, buffer)); } } -/// Handles a QoS connection +/// Handles a Quality of Service connection +/// +/// ## Arguments +/// * `socket` - The UDP socket used for sending the responses +/// * `socket_addr` - The socket address of the connection (Target for the response) +/// * `buffer` - Buffer of bytes recieved from the socket async fn handle(socket: Arc, socket_addr: SocketAddr, buffer: Box<[u8]>) { // Extract the IPv4 address from the socket address (Fallback to 0.0.0.0) let socket_ip = match socket_addr { @@ -99,16 +104,16 @@ async fn public_address() -> Option { // Try all addresses using the first valid value for address in addresses { - let response = match reqwest::get(address).await { - Ok(value) => value, - Err(_) => continue, + let Ok(response) = reqwest::get(address).await else { + continue; }; - let ip = match response.text().await { - Ok(value) => value.trim().replace('\n', ""), - Err(_) => continue, + let Ok(response) = response.text().await else { + continue; }; + let ip = response.trim().replace('\n', ""); + if let Ok(parsed) = ip.parse() { value = Some(parsed); break; @@ -119,7 +124,7 @@ async fn public_address() -> Option { // we don't have internet lets try using our local address if value.is_none() { if let Ok(IpAddr::V4(addr)) = local_ip_address::local_ip() { - value = Some(addr) + value = Some(addr); } } diff --git a/src/servers/redirector.rs b/src/servers/redirector.rs index bc3c70c..73d7088 100644 --- a/src/servers/redirector.rs +++ b/src/servers/redirector.rs @@ -3,7 +3,7 @@ //! servers as localhost use super::{spawn_server_task, BLAZE_PORT, REDIRECTOR_PORT}; -use crate::blaze::{FireCodec, FireFrame}; +use crate::fire::{FireCodec, Frame}; use blaze_ssl_async::{BlazeAccept, BlazeListener}; use futures::{SinkExt, TryStreamExt}; use log::{debug, error}; @@ -25,7 +25,7 @@ pub async fn start_redirector_server() -> std::io::Result<()> { if let Err(err) = handle(client_accept).await { error!("Error while redirecting: {}", err); } - }) + }); } } @@ -49,8 +49,15 @@ pub enum RedirectError { /// Allowed time for a redirect to occur before considering /// the connection as timed out const REDIRECT_TIMEOUT: Duration = Duration::from_secs(60); +/// Redirector component to expect +const COMPONENT_REDIRECTOR: u16 = 0x5; +/// getServerInstance command to expect +const COMMAND_GET_SERVER_INSTANCE: u16 = 0x1; /// Handler for processing redirector connections +/// +/// ## Arguments +/// * `client_accept` - The connecting SSL client to accept async fn handle(client_accept: BlazeAccept) -> Result<(), RedirectError> { let (stream, _) = client_accept.finish_accept().await?; let mut framed = Framed::new(stream, FireCodec::default()); @@ -64,9 +71,6 @@ async fn handle(client_accept: BlazeAccept) -> Result<(), RedirectError> { { let header = &packet.header; - const COMPONENT_REDIRECTOR: u16 = 0x5; - const COMMAND_GET_SERVER_INSTANCE: u16 = 0x1; - // Respond to unexpected packets with empty responses if header.component != COMPONENT_REDIRECTOR || header.command != COMMAND_GET_SERVER_INSTANCE { @@ -75,7 +79,7 @@ async fn handle(client_accept: BlazeAccept) -> Result<(), RedirectError> { header.component, header.command ); framed - .send(FireFrame::response_empty(header)) + .send(Frame::response_empty(header)) .await .map_err(RedirectError::WriteError)?; continue; @@ -84,7 +88,7 @@ async fn handle(client_accept: BlazeAccept) -> Result<(), RedirectError> { debug!("Redirector responding"); framed - .send(FireFrame::response(header, LocalInstanceResponse)) + .send(Frame::response(header, LocalInstanceResponse)) .await .map_err(RedirectError::WriteError)?; break; diff --git a/src/servers/telemetry.rs b/src/servers/telemetry.rs index e20f9ca..c7a8d9d 100644 --- a/src/servers/telemetry.rs +++ b/src/servers/telemetry.rs @@ -14,8 +14,8 @@ use url::Url; /// Starts the telemetry server /// /// ## Arguments -/// * http_client - The HTTP client used to forward messages -/// * base_url - The server base URL to connect clients to +/// * `http_client` - The HTTP client used to forward messages +/// * `base_url` - The server base URL to connect clients to pub async fn start_telemetry_server( http_client: reqwest::Client, base_url: Arc, @@ -47,7 +47,7 @@ const TLM3_KEY: &[u8] = b"The truth is back in style."; /// stream /// /// ## Arguments -/// * stream - The stream to decode from +/// * `stream` - The stream to decode from async fn read_telemetry_event(stream: &mut TcpStream) -> std::io::Result { let length: usize = { // Buffer for reading the header + padding + length bytes @@ -98,7 +98,7 @@ async fn read_telemetry_event(stream: &mut TcpStream) -> std::io::Result String { input .splitn(2, |value| b'-'.eq(value)) @@ -115,8 +115,8 @@ fn tlm3(input: &[u8]) -> String { /// Applies an xor cipher over the input using the provided key /// /// ## Arguments -/// * input - The input value to xor -/// * key - The key to xor with +/// * `input` - The input value to xor +/// * `key` - The key to xor with fn xor_cipher(input: &[u8], key: &[u8]) -> Vec { input .iter() diff --git a/src/update.rs b/src/update.rs index 2c490a9..1c409e9 100644 --- a/src/update.rs +++ b/src/update.rs @@ -31,8 +31,8 @@ pub struct GitHubReleaseAsset { /// Attempts to obtain the latest release from github /// /// ## Arguments -/// * http_client - The HTTP client to make the request with -/// * repository - The repository to get the latest release for (e.g "PocketRelay/Client") +/// * `http_client` - The HTTP client to make the request with +/// * `repository` - The repository to get the latest release for (e.g "PocketRelay/Client") pub async fn get_latest_release( http_client: &reqwest::Client, repository: &str, @@ -55,8 +55,8 @@ pub async fn get_latest_release( /// downloaded bytes /// /// ## Arguments -/// * http_client - The HTTP client to make the request with -/// * asset - The asset to download +/// * `http_client` - The HTTP client to make the request with +/// * `asset` - The asset to download pub async fn download_latest_release( http_client: &reqwest::Client, asset: &GitHubReleaseAsset,