lib: never mind, use snafu instead

This commit is contained in:
Artemis Tosini 2024-09-05 02:52:13 +00:00
parent d539731705
commit bd1f8096ac
Signed by: artemist
GPG key ID: ADFFE553DCBB831E
5 changed files with 155 additions and 69 deletions

29
Cargo.lock generated
View file

@ -558,6 +558,12 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@ -1525,6 +1531,27 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.7" version = "0.5.7"
@ -1873,7 +1900,7 @@ dependencies = [
"reqwest", "reqwest",
"rsa", "rsa",
"serde", "serde",
"thiserror", "snafu",
"tokio", "tokio",
"vapore-proto", "vapore-proto",
] ]

View file

@ -15,7 +15,7 @@ rand = "0.8.5"
reqwest = { version = "0.12", features = ["rustls-tls-native-roots"], default-features = false} reqwest = { version = "0.12", features = ["rustls-tls-native-roots"], default-features = false}
rsa = "0.9.6" rsa = "0.9.6"
serde = { version = "1.0.209", features = ["derive"] } 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"]} tokio = { version = "1.39", features = ["rt", "rt-multi-thread", "macros", "time"]}
vapore-proto.path = "../proto" vapore-proto.path = "../proto"

View file

@ -14,6 +14,7 @@ use async_tungstenite::{
}; };
use futures::{SinkExt as _, StreamExt}; use futures::{SinkExt as _, StreamExt};
use serde::Deserialize; use serde::Deserialize;
use snafu::prelude::*;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use vapore_proto::{ use vapore_proto::{
enums_clientserver::EMsg, steammessages_base::CMsgProtoBufHeader, enums_clientserver::EMsg, steammessages_base::CMsgProtoBufHeader,
@ -21,6 +22,10 @@ use vapore_proto::{
}; };
use crate::{ use crate::{
error::{
BadResponseActionSnafu, EResultSnafu, ReqwestSnafu, VdfSnafu, WebSocketConnectSnafu,
WebSocketSnafu,
},
message::{CMProtoBufMessage, CMRawProtoBufMessage}, message::{CMProtoBufMessage, CMRawProtoBufMessage},
ClientError, ClientError,
}; };
@ -51,17 +56,21 @@ pub async fn bootstrap_find_servers() -> Result<Vec<String>, ClientError> {
let response = reqwest::get( let response = reqwest::get(
"https://api.steampowered.com/ISteamDirectory/GetCMListForConnect/v1/?cellid=0&format=vdf", "https://api.steampowered.com/ISteamDirectory/GetCMListForConnect/v1/?cellid=0&format=vdf",
) )
.await? .await
.context(ReqwestSnafu {})?
.text() .text()
.await?; .await
let result: GetCMListForConnectResponse = keyvalues_serde::from_str(&response)?; .context(ReqwestSnafu {})?;
let result: GetCMListForConnectResponse =
keyvalues_serde::from_str(&response).context(VdfSnafu {})?;
if result.success != 1 { ensure!(
return Err(ClientError::EResult( result.success == 1,
result.success, EResultSnafu {
result.message.to_string(), eresult: result.success,
)); message: result.message
} }
);
Ok(result Ok(result
.serverlist .serverlist
@ -120,7 +129,7 @@ impl Context {
message: tungstenite::Result<tungstenite::Message>, message: tungstenite::Result<tungstenite::Message>,
) -> Result<(), ClientError> { ) -> Result<(), ClientError> {
// Technically everything should be Binary but I think I saw some Text before // 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::Text(t) => t.into_bytes(),
tungstenite::Message::Binary(b) => b, tungstenite::Message::Binary(b) => b,
_ => return Err(ClientError::BadWSMessageType), _ => return Err(ClientError::BadWSMessageType),
@ -175,15 +184,17 @@ impl Context {
while !session.send_queue.is_empty() { while !session.send_queue.is_empty() {
match self.socket.poll_ready_unpin(cx) { match self.socket.poll_ready_unpin(cx) {
Poll::Ready(ret) => ret?, Poll::Ready(ret) => ret.context(WebSocketSnafu {})?,
Poll::Pending => return Ok(()), Poll::Pending => return Ok(()),
} }
let message = session.send_queue.pop_front().unwrap(); 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) { match self.socket.poll_flush_unpin(cx) {
Poll::Ready(ret) => ret?, Poll::Ready(ret) => ret.context(WebSocketSnafu {})?,
Poll::Pending => (), Poll::Pending => (),
} }
@ -237,7 +248,11 @@ pub struct CMSession {
impl CMSession { impl CMSession {
pub async fn connect(server: &str) -> Result<Self, ClientError> { pub async fn connect(server: &str) -> Result<Self, ClientError> {
let (socket, _) = connect_async(server).await?; let (socket, _) = connect_async(server)
.await
.with_context(|_| WebSocketConnectSnafu {
url: server.to_string(),
})?;
let inner = SessionInner { let inner = SessionInner {
steam_id: None, steam_id: None,
@ -372,10 +387,12 @@ impl<'a, T: protobuf::Message, U: protobuf::Message> CallServiceMethod<'a, T, U>
&self, &self,
response: CMRawProtoBufMessage, response: CMRawProtoBufMessage,
) -> Result<CMProtoBufMessage<U>, ClientError> { ) -> Result<CMProtoBufMessage<U>, ClientError> {
if response.action != EMsg::k_EMsgServiceMethodResponse { ensure!(
return Err(ClientError::BadResponseAction(response.action)); response.action == EMsg::k_EMsgServiceMethodResponse,
} BadResponseActionSnafu {
actual: response.action
}
);
CMProtoBufMessage::<U>::deserialize(response) CMProtoBufMessage::<U>::deserialize(response)
} }
} }

View file

@ -1,47 +1,67 @@
#[non_exhaustive] use snafu::prelude::*;
#[derive(thiserror::Error, Debug)]
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum ClientError { pub enum ClientError {
#[error("Valve returned bad result {0} with message `{1}`")] #[snafu(display("Valve returned bad result {eresult} with message `{message}`"))]
EResult(u32, String), EResult { eresult: u32, message: String },
#[error("Request failure")] #[snafu(display("Request failure"))]
Reqwest(#[from] reqwest::Error), Reqwest { source: reqwest::Error },
#[error("VDF Deserialization failure")] #[snafu(display("VDF Deserialization failure"))]
Vdf(#[from] keyvalues_serde::Error), Vdf {
#[snafu(source(from(keyvalues_serde::Error, Box::new)))]
source: Box<keyvalues_serde::Error>,
},
#[error("WebSocket connection error")] #[snafu(display("Unable to connect to WebSocket at `{url}`"))]
WebSocket(#[from] async_tungstenite::tungstenite::Error), WebSocketConnect {
#[snafu(source(from(async_tungstenite::tungstenite::Error, Box::new)))]
source: Box<async_tungstenite::tungstenite::Error>,
url: String,
},
#[error("WebSocket was closed while trying to recieve")] #[snafu(display("WebSocket connection error"))]
WebSocket {
#[snafu(source(from(async_tungstenite::tungstenite::Error, Box::new)))]
source: Box<async_tungstenite::tungstenite::Error>,
},
#[snafu(display("WebSocket was closed while trying to recieve"))]
ClosedSocket, ClosedSocket,
#[error("ProtoBuf Deserialization error")] #[snafu(display("Protobuf Deserialization error"))]
Protobuf(#[from] protobuf::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, BadWSMessageType,
#[error("Decompression Error")] #[snafu(display("Decompression Error"))]
DecompressionError(#[source] std::io::Error), Decompression { source: std::io::Error },
#[error("Invalid decompressed output (expected {0} bytes, got {1})")] #[snafu(display("Invalid decompressed output (expected {expected} bytes, got {actual})"))]
DecompressionInvalid(u32, usize), DecompressionInvalid { expected: u32, actual: usize },
#[error("Invalid message action {0}")] #[snafu(display("Invalid message action {action}"))]
InvalidAction(u32), InvalidAction { action: u32 },
#[error("Invalid message length")] #[snafu(display("Invalid message length"))]
InvalidMessageLength, InvalidMessageLength,
#[error("Message too short (need {0} bytes, got {1})")] #[snafu(display("Message too short (need {expected} bytes, got {actual})"))]
MessageTooShort(usize, usize), MessageTooShort { expected: usize, actual: usize },
#[error("Lock was poisoned")] #[snafu(display("Lock was poisoned"))]
LockPoisoned, LockPoisoned,
#[error("Expected action ServiceMethodResponse, got {0:?}")] #[snafu(display("Expected action ServiceMethodResponse, got {actual:?}"))]
BadResponseAction(vapore_proto::enums_clientserver::EMsg), BadResponseAction {
actual: vapore_proto::enums_clientserver::EMsg,
},
} }
impl<T> From<std::sync::PoisonError<T>> for ClientError { impl<T> From<std::sync::PoisonError<T>> for ClientError {

View file

@ -2,12 +2,16 @@ use std::io::Read;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use protobuf::{Enum as _, Message as _}; use protobuf::{Enum as _, Message as _};
use snafu::prelude::*;
use vapore_proto::{ use vapore_proto::{
enums_clientserver::EMsg, enums_clientserver::EMsg,
steammessages_base::{CMsgMulti, CMsgProtoBufHeader}, 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 /// A message sent over the socket. Can be either sent or recieved
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -30,14 +34,18 @@ impl<T: protobuf::Message> CMProtoBufMessage<T> {
out.extend_from_slice(&(self.action.value() as u32 | 0x80000000).to_le_bytes()); out.extend_from_slice(&(self.action.value() as u32 | 0x80000000).to_le_bytes());
out.extend_from_slice(&self.header.cached_size().to_le_bytes()); out.extend_from_slice(&self.header.cached_size().to_le_bytes());
self.header.write_to_vec(&mut out)?; self.header
self.body.write_to_vec(&mut out)?; .write_to_vec(&mut out)
.context(ProtobufSerSnafu {})?;
self.body
.write_to_vec(&mut out)
.context(ProtobufSerSnafu {})?;
Ok(out) Ok(out)
} }
pub fn deserialize(raw: CMRawProtoBufMessage) -> Result<Self, ClientError> { pub fn deserialize(raw: CMRawProtoBufMessage) -> Result<Self, ClientError> {
let body = T::parse_from_bytes(&raw.body)?; let body = T::parse_from_bytes(&raw.body).context(ProtobufDeSnafu {})?;
Ok(Self { Ok(Self {
action: raw.action, action: raw.action,
@ -57,20 +65,30 @@ pub struct CMRawProtoBufMessage {
impl CMRawProtoBufMessage { impl CMRawProtoBufMessage {
pub fn try_parse(binary: &[u8]) -> Result<Self, ClientError> { pub fn try_parse(binary: &[u8]) -> Result<Self, ClientError> {
if binary.len() < 8 { ensure!(
return Err(ClientError::MessageTooShort(8, binary.len())); 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 raw_action = u32::from_le_bytes(binary[0..4].try_into().unwrap()) & !0x8000_0000;
let action = EMsg::from_i32(raw_action as i32) 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_length = u32::from_le_bytes(binary[4..8].try_into().unwrap());
let header_end = 8 + header_length as usize; let header_end = 8 + header_length as usize;
if binary.len() < header_end { ensure!(
return Err(ClientError::MessageTooShort(header_end, binary.len())); 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(); let body = binary[header_end..].to_vec();
Ok(Self { Ok(Self {
@ -97,14 +115,15 @@ impl CMRawProtoBufMessage {
let mut gz = GzDecoder::new(root.body.message_body()); let mut gz = GzDecoder::new(root.body.message_body());
gz.read_to_end(&mut gzip_decompressed) gz.read_to_end(&mut gzip_decompressed)
.map_err(ClientError::DecompressionError)?; .context(DecompressionSnafu {})?;
if gzip_decompressed.len() != size_unzipped as usize { ensure!(
return Err(ClientError::DecompressionInvalid( gzip_decompressed.len() == size_unzipped as usize,
size_unzipped, DecompressionInvalidSnafu {
gzip_decompressed.len(), expected: size_unzipped,
)); actual: gzip_decompressed.len(),
} }
);
&gzip_decompressed &gzip_decompressed
} else { } else {
@ -115,10 +134,13 @@ impl CMRawProtoBufMessage {
while body.len() >= 4 { while body.len() >= 4 {
let full_length = u32::from_le_bytes(body[0..4].try_into().unwrap()); let full_length = u32::from_le_bytes(body[0..4].try_into().unwrap());
let message_end = 4 + full_length as usize; let message_end = 4 + full_length as usize;
if body.len() < message_end { ensure!(
return Err(ClientError::MessageTooShort(message_end, body.len())); body.len() >= message_end,
} MessageTooShortSnafu {
expected: message_end,
actual: body.len()
}
);
match Self::try_parse(&body[4..message_end]) { match Self::try_parse(&body[4..message_end]) {
Ok(msg) => items.push(msg), Ok(msg) => items.push(msg),
Err(err) => log::warn!("Failed to parse sub-message: {:?}", err), Err(err) => log::warn!("Failed to parse sub-message: {:?}", err),