NetworkMessages.cs 8.1 KB

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