MessagePacking.cs 5.0 KB

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