Barotrauma Server Doc
SteamP2PServerPeer.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 
5 namespace Barotrauma.Networking
6 {
7  internal sealed class SteamP2PServerPeer : ServerPeer
8  {
9  private bool started;
10 
11  private readonly SteamId ownerSteamId;
12 
13  private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
14 
15  private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
16  private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
17 
18  public SteamP2PServerPeer(SteamId steamId, int ownerKey, ServerSettings settings, Callbacks callbacks) : base(callbacks)
19  {
20  serverSettings = settings;
21 
22  connectedClients = new List<NetworkConnection>();
23  pendingClients = new List<PendingClient>();
24 
25  this.ownerKey = Option<int>.Some(ownerKey);
26 
27  ownerSteamId = steamId;
28 
29  started = false;
30  }
31 
32  public override void Start()
33  {
34  var headers = new PeerPacketHeaders
35  {
36  DeliveryMethod = DeliveryMethod.Reliable,
37  PacketHeader = PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage,
38  Initialization = null
39  };
40  SendMsgInternal(ownerSteamId, headers, null);
41 
42  started = true;
43  }
44 
45  public override void Close()
46  {
47  if (!started) { return; }
48 
49  if (OwnerConnection != null) { OwnerConnection.Status = NetworkConnectionStatus.Disconnected; }
50 
51  for (int i = pendingClients.Count - 1; i >= 0; i--)
52  {
53  RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
54  }
55 
56  for (int i = connectedClients.Count - 1; i >= 0; i--)
57  {
58  Disconnect(connectedClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
59  }
60 
61  pendingClients.Clear();
62  connectedClients.Clear();
63 
64  ChildServerRelay.ShutDown();
65 
66  callbacks.OnShutdown.Invoke();
67  }
68 
69  public override void Update(float deltaTime)
70  {
71  if (!started) { return; }
72 
73  //backwards for loop so we can remove elements while iterating
74  for (int i = connectedClients.Count - 1; i >= 0; i--)
75  {
76  SteamP2PConnection conn = (SteamP2PConnection)connectedClients[i];
77  conn.Decay(deltaTime);
78  if (conn.Timeout < 0.0)
79  {
80  Disconnect(conn, PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
81  }
82  }
83 
84  try
85  {
86  while (ChildServerRelay.Read(out byte[] incBuf))
87  {
88  IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection);
89 
90  HandleDataMessage(inc);
91  }
92  }
93 
94  catch (Exception e)
95  {
96  string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
97  GameAnalyticsManager.AddErrorEventOnce($"SteamP2PServerPeer.Update:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
98 #if DEBUG
99  DebugConsole.ThrowError(errorMsg);
100 #else
101  if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
102 #endif
103  }
104 
105  for (int i = 0; i < pendingClients.Count; i++)
106  {
107  PendingClient pendingClient = pendingClients[i];
108  UpdatePendingClient(pendingClient);
109  if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
110  }
111  }
112 
113  private void HandleDataMessage(IReadMessage inc)
114  {
115  if (!started) { return; }
116 
117  SteamId senderSteamId = ReadSteamId(inc);
118  SteamId sentOwnerSteamId = ReadSteamId(inc);
119 
120  var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
121 
122  if (packetHeader.IsServerMessage())
123  {
124  DebugConsole.ThrowError($"Got server message from {senderSteamId}");
125  return;
126  }
127 
128  if (senderSteamId != ownerSteamId) //sender is remote, handle disconnects and heartbeats
129  {
130  bool connectionMatches(NetworkConnection conn) =>
131  conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var steamId } }
132  && steamId == senderSteamId;
133 
134  PendingClient? pendingClient = pendingClients.Find(c => connectionMatches(c.Connection));
135  SteamP2PConnection? connectedClient = connectedClients.Find(connectionMatches) as SteamP2PConnection;
136 
137  pendingClient?.Heartbeat();
138  connectedClient?.Heartbeat();
139 
140  if (packetHeader.IsConnectionInitializationStep())
141  {
142  if (!initialization.HasValue) { return; }
143  ConnectionInitialization initializationStep = initialization.Value;
144 
145  if (pendingClient != null)
146  {
147  pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId));
148  ReadConnectionInitializationStep(
149  pendingClient,
150  new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits, false),
151  initializationStep);
152  }
153  else if (initializationStep == ConnectionInitialization.ConnectionStarted)
154  {
155  pendingClient = new PendingClient(new SteamP2PConnection(senderSteamId));
156  pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId));
157  pendingClients.Add(pendingClient);
158  }
159  }
160  else if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason) ||
161  serverSettings.BanList.IsBanned(sentOwnerSteamId, out banReason))
162  {
163  if (pendingClient != null)
164  {
165  RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
166  }
167  else if (connectedClient != null)
168  {
169  Disconnect(connectedClient, PeerDisconnectPacket.Banned(banReason));
170  }
171  }
172  else if (packetHeader.IsDisconnectMessage())
173  {
174  if (pendingClient != null)
175  {
176  RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
177  }
178  else if (connectedClient != null)
179  {
180  Disconnect(connectedClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
181  }
182  }
183  else if (packetHeader.IsHeartbeatMessage())
184  {
185  //message exists solely as a heartbeat, ignore its contents
186  return;
187  }
188  else if (connectedClient != null)
189  {
190  var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
191  IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, connectedClient);
192  callbacks.OnMessageReceived.Invoke(connectedClient, msg);
193  }
194  }
195  else //sender is owner
196  {
197  (OwnerConnection as SteamP2PConnection)?.Heartbeat();
198 
199  if (packetHeader.IsDisconnectMessage())
200  {
201  DebugConsole.ThrowError("Received disconnect message from owner");
202  return;
203  }
204 
205  if (packetHeader.IsServerMessage())
206  {
207  DebugConsole.ThrowError("Received server message from owner");
208  return;
209  }
210 
211  if (packetHeader.IsConnectionInitializationStep())
212  {
213  if (OwnerConnection is null)
214  {
215  var packet = INetSerializableStruct.Read<SteamP2PInitializationOwnerPacket>(inc);
216  OwnerConnection = new SteamP2PConnection(ownerSteamId)
217  {
218  Language = GameSettings.CurrentConfig.Language
219  };
220  OwnerConnection.SetAccountInfo(new AccountInfo(ownerSteamId, ownerSteamId));
221 
222  callbacks.OnInitializationComplete.Invoke(OwnerConnection, packet.OwnerName);
223  callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
224  }
225 
226  return;
227  }
228 
229  if (packetHeader.IsHeartbeatMessage())
230  {
231  return;
232  }
233  else
234  {
235  var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
236  IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, OwnerConnection);
237  callbacks.OnMessageReceived.Invoke(OwnerConnection!, msg);
238  }
239  }
240  }
241 
242  public override void InitializeSteamServerCallbacks()
243  {
244  throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
245  }
246 
247  public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
248  {
249  if (!started) { return; }
250 
251  if (conn is not SteamP2PConnection steamP2PConn) { return; }
252 
253  if (!connectedClients.Contains(steamP2PConn) && conn != OwnerConnection)
254  {
255  DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {steamP2PConn.AccountInfo.AccountId}");
256  return;
257  }
258 
259  if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId) { return; }
260 
261  byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
262 
263  var headers = new PeerPacketHeaders
264  {
265  DeliveryMethod = deliveryMethod,
266  PacketHeader = (isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)
267  | PacketHeader.IsServerMessage,
268  Initialization = null
269  };
270  var body = new PeerPacketMessage
271  {
272  Buffer = bufAux
273  };
274  SendMsgInternal(steamP2PConn, headers, body);
275  }
276 
277  private void SendDisconnectMessage(SteamId steamId, PeerDisconnectPacket peerDisconnectPacket)
278  {
279  if (!started) { return; }
280 
281  var headers = new PeerPacketHeaders
282  {
283  DeliveryMethod = DeliveryMethod.Reliable,
284  PacketHeader = PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage,
285  Initialization = null
286  };
287 
288  SendMsgInternal(steamId, headers, peerDisconnectPacket);
289  }
290 
291  public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
292  {
293  if (!started) { return; }
294 
295  if (conn is not SteamP2PConnection steamp2pConn) { return; }
296 
297  if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || connAccountId is not SteamId connSteamId) { return; }
298 
299  SendDisconnectMessage(connSteamId, peerDisconnectPacket);
300 
301  if (connectedClients.Contains(steamp2pConn))
302  {
303  steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
304  connectedClients.Remove(steamp2pConn);
305  callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
306  Steam.SteamManager.StopAuthSession(connSteamId);
307  }
308  else if (steamp2pConn == OwnerConnection)
309  {
310  throw new InvalidOperationException("Cannot disconnect owner peer");
311  }
312  }
313 
314  protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
315  {
316  var connSteamId = conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var id } } ? id : null;
317  if (connSteamId is null) { return; }
318 
319  SendMsgInternal(connSteamId, headers, body);
320  }
321 
322  private void SendMsgInternal(SteamId connSteamId, PeerPacketHeaders headers, INetSerializableStruct? body)
323  {
324  IWriteMessage msgToSend = new WriteOnlyMessage();
325  WriteSteamId(msgToSend, connSteamId);
326  msgToSend.WriteNetSerializableStruct(headers);
327  body?.Write(msgToSend);
328 
329  ForwardToOwnerProcess(msgToSend);
330  }
331 
332  private static void ForwardToOwnerProcess(IWriteMessage msg)
333  {
334  byte[] bufToSend = (byte[])msg.Buffer.Clone();
335  Array.Resize(ref bufToSend, msg.LengthBytes);
336  ChildServerRelay.Write(bufToSend);
337  }
338 
339  protected override void ProcessAuthTicket(ClientSteamTicketAndVersionPacket packet, PendingClient pendingClient)
340  {
341  pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
342  pendingClient.Name = packet.Name;
343  pendingClient.AuthSessionStarted = true;
344  }
345  }
346 }