Unbatcher.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // un-batching functionality encapsulated into one class.
  2. // -> less complexity
  3. // -> easy to test
  4. //
  5. // includes timestamp for tick batching.
  6. // -> allows NetworkTransform etc. to use timestamp without including it in
  7. // every single message
  8. using System;
  9. using System.Collections.Generic;
  10. namespace Mirror
  11. {
  12. public class Unbatcher
  13. {
  14. // supporting adding multiple batches before GetNextMessage is called.
  15. // just in case.
  16. readonly Queue<NetworkWriterPooled> batches = new Queue<NetworkWriterPooled>();
  17. public int BatchesCount => batches.Count;
  18. // NetworkReader is only created once,
  19. // then pointed to the first batch.
  20. readonly NetworkReader reader = new NetworkReader(new byte[0]);
  21. // timestamp that was written into the batch remotely.
  22. // for the batch that our reader is currently pointed at.
  23. double readerRemoteTimeStamp;
  24. // helper function to start reading a batch.
  25. void StartReadingBatch(NetworkWriterPooled batch)
  26. {
  27. // point reader to it
  28. reader.SetBuffer(batch.ToArraySegment());
  29. // read remote timestamp (double)
  30. // -> AddBatch quarantees that we have at least 8 bytes to read
  31. readerRemoteTimeStamp = reader.ReadDouble();
  32. }
  33. // add a new batch.
  34. // returns true if valid.
  35. // returns false if not, in which case the connection should be disconnected.
  36. public bool AddBatch(ArraySegment<byte> batch)
  37. {
  38. // IMPORTANT: ArraySegment is only valid until returning. we copy it!
  39. //
  40. // NOTE: it's not possible to create empty ArraySegments, so we
  41. // don't need to check against that.
  42. // make sure we have at least 8 bytes to read for tick timestamp
  43. if (batch.Count < Batcher.TimestampSize)
  44. return false;
  45. // put into a (pooled) writer
  46. // -> WriteBytes instead of WriteSegment because the latter
  47. // would add a size header. we want to write directly.
  48. // -> will be returned to pool when sending!
  49. NetworkWriterPooled writer = NetworkWriterPool.Get();
  50. writer.WriteBytes(batch.Array, batch.Offset, batch.Count);
  51. // first batch? then point reader there
  52. if (batches.Count == 0)
  53. StartReadingBatch(writer);
  54. // add batch
  55. batches.Enqueue(writer);
  56. //Debug.Log($"Adding Batch {BitConverter.ToString(batch.Array, batch.Offset, batch.Count)} => batches={batches.Count} reader={reader}");
  57. return true;
  58. }
  59. // get next message, unpacked from batch (if any)
  60. // message ArraySegment is only valid until the next call.
  61. // timestamp is the REMOTE time when the batch was created remotely.
  62. public bool GetNextMessage(out ArraySegment<byte> message, out double remoteTimeStamp)
  63. {
  64. message = default;
  65. remoteTimeStamp = 0;
  66. // do nothing if we don't have any batches.
  67. // otherwise the below queue.Dequeue() would throw an
  68. // InvalidOperationException if operating on empty queue.
  69. if (batches.Count == 0)
  70. return false;
  71. // was our reader pointed to anything yet?
  72. if (reader.Capacity == 0)
  73. return false;
  74. // no more data to read?
  75. if (reader.Remaining == 0)
  76. {
  77. // retire the batch
  78. NetworkWriterPooled writer = batches.Dequeue();
  79. NetworkWriterPool.Return(writer);
  80. // do we have another batch?
  81. if (batches.Count > 0)
  82. {
  83. // point reader to the next batch.
  84. // we'll return the reader below.
  85. NetworkWriterPooled next = batches.Peek();
  86. StartReadingBatch(next);
  87. }
  88. // otherwise there's nothing more to read
  89. else return false;
  90. }
  91. // use the current batch's remote timestamp
  92. // AFTER potentially moving to the next batch ABOVE!
  93. remoteTimeStamp = readerRemoteTimeStamp;
  94. // enough data to read the size prefix?
  95. if (reader.Remaining == 0)
  96. return false;
  97. // read the size prefix as varint
  98. // see Batcher.AddMessage comments for explanation.
  99. int size = (int)Compression.DecompressVarUInt(reader);
  100. // validate size prefix, in case attackers send malicious data
  101. if (reader.Remaining < size)
  102. return false;
  103. // return the message of size
  104. message = reader.ReadBytesSegment(size);
  105. return true;
  106. }
  107. }
  108. }