diff --git a/Cargo.lock b/Cargo.lock index 9a27e6f..8de6ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,6 +558,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1525,6 +1531,27 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snafu" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.5.7" @@ -1873,7 +1900,7 @@ dependencies = [ "reqwest", "rsa", "serde", - "thiserror", + "snafu", "tokio", "vapore-proto", ] diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 0f00c44..f5aa948 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -15,7 +15,7 @@ rand = "0.8.5" reqwest = { version = "0.12", features = ["rustls-tls-native-roots"], default-features = false} rsa = "0.9.6" serde = { version = "1.0.209", features = ["derive"] } -thiserror = "1.0.63" +snafu = "0.8.4" tokio = { version = "1.39", features = ["rt", "rt-multi-thread", "macros", "time"]} vapore-proto.path = "../proto" diff --git a/lib/src/connection.rs b/lib/src/connection.rs index 88221ce..7f6f1c4 100644 --- a/lib/src/connection.rs +++ b/lib/src/connection.rs @@ -14,6 +14,7 @@ use async_tungstenite::{ }; use futures::{SinkExt as _, StreamExt}; use serde::Deserialize; +use snafu::prelude::*; use tokio::sync::broadcast; use vapore_proto::{ enums_clientserver::EMsg, steammessages_base::CMsgProtoBufHeader, @@ -21,6 +22,7 @@ use vapore_proto::{ }; use crate::{ + error::{BadResponseActionSnafu, EResultSnafu, ReqwestSnafu, VdfSnafu, WebSocketSnafu}, message::{CMProtoBufMessage, CMRawProtoBufMessage}, ClientError, }; @@ -51,17 +53,21 @@ pub async fn bootstrap_find_servers() -> Result, ClientError> { let response = reqwest::get( "https://api.steampowered.com/ISteamDirectory/GetCMListForConnect/v1/?cellid=0&format=vdf", ) - .await? + .await + .context(ReqwestSnafu {})? .text() - .await?; - let result: GetCMListForConnectResponse = keyvalues_serde::from_str(&response)?; + .await + .context(ReqwestSnafu {})?; + let result: GetCMListForConnectResponse = + keyvalues_serde::from_str(&response).context(VdfSnafu {})?; - if result.success != 1 { - return Err(ClientError::EResult( - result.success, - result.message.to_string(), - )); - } + ensure!( + result.success == 1, + EResultSnafu { + eresult: result.success, + message: result.message + } + ); Ok(result .serverlist @@ -120,7 +126,7 @@ impl Context { message: tungstenite::Result, ) -> Result<(), ClientError> { // Technically everything should be Binary but I think I saw some Text before - let message_data = match message? { + let message_data = match message.context(WebSocketSnafu {})? { tungstenite::Message::Text(t) => t.into_bytes(), tungstenite::Message::Binary(b) => b, _ => return Err(ClientError::BadWSMessageType), @@ -175,15 +181,17 @@ impl Context { while !session.send_queue.is_empty() { match self.socket.poll_ready_unpin(cx) { - Poll::Ready(ret) => ret?, + Poll::Ready(ret) => ret.context(WebSocketSnafu {})?, Poll::Pending => return Ok(()), } let message = session.send_queue.pop_front().unwrap(); - self.socket.start_send_unpin(message)?; + self.socket + .start_send_unpin(message) + .context(WebSocketSnafu {})?; } match self.socket.poll_flush_unpin(cx) { - Poll::Ready(ret) => ret?, + Poll::Ready(ret) => ret.context(WebSocketSnafu {})?, Poll::Pending => (), } @@ -237,7 +245,7 @@ pub struct CMSession { impl CMSession { pub async fn connect(server: &str) -> Result { - let (socket, _) = connect_async(server).await?; + let (socket, _) = connect_async(server).await.context(WebSocketSnafu {})?; let inner = SessionInner { steam_id: None, @@ -372,10 +380,12 @@ impl<'a, T: protobuf::Message, U: protobuf::Message> CallServiceMethod<'a, T, U> &self, response: CMRawProtoBufMessage, ) -> Result, ClientError> { - if response.action != EMsg::k_EMsgServiceMethodResponse { - return Err(ClientError::BadResponseAction(response.action)); - } - + ensure!( + response.action == EMsg::k_EMsgServiceMethodResponse, + BadResponseActionSnafu { + actual: response.action + } + ); CMProtoBufMessage::::deserialize(response) } } diff --git a/lib/src/error.rs b/lib/src/error.rs index 52be2e6..a74168f 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -1,47 +1,60 @@ -#[non_exhaustive] -#[derive(thiserror::Error, Debug)] +use snafu::prelude::*; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub(crate)))] pub enum ClientError { - #[error("Valve returned bad result {0} with message `{1}`")] - EResult(u32, String), + #[snafu(display("Valve returned bad result {eresult} with message `{message}`"))] + EResult { eresult: u32, message: String }, - #[error("Request failure")] - Reqwest(#[from] reqwest::Error), + #[snafu(display("Request failure"))] + Reqwest { source: reqwest::Error }, - #[error("VDF Deserialization failure")] - Vdf(#[from] keyvalues_serde::Error), + #[snafu(display("VDF Deserialization failure"))] + Vdf { + #[snafu(source(from(keyvalues_serde::Error, Box::new)))] + source: Box, + }, - #[error("WebSocket connection error")] - WebSocket(#[from] async_tungstenite::tungstenite::Error), + #[snafu(display("WebSocket connection error"))] + WebSocket { + #[snafu(source(from(async_tungstenite::tungstenite::Error, Box::new)))] + source: Box, + }, - #[error("WebSocket was closed while trying to recieve")] + #[snafu(display("WebSocket was closed while trying to recieve"))] ClosedSocket, - #[error("ProtoBuf Deserialization error")] - Protobuf(#[from] protobuf::Error), + #[snafu(display("Protobuf Deserialization error"))] + ProtobufDe { source: protobuf::Error }, - #[error("Invalid WebSocket message type from server")] + #[snafu(display("Protobuf Serialization error"))] + ProtobufSer { source: protobuf::Error }, + + #[snafu(display("Invalid WebSocket message type from server"))] BadWSMessageType, - #[error("Decompression Error")] - DecompressionError(#[source] std::io::Error), + #[snafu(display("Decompression Error"))] + Decompression { source: std::io::Error }, - #[error("Invalid decompressed output (expected {0} bytes, got {1})")] - DecompressionInvalid(u32, usize), + #[snafu(display("Invalid decompressed output (expected {expected} bytes, got {actual})"))] + DecompressionInvalid { expected: u32, actual: usize }, - #[error("Invalid message action {0}")] - InvalidAction(u32), + #[snafu(display("Invalid message action {action}"))] + InvalidAction { action: u32 }, - #[error("Invalid message length")] + #[snafu(display("Invalid message length"))] InvalidMessageLength, - #[error("Message too short (need {0} bytes, got {1})")] - MessageTooShort(usize, usize), + #[snafu(display("Message too short (need {expected} bytes, got {actual})"))] + MessageTooShort { expected: usize, actual: usize }, - #[error("Lock was poisoned")] + #[snafu(display("Lock was poisoned"))] LockPoisoned, - #[error("Expected action ServiceMethodResponse, got {0:?}")] - BadResponseAction(vapore_proto::enums_clientserver::EMsg), + #[snafu(display("Expected action ServiceMethodResponse, got {actual:?}"))] + BadResponseAction { + actual: vapore_proto::enums_clientserver::EMsg, + }, } impl From> for ClientError { diff --git a/lib/src/message.rs b/lib/src/message.rs index 6731fde..66f0f3d 100644 --- a/lib/src/message.rs +++ b/lib/src/message.rs @@ -2,12 +2,16 @@ use std::io::Read; use flate2::read::GzDecoder; use protobuf::{Enum as _, Message as _}; +use snafu::prelude::*; use vapore_proto::{ enums_clientserver::EMsg, steammessages_base::{CMsgMulti, CMsgProtoBufHeader}, }; -use crate::ClientError; +use crate::error::{ + ClientError, DecompressionInvalidSnafu, DecompressionSnafu, InvalidActionSnafu, + MessageTooShortSnafu, ProtobufDeSnafu, ProtobufSerSnafu, +}; /// A message sent over the socket. Can be either sent or recieved #[derive(Debug, Clone)] @@ -30,14 +34,18 @@ impl CMProtoBufMessage { out.extend_from_slice(&(self.action.value() as u32 | 0x80000000).to_le_bytes()); out.extend_from_slice(&self.header.cached_size().to_le_bytes()); - self.header.write_to_vec(&mut out)?; - self.body.write_to_vec(&mut out)?; + self.header + .write_to_vec(&mut out) + .context(ProtobufSerSnafu {})?; + self.body + .write_to_vec(&mut out) + .context(ProtobufSerSnafu {})?; Ok(out) } pub fn deserialize(raw: CMRawProtoBufMessage) -> Result { - let body = T::parse_from_bytes(&raw.body)?; + let body = T::parse_from_bytes(&raw.body).context(ProtobufDeSnafu {})?; Ok(Self { action: raw.action, @@ -57,20 +65,30 @@ pub struct CMRawProtoBufMessage { impl CMRawProtoBufMessage { pub fn try_parse(binary: &[u8]) -> Result { - if binary.len() < 8 { - return Err(ClientError::MessageTooShort(8, binary.len())); - } + ensure!( + binary.len() >= 8, + MessageTooShortSnafu { + expected: 8usize, + actual: binary.len() + } + ); + let raw_action = u32::from_le_bytes(binary[0..4].try_into().unwrap()) & !0x8000_0000; let action = EMsg::from_i32(raw_action as i32) - .ok_or_else(|| ClientError::InvalidAction(raw_action))?; + .with_context(|| InvalidActionSnafu { action: raw_action })?; let header_length = u32::from_le_bytes(binary[4..8].try_into().unwrap()); let header_end = 8 + header_length as usize; - if binary.len() < header_end { - return Err(ClientError::MessageTooShort(header_end, binary.len())); - } + ensure!( + binary.len() >= header_end, + MessageTooShortSnafu { + expected: header_end, + actual: binary.len() + } + ); - let header = CMsgProtoBufHeader::parse_from_bytes(&binary[8..header_end])?; + let header = CMsgProtoBufHeader::parse_from_bytes(&binary[8..header_end]) + .context(ProtobufDeSnafu {})?; let body = binary[header_end..].to_vec(); Ok(Self { @@ -97,14 +115,15 @@ impl CMRawProtoBufMessage { let mut gz = GzDecoder::new(root.body.message_body()); gz.read_to_end(&mut gzip_decompressed) - .map_err(ClientError::DecompressionError)?; + .context(DecompressionSnafu {})?; - if gzip_decompressed.len() != size_unzipped as usize { - return Err(ClientError::DecompressionInvalid( - size_unzipped, - gzip_decompressed.len(), - )); - } + ensure!( + gzip_decompressed.len() == size_unzipped as usize, + DecompressionInvalidSnafu { + expected: size_unzipped, + actual: gzip_decompressed.len(), + } + ); &gzip_decompressed } else { @@ -115,10 +134,13 @@ impl CMRawProtoBufMessage { while body.len() >= 4 { let full_length = u32::from_le_bytes(body[0..4].try_into().unwrap()); let message_end = 4 + full_length as usize; - if body.len() < message_end { - return Err(ClientError::MessageTooShort(message_end, body.len())); - } - + ensure!( + body.len() >= message_end, + MessageTooShortSnafu { + expected: message_end, + actual: body.len() + } + ); match Self::try_parse(&body[4..message_end]) { Ok(msg) => items.push(msg), Err(err) => log::warn!("Failed to parse sub-message: {:?}", err),