Server LuaCsForBarotrauma
FileSender.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using Barotrauma.IO;
5 using System.Linq;
6 using System.Threading;
7 
8 namespace Barotrauma.Networking
9 {
10  class FileSender
11  {
12  public class FileTransferOut
13  {
15 
16  public string FileName
17  {
18  get;
19  private set;
20  }
21 
22  public string FilePath
23  {
24  get;
25  private set;
26  }
27 
29  {
30  get;
31  private set;
32  }
33 
34  public float Progress
35  {
36  get { return KnownReceivedOffset / (float)Data.Length; }
37  }
38 
39  private float waitTimer;
40  public float WaitTimer
41  {
42  get => waitTimer;
43  set
44  {
45  if (value > 0.0f)
46  {
47  //setting a wait timer means that network conditions
48  //aren't ideal, slow down the packet rate
49  PacketsPerUpdate = Math.Max(PacketsPerUpdate / 4.0f, 1.0f);
50  }
51  waitTimer = value;
52  }
53  }
54 
55  public static int MaxPacketsPerUpdate = 10;
56  public float PacketsPerUpdate { get; set; } = 1.0f;
57 
58  public byte[] Data { get; }
59 
60  public bool Acknowledged;
61 
62  public int SentOffset
63  {
64  get;
65  set;
66  }
67 
68  public int KnownReceivedOffset;
69 
70  public NetworkConnection Connection { get; }
71 
72  public DateTime StartingTime { get; }
73 
74  public int ID;
75 
76  public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
77  {
78  Connection = recipient;
79 
80  FileType = fileType;
81  FilePath = filePath;
82  FileName = Path.GetFileName(filePath);
83 
84  Acknowledged = false;
85  SentOffset = 0;
87 
88  Status = FileTransferStatus.NotStarted;
89 
90  StartingTime = DateTime.Now;
91 
92  int maxRetries = 4;
93  for (int i = 0; i <= maxRetries; i++)
94  {
95  try
96  {
97  Data = File.ReadAllBytes(filePath, catchUnauthorizedAccessExceptions: false);
98  }
99  catch (System.IO.IOException e)
100  {
101  if (i >= maxRetries) { throw; }
102  DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}, retrying in 250 ms...", Color.Red);
103  Thread.Sleep(250);
104  }
105  }
106  }
107  }
108 
109  const int MaxTransferCount = 16;
110  const int MaxTransferCountPerRecipient = 5;
111 
112  public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
115 
116  private readonly List<FileTransferOut> activeTransfers;
117 
118  private readonly int chunkLen;
119 
120  private readonly ServerPeer peer;
121 
122  public static DateTime StartTime;
123 #if DEBUG
124  public float ForceMinimumFileTransferDuration { get; set; }
125 #endif
126 
127  public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
128 
129  public FileSender(ServerPeer serverPeer, int mtu)
130  {
131  peer = serverPeer;
132  chunkLen = mtu - 200;
133 
134  activeTransfers = new List<FileTransferOut>();
135  }
136 
137  public FileTransferOut StartTransfer(NetworkConnection recipient, FileTransferType fileType, string filePath)
138  {
139  if (activeTransfers.Count >= MaxTransferCount)
140  {
141  return null;
142  }
143 
144  if (activeTransfers.Count(t => t.Connection == recipient) > MaxTransferCountPerRecipient)
145  {
146  return null;
147  }
148 
149  if (!File.Exists(filePath))
150  {
151  DebugConsole.ThrowError("Failed to initiate file transfer (file \"" + filePath + "\" not found).\n" + Environment.StackTrace);
152  return null;
153  }
154 
155  FileTransferOut transfer = null;
156  try
157  {
158  transfer = new FileTransferOut(recipient, fileType, filePath)
159  {
160  ID = 1
161  };
162  while (activeTransfers.Any(t => t.Connection == recipient && t.ID == transfer.ID))
163  {
164  transfer.ID++;
165  }
166  activeTransfers.Add(transfer);
167  }
168  catch (Exception e)
169  {
170  DebugConsole.ThrowError("Failed to initiate file transfer", e);
171  return null;
172  }
173 
174  StartTime = DateTime.Now;
175 
176  OnStarted(transfer);
177  GameMain.Server.LastClientListUpdateID++;
178 
179  return transfer;
180  }
181 
182  public void Update(float deltaTime)
183  {
184  int numRemoved = activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
185 
186  var endedTransfers = activeTransfers.FindAll(t =>
187  t.Connection.Status != NetworkConnectionStatus.Connected ||
188  t.Status == FileTransferStatus.Finished ||
189  t.Status == FileTransferStatus.Canceled ||
190  t.Status == FileTransferStatus.Error);
191 
192  foreach (FileTransferOut transfer in endedTransfers)
193  {
194  activeTransfers.Remove(transfer);
195  OnEnded(transfer);
196  }
197 
198  foreach (FileTransferOut transfer in activeTransfers)
199  {
200  transfer.WaitTimer -= deltaTime;
201  if (transfer.WaitTimer > 0.0f) { continue; }
202  Send(transfer);
203  }
204 
205  if (numRemoved > 0 || endedTransfers.Count > 0)
206  {
207  GameMain.Server.LastClientListUpdateID++;
208  }
209  }
210 
211  private void Send(FileTransferOut transfer)
212  {
213  // send another part of the file
214  IWriteMessage message;
215 
216  try
217  {
218  //first message; send length, file name etc
219  //wait for acknowledgement before sending data
220  if (!transfer.Acknowledged)
221  {
222  message = new WriteOnlyMessage();
223  message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
224 
225  //if the recipient is the owner of the server (= a client running the server from the main exe)
226  //we don't need to send anything, the client can just read the file directly
227  if (transfer.Connection == GameMain.Server.OwnerConnection)
228  {
229  message.WriteByte((byte)FileTransferMessageType.TransferOnSameMachine);
230  message.WriteByte((byte)transfer.ID);
231  message.WriteByte((byte)transfer.FileType);
232  message.WriteString(transfer.FilePath);
233  peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
234  transfer.Status = FileTransferStatus.Finished;
235  }
236  else
237  {
238  message.WriteByte((byte)FileTransferMessageType.Initiate);
239  message.WriteByte((byte)transfer.ID);
240  message.WriteByte((byte)transfer.FileType);
241  //message.Write((ushort)chunkLen);
242  message.WriteInt32(transfer.Data.Length);
243  message.WriteString(transfer.FileName);
244  peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
245 
246  transfer.Status = FileTransferStatus.Sending;
247 
248  if (GameSettings.CurrentConfig.VerboseLogging)
249  {
250  DebugConsole.Log("Sending file transfer initiation message: ");
251  DebugConsole.Log(" File: " + transfer.FileName);
252  DebugConsole.Log(" Size: " + transfer.Data.Length);
253  DebugConsole.Log(" ID: " + transfer.ID);
254  }
255  }
256  transfer.WaitTimer = 0.1f;
257  return;
258  }
259 
260  for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
261  {
262  long remaining = transfer.Data.Length - transfer.SentOffset;
263 #if DEBUG
264  bool stalling = false;
265  float elapsedTime = (float)(DateTime.Now - StartTime).TotalSeconds;
266  if (elapsedTime < ForceMinimumFileTransferDuration)
267  {
268  int remainingChunks = (int)Math.Max(remaining / chunkLen, 1);
269  transfer.WaitTimer =
270  Math.Max(transfer.WaitTimer, (ForceMinimumFileTransferDuration - elapsedTime) / remainingChunks);
271  if (remainingChunks <= 1) { break; }
272  }
273 #endif
274  int sendByteCount = remaining > chunkLen ? chunkLen : (int)remaining;
275 
276  message = new WriteOnlyMessage();
277  message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
278  message.WriteByte((byte)FileTransferMessageType.Data);
279 
280  message.WriteByte((byte)transfer.ID);
281  message.WriteInt32(transfer.SentOffset);
282 
283  message.WriteUInt16((ushort)sendByteCount);
284  int chunkDestPos = message.BytePosition;
285  message.BitPosition += sendByteCount * 8;
286  message.LengthBits = Math.Max(message.LengthBits, message.BitPosition);
287  Array.Copy(transfer.Data, transfer.SentOffset, message.Buffer, chunkDestPos, sendByteCount);
288 
289  transfer.SentOffset += sendByteCount;
290 
291  peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable, compressPastThreshold: false);
292 
293  if (GameSettings.CurrentConfig.VerboseLogging)
294  {
295  DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
296  }
297 
298  if (transfer.SentOffset >= transfer.Data.Length)
299  {
300  transfer.SentOffset = transfer.KnownReceivedOffset;
301  transfer.WaitTimer = 1.0f;
302  }
303 
304  //try to increase the packet rate so large files get sent faster,
305  //this gets reset when packet loss or disorder sets in
306  transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
307  transfer.PacketsPerUpdate + 0.05f);
308 #if DEBUG
309  if (stalling) { break; }
310 #endif
311  }
312  }
313 
314  catch (Exception e)
315  {
316  DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
317  GameAnalyticsManager.AddErrorEventOnce(
318  "FileSender.Update:Exception",
319  GameAnalyticsManager.ErrorSeverity.Error,
320  "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
321  transfer.Status = FileTransferStatus.Error;
322  return;
323  }
324  }
325 
326  public void CancelTransfer(FileTransferOut transfer)
327  {
328  transfer.Status = FileTransferStatus.Canceled;
329  activeTransfers.Remove(transfer);
330 
331  OnEnded(transfer);
332 
334  }
335 
336  public void ReadFileRequest(IReadMessage inc, Client client)
337  {
339 
340  if (messageType == FileTransferMessageType.Cancel)
341  {
342  byte transferId = inc.ReadByte();
343  var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
344  if (matchingTransfer != null) { CancelTransfer(matchingTransfer); }
345  return;
346  }
347  else if (messageType == FileTransferMessageType.Data)
348  {
349  byte transferId = inc.ReadByte();
350  var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
351  if (matchingTransfer != null)
352  {
353  matchingTransfer.Acknowledged = true;
354  int expecting = inc.ReadInt32(); //the offset the client is waiting for
355  int lastSeen = Math.Min(matchingTransfer.SentOffset, inc.ReadInt32()); //the last offset the client got from us
356  matchingTransfer.KnownReceivedOffset = Math.Max(expecting, matchingTransfer.KnownReceivedOffset);
357  if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset)
358  {
359  matchingTransfer.WaitTimer = 0.0f;
360  matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
361  }
362 
363  if (lastSeen - matchingTransfer.KnownReceivedOffset >= chunkLen * 10 ||
364  matchingTransfer.SentOffset >= matchingTransfer.Data.Length)
365  {
366  matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
367  matchingTransfer.WaitTimer = 1.0f;
368  }
369 
370  if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
371  {
372  matchingTransfer.Status = FileTransferStatus.Finished;
373  DebugConsole.Log($"Finished sending file \"{matchingTransfer.FilePath}\" to \"{client.Name}\". Took {DateTime.Now - StartTime}");
374  }
375  }
376  return;
377  }
378 
379  FileTransferType fileType = (FileTransferType)inc.ReadByte();
380  switch (fileType)
381  {
382  case FileTransferType.Submarine:
383  string fileName = inc.ReadString();
384  string fileHash = inc.ReadString();
385  var requestedSubmarine = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.StringRepresentation == fileHash);
386 
387  DebugConsole.Log($"Received a submarine file request from \"{client.Name}\" ({fileName}).");
388 
389  if (requestedSubmarine != null)
390  {
391  if (activeTransfers.Any(t => t.Connection == inc.Sender && t.FilePath == requestedSubmarine.FilePath))
392  {
393  DebugConsole.Log($"Ignoring a submarine file request from \"{client.Name}\" ({fileName}) - already transferring.");
394  return;
395  }
396  StartTransfer(inc.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
397  }
398  break;
399  case FileTransferType.CampaignSave:
400  if (GameMain.GameSession != null &&
401  !ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
402  {
405  {
406  client.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
407  }
408  }
409  break;
410  case FileTransferType.Mod:
411  string modName = inc.ReadString();
412  Md5Hash modHash = Md5Hash.StringAsHash(inc.ReadString());
413 
414  DebugConsole.Log($"Received a mod file request from \"{client.Name}\" ({modName}).");
415 
416  if (!GameMain.Server.ServerSettings.AllowModDownloads) { return; }
417  if (!(GameMain.Server.ModSender is { Ready: true })) { return; }
418 
419  ContentPackage mod = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Hash.Equals(modHash));
420 
421  if (mod is null) { return; }
422 
423  string modCompressedPath = ModSender.GetCompressedModPath(mod);
424  if (!File.Exists(modCompressedPath)) { return; }
425 
426  if (activeTransfers.Any(t => t.Connection == inc.Sender && t.FilePath == modCompressedPath))
427  {
428  DebugConsole.Log($"Ignoring a mod file request from \"{client.Name}\" ({modName}) - already transferring.");
429  return;
430  }
431 
432  StartTransfer(inc.Sender, FileTransferType.Mod, modCompressedPath);
433 
434  break;
435  }
436  }
437 
438  }
439 }
static GameServer Server
Definition: GameMain.cs:39
static GameSession GameSession
Definition: GameMain.cs:45
CampaignDataPath DataPath
Definition: GameSession.cs:173
static Md5Hash StringAsHash(string hash)
Definition: Md5Hash.cs:80
FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
Definition: FileSender.cs:76
IReadOnlyList< FileTransferOut > ActiveTransfers
Definition: FileSender.cs:127
FileSender(ServerPeer serverPeer, int mtu)
Definition: FileSender.cs:129
delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver)
FileTransferOut StartTransfer(NetworkConnection recipient, FileTransferType fileType, string filePath)
Definition: FileSender.cs:137
void ReadFileRequest(IReadMessage inc, Client client)
Definition: FileSender.cs:336
FileTransferDelegate OnStarted
Definition: FileSender.cs:113
void Update(float deltaTime)
Definition: FileSender.cs:182
FileTransferDelegate OnEnded
Definition: FileSender.cs:114
void CancelTransfer(FileTransferOut transfer)
Definition: FileSender.cs:326
NetworkConnection OwnerConnection
Definition: GameServer.cs:135
void SendCancelTransferMsg(FileSender.FileTransferOut transfer)
Definition: GameServer.cs:3944
static string GetCompressedModPath(ContentPackage mod)
Definition: ModSender.cs:36
static IEnumerable< SubmarineInfo > SavedSubmarines
readonly string LoadPath
Definition: SaveUtil.cs:19