steam_client/internal/
messaging.rs1use async_trait::async_trait;
24use prost::Message;
25use steam_enums::EMsg;
26
27use crate::error::SteamError;
28
29#[derive(Debug, Clone, Copy, Default)]
31pub struct SessionInfo {
32 pub session_id: i32,
34 pub steam_id: u64,
36}
37
38impl SessionInfo {
39 pub fn new(session_id: i32, steam_id: u64) -> Self {
41 Self { session_id, steam_id }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct SentMessage {
48 pub msg_type: EMsg,
50 pub body: Vec<u8>,
52 pub job_id: Option<u64>,
54 pub service_method: Option<String>,
56}
57
58#[async_trait]
83pub trait MessageSender: Send {
84 fn is_logged_in(&self) -> bool;
86
87 fn session_info(&self) -> SessionInfo;
89
90 async fn send_message<T: Message + Send + Sync>(&mut self, msg_type: EMsg, body: &T) -> Result<(), SteamError>;
96
97 async fn send_service_method<T: Message + Send + Sync>(&mut self, method: &str, body: &T) -> Result<(), SteamError>;
103}
104
105#[derive(Debug, Default)]
127pub struct MockMessageSender {
128 pub logged_in: bool,
130 pub session_info: SessionInfo,
132 pub sent_messages: Vec<SentMessage>,
134 pub next_error: Option<SteamError>,
136 pub current_job_id: u64,
138}
139
140impl MockMessageSender {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn new_logged_in(session_id: i32, steam_id: u64) -> Self {
148 Self {
149 logged_in: true,
150 session_info: SessionInfo::new(session_id, steam_id),
151 sent_messages: Vec::new(),
152 next_error: None,
153 current_job_id: 0,
154 }
155 }
156
157 pub fn set_logged_in(&mut self, logged_in: bool) {
159 self.logged_in = logged_in;
160 }
161
162 pub fn set_next_error(&mut self, error: SteamError) {
164 self.next_error = Some(error);
165 }
166
167 pub fn clear(&mut self) {
169 self.sent_messages.clear();
170 }
171
172 pub fn last_sent(&self) -> Option<&SentMessage> {
174 self.sent_messages.last()
175 }
176
177 pub fn decode_last_message<T: Message + Default>(&self) -> Result<T, SteamError> {
181 let msg = self.last_sent().ok_or_else(|| SteamError::Other("No messages sent".into()))?;
182 T::decode(&msg.body[..]).map_err(|e| SteamError::ProtocolError(format!("Failed to decode: {}", e)))
183 }
184
185 pub fn messages_of_type(&self, msg_type: EMsg) -> Vec<&SentMessage> {
187 self.sent_messages.iter().filter(|m| m.msg_type == msg_type).collect()
188 }
189
190 pub fn service_calls(&self) -> Vec<&SentMessage> {
192 self.sent_messages.iter().filter(|m| m.service_method.is_some()).collect()
193 }
194}
195
196#[async_trait]
197impl MessageSender for MockMessageSender {
198 fn is_logged_in(&self) -> bool {
199 self.logged_in
200 }
201
202 fn session_info(&self) -> SessionInfo {
203 self.session_info
204 }
205
206 async fn send_message<T: Message + Send + Sync>(&mut self, msg_type: EMsg, body: &T) -> Result<(), SteamError> {
207 if let Some(error) = self.next_error.take() {
209 return Err(error);
210 }
211
212 self.current_job_id += 1;
214
215 self.sent_messages.push(SentMessage { msg_type, body: body.encode_to_vec(), job_id: Some(self.current_job_id), service_method: None });
217
218 Ok(())
219 }
220
221 async fn send_service_method<T: Message + Send + Sync>(&mut self, method: &str, body: &T) -> Result<(), SteamError> {
222 if let Some(error) = self.next_error.take() {
224 return Err(error);
225 }
226
227 self.current_job_id += 1;
229
230 self.sent_messages.push(SentMessage {
232 msg_type: EMsg::ServiceMethodCallFromClient,
233 body: body.encode_to_vec(),
234 job_id: Some(self.current_job_id),
235 service_method: Some(method.to_string()),
236 });
237
238 Ok(())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use steam_protos::CMsgClientChangeStatus;
245
246 use super::*;
247
248 #[tokio::test]
249 async fn test_mock_sender_records_messages() {
250 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
251
252 let body = CMsgClientChangeStatus { persona_state: Some(1), ..Default::default() };
253
254 mock.send_message(EMsg::ClientChangeStatus, &body).await.expect("test should not fail");
255
256 assert_eq!(mock.sent_messages.len(), 1);
257 assert_eq!(mock.sent_messages[0].msg_type, EMsg::ClientChangeStatus);
258 assert!(mock.sent_messages[0].service_method.is_none());
259 }
260
261 #[tokio::test]
262 async fn test_mock_sender_decode_last_message() {
263 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
264
265 let body = CMsgClientChangeStatus { persona_state: Some(3), player_name: Some("TestPlayer".to_string()), ..Default::default() };
266
267 mock.send_message(EMsg::ClientChangeStatus, &body).await.expect("test should not fail");
268
269 let decoded: CMsgClientChangeStatus = mock.decode_last_message().expect("test should not fail");
270 assert_eq!(decoded.persona_state, Some(3));
271 assert_eq!(decoded.player_name, Some("TestPlayer".to_string()));
272 }
273
274 #[tokio::test]
275 async fn test_mock_sender_service_method() {
276 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
277
278 let body = steam_protos::CPlayerGetNicknameListRequest {};
279 mock.send_service_method("Player.GetNicknameList#1", &body).await.expect("test should not fail");
280
281 assert_eq!(mock.sent_messages.len(), 1);
282 assert_eq!(mock.sent_messages[0].service_method, Some("Player.GetNicknameList#1".to_string()));
283 assert_eq!(mock.sent_messages[0].msg_type, EMsg::ServiceMethodCallFromClient);
284 }
285
286 #[tokio::test]
287 async fn test_mock_sender_error_injection() {
288 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
289 mock.set_next_error(SteamError::NotConnected);
290
291 let body = CMsgClientChangeStatus::default();
292 let result = mock.send_message(EMsg::ClientChangeStatus, &body).await;
293
294 assert!(result.is_err());
295 assert!(mock.sent_messages.is_empty()); }
297
298 #[tokio::test]
299 async fn test_mock_sender_is_logged_in() {
300 let mock_out = MockMessageSender::new();
301 assert!(!mock_out.is_logged_in());
302
303 let mock_in = MockMessageSender::new_logged_in(123, 456);
304 assert!(mock_in.is_logged_in());
305 }
306
307 #[tokio::test]
308 async fn test_mock_sender_session_info() {
309 let mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
310 let info = mock.session_info();
311
312 assert_eq!(info.session_id, 12345);
313 assert_eq!(info.steam_id, 76561198012345678);
314 }
315
316 #[tokio::test]
317 async fn test_mock_sender_messages_of_type() {
318 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
319
320 mock.send_message(EMsg::ClientChangeStatus, &CMsgClientChangeStatus::default()).await.expect("test should not fail");
321 mock.send_message(EMsg::ClientHeartBeat, &steam_protos::CMsgClientHeartBeat::default()).await.expect("test should not fail");
322 mock.send_message(EMsg::ClientChangeStatus, &CMsgClientChangeStatus::default()).await.expect("test should not fail");
323
324 let status_msgs = mock.messages_of_type(EMsg::ClientChangeStatus);
325 assert_eq!(status_msgs.len(), 2);
326
327 let heartbeat_msgs = mock.messages_of_type(EMsg::ClientHeartBeat);
328 assert_eq!(heartbeat_msgs.len(), 1);
329 }
330
331 #[tokio::test]
332 async fn test_mock_sender_clear() {
333 let mut mock = MockMessageSender::new_logged_in(12345, 76561198012345678);
334
335 mock.send_message(EMsg::ClientChangeStatus, &CMsgClientChangeStatus::default()).await.expect("test should not fail");
336 assert_eq!(mock.sent_messages.len(), 1);
337
338 mock.clear();
339 assert!(mock.sent_messages.is_empty());
340 }
341}