MessagePacking.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using UnityEngine;
  4. namespace Mirror
  5. {
  6. // message packing all in one place, instead of constructing headers in all
  7. // kinds of different places
  8. //
  9. // MsgType (2 bytes)
  10. // Content (ContentSize bytes)
  11. public static class MessagePacking
  12. {
  13. // message header size
  14. public const int HeaderSize = sizeof(ushort);
  15. // max message content size (without header) calculation for convenience
  16. // -> Transport.GetMaxPacketSize is the raw maximum
  17. // -> Every message gets serialized into <<id, content>>
  18. // -> Every serialized message get put into a batch with a header
  19. public static int MaxContentSize
  20. {
  21. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  22. get => Transport.activeTransport.GetMaxPacketSize()
  23. - HeaderSize
  24. - Batcher.HeaderSize;
  25. }
  26. // paul: 16 bits is enough to avoid collisions
  27. // - keeps the message size small
  28. // - in case of collisions, Mirror will display an error
  29. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  30. public static ushort GetId<T>() where T : struct, NetworkMessage =>
  31. (ushort)(typeof(T).FullName.GetStableHashCode() & 0xFFFF);
  32. // pack message before sending
  33. // -> NetworkWriter passed as arg so that we can use .ToArraySegment
  34. // and do an allocation free send before recycling it.
  35. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  36. public static void Pack<T>(T message, NetworkWriter writer)
  37. where T : struct, NetworkMessage
  38. {
  39. ushort msgType = GetId<T>();
  40. writer.WriteUShort(msgType);
  41. // serialize message into writer
  42. writer.Write(message);
  43. }
  44. // unpack message after receiving
  45. // -> pass NetworkReader so it's less strange if we create it in here
  46. // and pass it upwards.
  47. // -> NetworkReader will point at content afterwards!
  48. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  49. public static bool Unpack(NetworkReader messageReader, out ushort msgType)
  50. {
  51. // read message type
  52. try
  53. {
  54. msgType = messageReader.ReadUShort();
  55. return true;
  56. }
  57. catch (System.IO.EndOfStreamException)
  58. {
  59. msgType = 0;
  60. return false;
  61. }
  62. }
  63. // version for handlers with channelId
  64. // inline! only exists for 20-30 messages and they call it all the time.
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. internal static NetworkMessageDelegate WrapHandler<T, C>(Action<C, T, int> handler, bool requireAuthentication)
  67. where T : struct, NetworkMessage
  68. where C : NetworkConnection
  69. => (conn, reader, channelId) =>
  70. {
  71. // protect against DOS attacks if attackers try to send invalid
  72. // data packets to crash the server/client. there are a thousand
  73. // ways to cause an exception in data handling:
  74. // - invalid headers
  75. // - invalid message ids
  76. // - invalid data causing exceptions
  77. // - negative ReadBytesAndSize prefixes
  78. // - invalid utf8 strings
  79. // - etc.
  80. //
  81. // let's catch them all and then disconnect that connection to avoid
  82. // further attacks.
  83. T message = default;
  84. // record start position for NetworkDiagnostics because reader might contain multiple messages if using batching
  85. int startPos = reader.Position;
  86. try
  87. {
  88. if (requireAuthentication && !conn.isAuthenticated)
  89. {
  90. // message requires authentication, but the connection was not authenticated
  91. Debug.LogWarning($"Closing connection: {conn}. Received message {typeof(T)} that required authentication, but the user has not authenticated yet");
  92. conn.Disconnect();
  93. return;
  94. }
  95. //Debug.Log($"ConnectionRecv {conn} msgType:{typeof(T)} content:{BitConverter.ToString(reader.buffer.Array, reader.buffer.Offset, reader.buffer.Count)}");
  96. // if it is a value type, just use default(T)
  97. // otherwise allocate a new instance
  98. message = reader.Read<T>();
  99. }
  100. catch (Exception exception)
  101. {
  102. Debug.LogError($"Closed connection: {conn}. This can happen if the other side accidentally (or an attacker intentionally) sent invalid data. Reason: {exception}");
  103. conn.Disconnect();
  104. return;
  105. }
  106. finally
  107. {
  108. int endPos = reader.Position;
  109. // TODO: Figure out the correct channel
  110. NetworkDiagnostics.OnReceive(message, channelId, endPos - startPos);
  111. }
  112. // user handler exception should not stop the whole server
  113. try
  114. {
  115. // user implemented handler
  116. handler((C)conn, message, channelId);
  117. }
  118. catch (Exception e)
  119. {
  120. Debug.LogError($"Disconnecting connId={conn.connectionId} to prevent exploits from an Exception in MessageHandler: {e.GetType().Name} {e.Message}\n{e.StackTrace}");
  121. conn.Disconnect();
  122. }
  123. };
  124. // version for handlers without channelId
  125. // TODO obsolete this some day to always use the channelId version.
  126. // all handlers in this version are wrapped with 1 extra action.
  127. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  128. internal static NetworkMessageDelegate WrapHandler<T, C>(Action<C, T> handler, bool requireAuthentication)
  129. where T : struct, NetworkMessage
  130. where C : NetworkConnection
  131. {
  132. // wrap action as channelId version, call original
  133. void Wrapped(C conn, T msg, int _) => handler(conn, msg);
  134. return WrapHandler((Action<C, T, int>) Wrapped, requireAuthentication);
  135. }
  136. }
  137. }