1use std::fmt;
7use std::io::Read;
8use std::net::{IpAddr, SocketAddr};
9use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::Duration;
12
13use async_trait::async_trait;
14use bytes::Bytes;
15use parking_lot::RwLock;
16use prost::Message;
17use tokio::sync::mpsc;
18use tokio_util::sync::CancellationToken;
19
20use crate::base::client_msg::{ClientMsgProtobuf, IClientMsg, SteamIDRaw};
21use crate::base::generated::steam_language::{EMsg, EResult};
22use crate::base::packet_base::{IPacketMsg, PacketClientMsg, PacketClientMsgProtobuf, PacketMsg};
23use crate::error::SteamError;
24use crate::networking::steam3::envelope_encrypted_connection::EnvelopeEncryptedConnection;
25use crate::networking::steam3::i_connection::{Connection, ConnectionEvent};
26use crate::networking::steam3::protocol_type::ProtocolTypes;
27use crate::networking::steam3::tcp_connection::TcpConnection;
28use crate::networking::steam3::udp_connection::UdpConnection;
29use crate::networking::steam3::web_socket_connection::WebSocketConnection;
30use crate::steam::discovery::server_record::ServerRecord;
31use crate::steam::discovery::smart_cm_server_list::{ServerQuality, SmartCMServerList};
32use crate::steam::steam_client::configuration::steam_configuration::{
33 default_user_agent, SteamConfiguration,
34};
35use crate::types::steam_id::SteamID;
36use crate::util::debug_log::DebugLog;
37use crate::util::debug_network_listener::IDebugNetworkListener;
38use crate::util::net_helpers::NetHelpers;
39use steamkit_protos::steam::{
40 CMsgClientHello, CMsgClientHeartBeat, CMsgClientLogonResponse, CMsgClientLoggedOff,
41 CMsgClientSessionToken, CMsgMulti, CMsgProtoBufHeader,
42};
43
44pub const CURRENT_PROTOCOL: u32 = 65581;
47
48const PROTO_MASK: u32 = 0x8000_0000;
50
51const EXTENDED_HEADER_SIZE: usize = 36;
53
54const EXTENDED_HEADER_STEAM_ID_OFFSET: usize = 24;
56
57const EXTENDED_HEADER_SESSION_ID_OFFSET: usize = 32;
59
60const SWITCH_ATTEMPTS: usize = 3;
62
63pub const MAX_CONSECUTIVE_SERVER_SWITCHES: u32 = 5;
68
69#[async_trait]
71pub trait ClientMsgDispatcher: Send + Sync {
72 async fn on_client_msg_received(&self, client: &CMClient, packet: &dyn IPacketMsg) -> bool;
75
76 async fn on_client_connected(&self, client: &CMClient);
78
79 async fn on_client_disconnected(&self, client: &CMClient, user_initiated: bool);
81}
82
83pub type ConnectionFactory = Arc<
86 dyn Fn(&CMClient, &ServerRecord, ProtocolTypes) -> Result<Arc<dyn Connection>, SteamError>
87 + Send
88 + Sync,
89>;
90
91fn default_connection_factory(
98 client: &CMClient,
99 record: &ServerRecord,
100 protocol: ProtocolTypes,
101) -> Result<Arc<dyn Connection>, SteamError> {
102 let universe = client.universe();
103 let debug_network_listener = client.debug_network_listener();
104
105 if protocol.has(ProtocolTypes::TCP) {
106 return Ok(Arc::new(EnvelopeEncryptedConnection::with_debug_network_listener(
107 Arc::new(TcpConnection::new()),
108 universe,
109 debug_network_listener,
110 )));
111 }
112
113 if protocol.has(ProtocolTypes::WEB_SOCKET) {
114 return Ok(Arc::new(
115 WebSocketConnection::new_tungstenite_for_host_with_user_agent(
116 record.get_host(),
117 default_user_agent(),
118 ),
119 ));
120 }
121
122 if protocol.has(ProtocolTypes::UDP) {
123 return Ok(Arc::new(EnvelopeEncryptedConnection::with_debug_network_listener(
124 Arc::new(UdpConnection::new()),
125 universe,
126 debug_network_listener,
127 )));
128 }
129
130 Err(SteamError::InvalidArgument(
131 "Protocol bitmask has no supported protocols set.".to_owned(),
132 ))
133}
134
135struct HeartBeat {
137 cancel: CancellationToken,
138 task: tokio::task::JoinHandle<()>,
139}
140
141pub struct CMClient {
144 configuration: Arc<SteamConfiguration>,
145 id: String,
146 servers: Arc<SmartCMServerList>,
147 dispatcher: Arc<dyn ClientMsgDispatcher>,
148 connection_factory: RwLock<Option<ConnectionFactory>>,
149 connection: RwLock<Option<Arc<dyn Connection>>>,
150 connection_cancellation: RwLock<Option<CancellationToken>>,
151 outbound: RwLock<Option<mpsc::UnboundedSender<Bytes>>>,
152 expect_disconnection: AtomicBool,
153 reconnect_requested: AtomicBool,
154 pending_logon: RwLock<Option<Vec<u8>>>,
158 consecutive_switches: AtomicU32,
160 generation: AtomicU64,
163 announced_generation: AtomicU64,
166 is_connected: AtomicBool,
167 connected_handled: AtomicBool,
168 session_token: AtomicU64,
169 cell_id: RwLock<Option<u32>>,
170 session_id: RwLock<Option<i32>>,
171 steam_id: RwLock<Option<SteamID>>,
172 public_ip: RwLock<Option<IpAddr>>,
173 ip_country_code: RwLock<Option<String>>,
174 user_country_code: RwLock<Option<String>>,
175 debug_network_listener: RwLock<Option<Arc<dyn IDebugNetworkListener>>>,
176 heart_beat: RwLock<Option<HeartBeat>>,
177}
178
179struct ConnectionSession {
182 connection: Arc<dyn Connection>,
183 events: mpsc::UnboundedReceiver<ConnectionEvent>,
185}
186
187impl fmt::Debug for CMClient {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 f.debug_struct("CMClient")
190 .field("id", &self.id)
191 .field("is_connected", &self.is_connected.load(Ordering::SeqCst))
192 .field("session_id", &*self.session_id.read())
193 .field("steam_id", &*self.steam_id.read())
194 .field("cell_id", &*self.cell_id.read())
195 .finish()
196 }
197}
198
199impl CMClient {
200 pub fn new(
204 configuration: Arc<SteamConfiguration>,
205 identifier: &str,
206 dispatcher: Arc<dyn ClientMsgDispatcher>,
207 ) -> CMClient {
208 let servers = configuration.server_list().clone();
209
210 CMClient {
211 configuration,
212 id: identifier.to_owned(),
213 servers,
214 dispatcher,
215 connection_factory: RwLock::new(None),
216 connection: RwLock::new(None),
217 connection_cancellation: RwLock::new(None),
218 outbound: RwLock::new(None),
219 expect_disconnection: AtomicBool::new(false),
220 reconnect_requested: AtomicBool::new(false),
221 pending_logon: RwLock::new(None),
222 consecutive_switches: AtomicU32::new(0),
223 generation: AtomicU64::new(0),
224 announced_generation: AtomicU64::new(0),
225 is_connected: AtomicBool::new(false),
226 connected_handled: AtomicBool::new(false),
227 session_token: AtomicU64::new(0),
228 cell_id: RwLock::new(None),
229 session_id: RwLock::new(None),
230 steam_id: RwLock::new(None),
231 public_ip: RwLock::new(None),
232 ip_country_code: RwLock::new(None),
233 user_country_code: RwLock::new(None),
234 debug_network_listener: RwLock::new(None),
235 heart_beat: RwLock::new(None),
236 }
237 }
238
239 pub fn configuration(&self) -> &Arc<SteamConfiguration> {
241 &self.configuration
242 }
243
244 pub fn id(&self) -> &str {
246 &self.id
247 }
248
249 pub fn servers(&self) -> &Arc<SmartCMServerList> {
251 &self.servers
252 }
253
254 pub fn local_ip(&self) -> Option<IpAddr> {
256 self.connection.read().as_ref().and_then(|connection| connection.local_ip())
257 }
258
259 pub fn current_end_point(&self) -> Option<SocketAddr> {
261 self.connection.read().as_ref().and_then(|connection| connection.current_end_point())
262 }
263
264 pub fn public_ip(&self) -> Option<IpAddr> {
266 *self.public_ip.read()
267 }
268
269 pub fn ip_country_code(&self) -> Option<String> {
271 self.ip_country_code.read().clone()
272 }
273
274 pub fn user_country_code(&self) -> Option<String> {
276 self.user_country_code.read().clone()
277 }
278
279 pub fn universe(&self) -> crate::base::generated::steam_language::EUniverse {
281 self.configuration.universe()
282 }
283
284 pub fn is_connected(&self) -> bool {
286 self.is_connected.load(Ordering::SeqCst)
287 }
288
289 pub fn session_token(&self) -> u64 {
291 self.session_token.load(Ordering::SeqCst)
292 }
293
294 pub fn cell_id(&self) -> Option<u32> {
296 *self.cell_id.read()
297 }
298
299 pub fn session_id(&self) -> Option<i32> {
301 *self.session_id.read()
302 }
303
304 pub fn steam_id(&self) -> Option<SteamID> {
306 *self.steam_id.read()
307 }
308
309 pub fn connection_timeout(&self) -> Duration {
311 self.configuration.connection_timeout()
312 }
313
314 pub fn debug_network_listener(&self) -> Option<Arc<dyn IDebugNetworkListener>> {
316 self.debug_network_listener.read().clone()
317 }
318
319 pub fn set_debug_network_listener(&self, listener: Option<Arc<dyn IDebugNetworkListener>>) {
321 *self.debug_network_listener.write() = listener;
322 }
323
324 pub fn expect_disconnection(&self) -> bool {
326 self.expect_disconnection.load(Ordering::SeqCst)
327 }
328
329 pub fn set_expect_disconnection(&self, expect: bool) {
331 self.expect_disconnection.store(expect, Ordering::SeqCst);
332 }
333
334 pub fn set_connection_factory(&self, factory: ConnectionFactory) {
336 *self.connection_factory.write() = Some(factory);
337 }
338
339 pub fn log_debug(&self, category: &str, message: &str) {
342 let full_category = format!("{}/{}", self.id, category);
343
344 tracing::debug!(client = %self.id, category = %full_category, "{}", message);
345
346 DebugLog::write_line(&full_category, message);
347 }
348
349 pub async fn connect(self: &Arc<Self>, cm_server: Option<ServerRecord>) -> Result<(), SteamError> {
356 self.disconnect_with(true).await;
357
358 *self.pending_logon.write() = None;
360 self.consecutive_switches.store(0, Ordering::SeqCst);
361 self.reconnect_requested.store(false, Ordering::SeqCst);
362 let generation = self.generation.fetch_add(1, Ordering::SeqCst) + 1;
363
364 let cancellation = CancellationToken::new();
365 *self.connection_cancellation.write() = Some(cancellation.clone());
366 self.expect_disconnection.store(false, Ordering::SeqCst);
367 self.connected_handled.store(false, Ordering::SeqCst);
368
369 let record = match cm_server {
370 Some(record) => Some(record),
371 None => {
372 self.servers
373 .get_next_server_candidate_async(self.configuration.protocol_types())
374 .await
375 }
376 };
377
378 let Some(record) = record else {
379 self.log_debug("CMClient", "Server record task returned no result.");
380 self.announced_generation.fetch_max(generation, Ordering::SeqCst);
381 self.on_client_disconnected(false).await;
382 return Err(SteamError::Connection("no server candidate is available".to_owned()));
383 };
384
385 let end_point = record.end_point();
387
388 let session = match self.open_session(record, generation).await {
389 Ok(session) => session,
390 Err(error) => {
391 self.announced_generation.fetch_max(generation, Ordering::SeqCst);
393 return Err(error);
394 }
395 };
396 let connect_target = session.connection.clone();
397
398 let this = Arc::clone(self);
399 let task_cancellation = cancellation.clone();
400 tokio::spawn(async move {
401 this.dispatch_loop(session, task_cancellation, generation).await;
402 });
403
404 if let Err(error) = connect_target.connect(end_point, self.connection_timeout()).await {
405 self.log_debug("CMClient", &format!("Unhandled exception when attempting to connect to Steam: {}", error));
406 cancellation.cancel();
411 self.end_session(generation, &connect_target, false).await;
412 return Err(error);
413 }
414
415 Ok(())
416 }
417
418 pub async fn disconnect(&self) {
420 self.disconnect_with(true).await;
421 }
422
423 pub async fn disconnect_with(&self, user_initiated: bool) {
425 let generation = self.generation.load(Ordering::SeqCst);
426
427 self.stop_heart_beat();
428
429 if let Some(cancellation) = self.connection_cancellation.write().take() {
432 cancellation.cancel();
433 }
434
435 *self.outbound.write() = None;
437
438 let connection = self.connection.write().take();
439 if let Some(connection) = &connection {
440 connection.disconnect(user_initiated).await;
441 }
442
443 if self.claim_disconnect_announcement(generation) {
446 self.teardown(connection.as_ref(), user_initiated).await;
447 }
448 }
449
450 async fn open_session(
457 self: &Arc<Self>,
458 record: ServerRecord,
459 generation: u64,
460 ) -> Result<ConnectionSession, SteamError> {
461 let protocol = ProtocolTypes(
462 record.protocol_types().raw() & self.configuration.protocol_types().raw(),
463 );
464
465 let factory = self.connection_factory.read().clone();
466 let connection = match factory {
467 Some(factory) => factory(self, &record, protocol)?,
468 None => default_connection_factory(self, &record, protocol)?,
469 };
470
471 let events = connection.events();
473
474 let (outbound_tx, mut outbound_rx) = mpsc::unbounded_channel::<Bytes>();
476
477 {
478 let mut slot = self.connection.write();
479 if self.generation.load(Ordering::SeqCst) != generation {
480 return Err(SteamError::Cancelled);
481 }
482 *slot = Some(connection.clone());
483 *self.outbound.write() = Some(outbound_tx);
484 }
485
486 let writer_connection = connection.clone();
487 tokio::spawn(async move {
488 while let Some(bytes) = outbound_rx.recv().await {
489 if let Err(error) = writer_connection.send(bytes).await {
490 tracing::debug!(%error, "failed to send a message to Steam");
491 }
492 }
493 });
494
495 Ok(ConnectionSession { connection, events })
496 }
497
498 fn release_connection(&self, generation: u64, connection: &Arc<dyn Connection>) {
501 let mut slot = self.connection.write();
502 if self.generation.load(Ordering::SeqCst) != generation {
503 return;
504 }
505 if slot.as_ref().is_some_and(|current| same_connection(current, connection)) {
506 *slot = None;
507 *self.outbound.write() = None;
508 }
509 }
510
511 fn claim_disconnect_announcement(&self, generation: u64) -> bool {
515 self.announced_generation.fetch_max(generation, Ordering::SeqCst) < generation
516 }
517
518 fn can_resume_session(&self) -> bool {
522 !self.expect_disconnection.load(Ordering::SeqCst) && self.pending_logon.read().is_some()
523 }
524
525 fn record_switch_attempt(&self) -> bool {
528 self.consecutive_switches.fetch_add(1, Ordering::SeqCst) < MAX_CONSECUTIVE_SERVER_SWITCHES
529 }
530
531 async fn end_session(
538 &self,
539 generation: u64,
540 connection: &Arc<dyn Connection>,
541 user_initiated: bool,
542 ) {
543 let current = {
544 let mut slot = self.connection.write();
545 if self.generation.load(Ordering::SeqCst) != generation {
546 return;
547 }
548 let current = slot.take();
549 *self.outbound.write() = None;
550 current
551 };
552
553 if !self.claim_disconnect_announcement(generation) {
554 return;
555 }
556
557 let connection = current.unwrap_or_else(|| connection.clone());
558 self.teardown(Some(&connection), user_initiated).await;
559 }
560
561 async fn switch_session(
571 self: &Arc<Self>,
572 session: &mut ConnectionSession,
573 cancellation: &CancellationToken,
574 generation: u64,
575 ) -> Result<(), SteamError> {
576 self.stop_heart_beat();
577
578 let old_connection = {
582 let mut slot = self.connection.write();
583 if self.generation.load(Ordering::SeqCst) != generation {
584 return Err(SteamError::Cancelled);
585 }
586 *self.outbound.write() = None;
587 slot.take()
588 };
589
590 let mut last_error = None;
591
592 for _ in 0..SWITCH_ATTEMPTS {
593 if cancellation.is_cancelled() {
594 last_error = Some(SteamError::Cancelled);
595 break;
596 }
597
598 let mut record = self
599 .servers
600 .get_next_server_candidate_async(self.configuration.protocol_types())
601 .await;
602
603 if record.is_none() {
604 let _ = self.servers.force_refresh_server_list().await;
607
608 record = self
609 .servers
610 .get_next_server_candidate_async(self.configuration.protocol_types())
611 .await;
612 }
613
614 let Some(record) = record else {
615 break;
616 };
617
618 let end_point = record.end_point();
619 self.log_debug(
620 "CMClient",
621 &format!("Switching to another CM server: {end_point}"),
622 );
623
624 let new_session = match self.open_session(record, generation).await {
625 Ok(new_session) => new_session,
626 Err(SteamError::Cancelled) => {
627 last_error = Some(SteamError::Cancelled);
628 break;
629 }
630 Err(error) => {
631 last_error = Some(error);
632 continue;
633 }
634 };
635 let new_connection = new_session.connection.clone();
636
637 match new_connection
638 .connect(end_point, self.connection_timeout())
639 .await
640 {
641 Ok(()) if cancellation.is_cancelled() => {
642 self.release_connection(generation, &new_connection);
645 new_connection.disconnect(true).await;
646 last_error = Some(SteamError::Cancelled);
647 break;
648 }
649 Ok(()) => {
650 *session = new_session;
653
654 self.connected_handled.store(false, Ordering::SeqCst);
657 self.is_connected.store(false, Ordering::SeqCst);
658 *self.session_id.write() = None;
659 *self.steam_id.write() = None;
660
661 if let Some(old_connection) = old_connection {
662 old_connection.disconnect(false).await;
663 }
664
665 return Ok(());
666 }
667 Err(error) => {
668 self.log_debug(
671 "CMClient",
672 &format!("The CM server at {end_point} did not answer: {error}"),
673 );
674 last_error = Some(error);
675
676 self.release_connection(generation, &new_connection);
677 }
678 }
679 }
680
681 if let Some(old_connection) = old_connection {
682 old_connection.disconnect(false).await;
683 }
684
685 Err(last_error.unwrap_or_else(|| {
686 SteamError::Connection("no server candidate is available".to_owned())
687 }))
688 }
689
690 pub fn send(&self, msg: &dyn IClientMsg) -> Result<(), SteamError> {
695 let serialized = self.serialize_message(msg);
696
697 self.log_debug("CMClient", &format!("CM: --> {:?}", msg.msg_type()));
698
699 if let Some(listener) = self.debug_network_listener.read().clone() {
700 listener.on_outgoing_network_message(msg.msg_type(), &serialized);
701 }
702
703 if msg.msg_type() == EMsg::ClientLogon {
705 *self.pending_logon.write() = Some(serialized.clone());
706 }
707
708 let outbound = self.outbound.read().clone();
709 let Some(outbound) = outbound else {
710 return Err(SteamError::Connection(
711 "the client is not connected to Steam".to_owned(),
712 ));
713 };
714
715 outbound
716 .send(Bytes::from(serialized))
717 .map_err(|_| SteamError::Connection("the connection to Steam has been closed".to_owned()))
718 }
719
720 fn serialize_message(&self, msg: &dyn IClientMsg) -> Vec<u8> {
722 let session_id = *self.session_id.read();
723 let steam_id = *self.steam_id.read();
724
725 if session_id.is_none() && steam_id.is_none() {
726 return msg.serialize();
727 }
728
729 stamp_identity(msg, msg.serialize(), session_id, steam_id)
730 }
731
732 pub async fn on_client_connected(&self) {
734 let mut request = ClientMsgProtobuf::<CMsgClientHello>::new(EMsg::ClientHello);
735 request.body_mut().protocol_version = Some(CURRENT_PROTOCOL);
736
737 let _ = self.send(&request);
738
739 self.dispatcher.on_client_connected(self).await;
740 }
741
742 pub async fn on_client_disconnected(&self, user_initiated: bool) {
744 self.dispatcher.on_client_disconnected(self, user_initiated).await;
745 }
746
747 pub fn get_packet_msg(&self, data: Vec<u8>) -> Option<Box<dyn IPacketMsg>> {
749 if data.len() < 4 {
750 self.log_debug(
751 "CMClient",
752 &format!(
753 "PacketMsg too small to contain a message, was only {} bytes. Message: 0x{}",
754 data.len(),
755 hex::encode(&data)
756 ),
757 );
758 return None;
759 }
760
761 let raw = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
762 let e_msg = EMsg::from_value((raw & !PROTO_MASK) as i32).unwrap_or(EMsg::Invalid);
763
764 match e_msg {
765 EMsg::ChannelEncryptRequest | EMsg::ChannelEncryptResponse | EMsg::ChannelEncryptResult => {
767 return match PacketMsg::new(e_msg, data) {
768 Ok(packet) => Some(Box::new(packet) as Box<dyn IPacketMsg>),
769 Err(error) => {
770 self.log_debug("CMClient", &format!("Exception deserializing emsg {}: {}", e_msg.value(), error));
771 None
772 }
773 };
774 }
775 _ => {}
776 }
777
778 let result = if raw & PROTO_MASK != 0 {
779 PacketClientMsgProtobuf::new(e_msg, data).map(|packet| Box::new(packet) as Box<dyn IPacketMsg>)
780 } else {
781 PacketClientMsg::new(e_msg, data).map(|packet| Box::new(packet) as Box<dyn IPacketMsg>)
782 };
783
784 match result {
785 Ok(packet) => Some(packet),
786 Err(error) => {
787 self.log_debug(
788 "CMClient",
789 &format!("Exception deserializing emsg {} ({}): {}", e_msg.value(), raw & PROTO_MASK != 0, error),
790 );
791 None
792 }
793 }
794 }
795
796 async fn dispatch_loop(
798 self: Arc<Self>,
799 mut session: ConnectionSession,
800 cancellation: CancellationToken,
801 generation: u64,
802 ) {
803 loop {
804 if self.reconnect_requested.swap(false, Ordering::SeqCst) {
805 if !self.record_switch_attempt() {
806 self.log_debug(
807 "CMClient",
808 "Not moving to another CM server: too many moves without a logon",
809 );
810 self.end_session(generation, &session.connection, false).await;
811 break;
812 }
813
814 if let Err(error) = self.switch_session(&mut session, &cancellation, generation).await {
815 self.log_debug(
816 "CMClient",
817 &format!("Unable to switch to another CM server: {error}"),
818 );
819 let user_initiated = cancellation.is_cancelled();
820 self.end_session(generation, &session.connection, user_initiated).await;
821 break;
822 }
823 continue;
824 }
825
826 let event = tokio::select! {
829 biased;
830 _ = cancellation.cancelled() => break,
831 event = session.events.recv() => event,
832 };
833
834 match event {
835 Some(ConnectionEvent::NetMsgReceived(args)) => {
836 let packet = self.get_packet_msg(args.data);
837 let Some(packet) = packet else {
838 self.log_debug("CMClient", "Packet message failed to parse, shutting down connection");
839 self.disconnect_with(false).await;
840 break;
841 };
842
843 self.log_debug(
844 "CMClient",
845 &format!("CM: <-- {:?}", packet.msg_type()),
846 );
847
848 if !self.dispatch_client_msg(Some(packet.as_ref())).await {
849 break;
850 }
851 }
852 Some(ConnectionEvent::Connected) => {
853 self.handle_connected(&session.connection).await;
854 }
855 Some(ConnectionEvent::Disconnected(args)) => {
856 let resume = !args.user_initiated && self.can_resume_session();
861 if !resume {
862 self.end_session(generation, &session.connection, args.user_initiated)
863 .await;
864 break;
865 }
866
867 if !self.record_switch_attempt() {
868 self.log_debug(
869 "CMClient",
870 "Not reconnecting to Steam: too many reconnections without a logon",
871 );
872 self.end_session(generation, &session.connection, false).await;
873 break;
874 }
875
876 if let Err(error) = self.switch_session(&mut session, &cancellation, generation).await {
877 self.log_debug(
878 "CMClient",
879 &format!("Unable to reconnect to Steam: {error}"),
880 );
881 let user_initiated = cancellation.is_cancelled();
882 self.end_session(generation, &session.connection, user_initiated).await;
883 break;
884 }
885 }
886 None => {
887 self.end_session(generation, &session.connection, false).await;
889 break;
890 }
891 }
892 }
893 }
894
895 async fn handle_connected(&self, connection: &Arc<dyn Connection>) {
897 if self.connected_handled.swap(true, Ordering::SeqCst) {
898 return;
899 }
900
901 if let Some(end_point) = connection.current_end_point() {
902 self.servers.try_mark(end_point, connection.protocol_types(), ServerQuality::Good);
903 }
904
905 self.is_connected.store(true, Ordering::SeqCst);
906
907 self.on_client_connected().await;
908
909 let pending = self.pending_logon.read().clone();
912 if let Some(bytes) = pending {
913 let outbound = self.outbound.read().clone();
914 if let Some(outbound) = outbound {
915 let _ = outbound.send(Bytes::from(bytes));
916 }
917 }
918 }
919
920 async fn dispatch_client_msg(self: &Arc<Self>, packet: Option<&dyn IPacketMsg>) -> bool {
923 let Some(packet) = packet else {
924 self.log_debug("CMClient", "Packet message failed to parse, shutting down connection");
925 self.disconnect_with(false).await;
926 return false;
927 };
928
929 if !self.on_client_msg_received(packet).await {
930 return false;
931 }
932
933 self.dispatcher.on_client_msg_received(self, packet).await
934 }
935
936 async fn on_client_msg_received(self: &Arc<Self>, packet: &dyn IPacketMsg) -> bool {
938 if packet.msg_type() != EMsg::Multi {
940 if let Some(listener) = self.debug_network_listener.read().clone() {
941 listener.on_incoming_network_message(packet.msg_type(), packet.get_data());
942 }
943 }
944
945 match packet.msg_type() {
946 EMsg::Multi => return self.handle_multi(packet).await,
947 EMsg::ClientLogOnResponse => self.handle_logon_response(packet).await,
948 EMsg::ClientLoggedOff => self.handle_logged_off(packet).await,
949 EMsg::ClientServerUnavailable => self.handle_server_unavailable(packet).await,
950 EMsg::ClientSessionToken => self.handle_session_token(packet).await,
951 _ => {}
952 }
953
954 true
955 }
956
957 async fn handle_multi(self: &Arc<Self>, packet: &dyn IPacketMsg) -> bool {
959 if !packet.is_proto() {
960 self.log_debug("CMClient", "HandleMulti got non-proto MsgMulti!!");
961 return true;
962 }
963
964 let Ok(msg_multi) = ClientMsgProtobuf::<CMsgMulti>::from_packet(packet) else {
965 return true;
966 };
967
968 let payload = msg_multi.body().message_body.clone().unwrap_or_default();
969 let unzipped = msg_multi.body().size_unzipped.unwrap_or(0);
970
971 let data = if unzipped > 0 {
972 match decompress_gzip(&payload) {
973 Ok(data) => data,
974 Err(error) => {
975 self.log_debug("CMClient", &format!("Failed to decompress multi message: {}", error));
976 return true;
977 }
978 }
979 } else {
980 payload
981 };
982
983 let mut offset = 0usize;
984 while offset + 4 <= data.len() {
985 let sub_size = i32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]]);
986 offset += 4;
987
988 if sub_size <= 0 {
989 break;
990 }
991
992 let sub_size = sub_size as usize;
993 if offset + sub_size > data.len() {
994 break;
995 }
996
997 let sub_data = data[offset..offset + sub_size].to_vec();
998 offset += sub_size;
999
1000 match self.get_packet_msg(sub_data) {
1001 Some(sub_packet) => {
1002 if !Box::pin(self.dispatch_client_msg(Some(sub_packet.as_ref()))).await {
1005 return false;
1006 }
1007 }
1008 None => return Box::pin(self.dispatch_client_msg(None)).await,
1012 }
1013 }
1014
1015 true
1016 }
1017
1018 async fn handle_logon_response(self: &Arc<Self>, packet: &dyn IPacketMsg) {
1019 if !packet.is_proto() {
1020 self.log_debug(
1023 "CMClient",
1024 "Got non-proto logon response, this is indicative of no logon attempt after connecting.",
1025 );
1026 return;
1027 }
1028
1029 let Ok(response) = ClientMsgProtobuf::<CMsgClientLogonResponse>::from_packet(packet) else {
1030 return;
1031 };
1032
1033 let logon_result = response
1034 .body()
1035 .eresult
1036 .and_then(EResult::from_value)
1037 .unwrap_or(EResult::Invalid);
1038
1039 if logon_result == EResult::OK {
1040 let header = response.proto_header();
1041
1042 *self.session_id.write() = header.client_sessionid;
1043 *self.steam_id.write() = header.steamid.map(SteamID::from_id);
1044 *self.cell_id.write() = response.body().cell_id;
1045 *self.public_ip.write() = response
1046 .body()
1047 .public_ip
1048 .as_ref()
1049 .and_then(NetHelpers::get_ip_address_from_msg);
1050 *self.ip_country_code.write() = response.body().ip_country_code.clone();
1051 *self.user_country_code.write() = response.body().user_country.clone();
1052
1053 let heartbeat_seconds = response.body().legacy_out_of_game_heartbeat_seconds.unwrap_or(0);
1054
1055 self.restart_heart_beat(Duration::from_secs(heartbeat_seconds.max(0) as u64));
1056
1057 self.consecutive_switches.store(0, Ordering::SeqCst);
1059 } else if logon_result == EResult::TryAnotherCM || logon_result == EResult::ServiceUnavailable {
1060 let connection = self.connection.read().clone();
1061 if let Some(connection) = connection {
1062 if let Some(end_point) = connection.current_end_point() {
1063 self.servers.try_mark(end_point, connection.protocol_types(), ServerQuality::Bad);
1064 }
1065 }
1066
1067 self.reconnect_requested.store(true, Ordering::SeqCst);
1070 } else {
1071 *self.pending_logon.write() = None;
1075 }
1076 }
1077
1078 async fn handle_logged_off(self: &Arc<Self>, packet: &dyn IPacketMsg) {
1079 *self.session_id.write() = None;
1080 *self.steam_id.write() = None;
1081 *self.cell_id.write() = None;
1082 *self.public_ip.write() = None;
1083 *self.ip_country_code.write() = None;
1084 *self.user_country_code.write() = None;
1085
1086 self.stop_heart_beat();
1087
1088 *self.pending_logon.write() = None;
1092
1093 if !packet.is_proto() {
1094 return;
1095 }
1096
1097 let Ok(logoff) = ClientMsgProtobuf::<CMsgClientLoggedOff>::from_packet(packet) else {
1098 return;
1099 };
1100
1101 let logoff_result = logoff
1102 .body()
1103 .eresult
1104 .and_then(EResult::from_value)
1105 .unwrap_or(EResult::Invalid);
1106
1107 if logoff_result == EResult::TryAnotherCM || logoff_result == EResult::ServiceUnavailable {
1108 let connection = self.connection.read().clone();
1109 if let Some(connection) = connection {
1110 if let Some(end_point) = connection.current_end_point() {
1111 self.servers.try_mark(end_point, connection.protocol_types(), ServerQuality::Bad);
1112 }
1113 }
1114 }
1115 }
1116
1117 async fn handle_server_unavailable(self: &Arc<Self>, packet: &dyn IPacketMsg) {
1118 self.log_debug(
1119 "SteamClient",
1120 &format!("A server was not available for request: '{}'", packet.msg_type().value()),
1121 );
1122 self.disconnect_with(false).await;
1123 }
1124
1125 async fn handle_session_token(self: &Arc<Self>, packet: &dyn IPacketMsg) {
1126 let Ok(token) = ClientMsgProtobuf::<CMsgClientSessionToken>::from_packet(packet) else {
1127 return;
1128 };
1129
1130 self.session_token.store(token.body().token.unwrap_or(0), Ordering::SeqCst);
1131 }
1132
1133 async fn teardown(&self, connection: Option<&Arc<dyn Connection>>, user_initiated: bool) {
1136 self.is_connected.store(false, Ordering::SeqCst);
1137
1138 let expect_disconnection = self.expect_disconnection.load(Ordering::SeqCst);
1139
1140 if !user_initiated && !expect_disconnection {
1141 if let Some(connection) = connection {
1142 if let Some(end_point) = connection.current_end_point() {
1143 self.servers.try_mark(end_point, connection.protocol_types(), ServerQuality::Bad);
1144 }
1145 }
1146 }
1147
1148 if user_initiated {
1149 *self.pending_logon.write() = None;
1151 }
1152
1153 *self.session_id.write() = None;
1154 *self.steam_id.write() = None;
1155
1156 self.stop_heart_beat();
1157
1158 self.on_client_disconnected(user_initiated || expect_disconnection).await;
1159 }
1160
1161 fn restart_heart_beat(self: &Arc<Self>, delay: Duration) {
1162 self.stop_heart_beat();
1163
1164 let delay = if delay.is_zero() { Duration::from_secs(1) } else { delay };
1165 let Ok(handle) = tokio::runtime::Handle::try_current() else {
1166 return;
1167 };
1168
1169 let cancel = CancellationToken::new();
1170 let token = cancel.clone();
1171 let this = Arc::clone(self);
1172
1173 let task = handle.spawn(async move {
1174 let mut interval = tokio::time::interval(delay);
1175 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
1176
1177 loop {
1178 tokio::select! {
1179 _ = token.cancelled() => break,
1180 _ = interval.tick() => {
1181 let request = ClientMsgProtobuf::<CMsgClientHeartBeat>::new(EMsg::ClientHeartBeat);
1182 let _ = this.send(&request);
1183 }
1184 }
1185 }
1186 });
1187
1188 *self.heart_beat.write() = Some(HeartBeat { cancel, task });
1189 }
1190
1191 fn stop_heart_beat(&self) {
1192 if let Some(heart_beat) = self.heart_beat.write().take() {
1193 heart_beat.cancel.cancel();
1194 heart_beat.task.abort();
1195 }
1196 }
1197}
1198
1199fn stamp_identity(
1203 msg: &dyn IClientMsg,
1204 mut bytes: Vec<u8>,
1205 session_id: Option<i32>,
1206 steam_id: Option<SteamID>,
1207) -> Vec<u8> {
1208 if msg.is_proto() {
1209 if bytes.len() < 8 {
1212 return bytes;
1213 }
1214
1215 let header_length = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1216 if header_length < 0 {
1217 return bytes;
1218 }
1219
1220 let header_length = header_length as usize;
1221 if bytes.len() < 8 + header_length {
1222 return bytes;
1223 }
1224
1225 let Ok(mut header) = CMsgProtoBufHeader::decode(&bytes[8..8 + header_length]) else {
1226 return bytes;
1227 };
1228
1229 if let Some(session_id) = session_id {
1230 header.client_sessionid = Some(session_id);
1231 }
1232 if let Some(steam_id) = steam_id {
1233 header.steamid = Some(steam_id.raw_u64());
1234 }
1235
1236 let encoded = header.encode_to_vec();
1237
1238 let mut stamped = Vec::with_capacity(bytes.len() + encoded.len());
1239 stamped.extend_from_slice(&bytes[..4]);
1240 stamped.extend_from_slice(&(encoded.len() as i32).to_le_bytes());
1241 stamped.extend_from_slice(&encoded);
1242 stamped.extend_from_slice(&bytes[8 + header_length..]);
1243 return stamped;
1244 }
1245
1246 if bytes.len() >= EXTENDED_HEADER_SIZE && bytes[4] as usize == EXTENDED_HEADER_SIZE {
1250 if let Some(steam_id) = steam_id {
1251 bytes[EXTENDED_HEADER_STEAM_ID_OFFSET..EXTENDED_HEADER_STEAM_ID_OFFSET + 8]
1252 .copy_from_slice(&steam_id.raw_u64().to_le_bytes());
1253 }
1254 if let Some(session_id) = session_id {
1255 bytes[EXTENDED_HEADER_SESSION_ID_OFFSET..EXTENDED_HEADER_SESSION_ID_OFFSET + 4]
1256 .copy_from_slice(&session_id.to_le_bytes());
1257 }
1258 }
1259
1260 bytes
1261}
1262
1263fn same_connection(a: &Arc<dyn Connection>, b: &Arc<dyn Connection>) -> bool {
1267 std::ptr::eq(Arc::as_ptr(a) as *const (), Arc::as_ptr(b) as *const ())
1268}
1269
1270fn decompress_gzip(data: &[u8]) -> Result<Vec<u8>, SteamError> {
1271 let mut decoder = flate2::read::GzDecoder::new(data);
1272 let mut decompressed = Vec::new();
1273 decoder
1274 .read_to_end(&mut decompressed)
1275 .map_err(|error| SteamError::Decompression(error.to_string()))?;
1276 Ok(decompressed)
1277}