From 6cb9c98b71e68ccda8894336df00fa4f8dce127c Mon Sep 17 00:00:00 2001
From: Unity Technologies <@unity>
Date: Thu, 29 Sep 2022 00:00:00 +0000
Subject: [PATCH] com.unity.transport@2.0.0-exp.7 ## [2.0.0-exp.7] - 2022-09-29
### New features
* It is now possible to obtain `RelayAllocationId`, `RelayConnectionData`, and `RelayHMACKey` structures from byte arrays using their static `FromByteArray` method.
* A new constructor for `RelayServerData` is now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use this constructor.
* New constructors for `RelayServerData` are now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use them constructor.
* `NetworkSettings` now has a `IsCreated` property which can be used to check if it's been disposed of or not.
### Changes
* Reverted the fix for the `SimulatorPipelineStage` always using the same random seed, reverting its behavior to always be deterministic. If non-determinism is desired, use a dynamic random seed (e.g. `Stopwatch.GetTimestamp`).
* The default network interface (`UDPNetworkInterface`) does not enable address reuse anymore. This means `NetworkDriver.Bind` will now always fail if something else is listening on the same port, even if that something else is bound to a wildcard address and we are trying to bind to a specific one.
* Added: `NetworkDriverIdentifierParameter` struct and `NetworkSettings.WithDriverIdentifierParameters()` method that can be use to identify the NetworkDriver instances with a custom label. Currently this method serves no purpose, but might be used in future releases to make debugging easier.
* The `InitialEventQueueSize`, `InvalidConnectionId`, and `DriverDataStreamSize` fields were removed from `NetworkParameterConstants`. They all served no purpose anymore.
* If using Relay, it is now possible to call `Connect` without an endpoint (the endpoint would be ignored anyway). This extension to `NetworkDriver` is provided in the `Unity.Networking.Transport.Relay` namespace.
* The `RelayServerData.HMAC` field is now internal. There was no use to this being available publicly.
* The deprecated constructor for `RelayServerData` that was taking strings for the allocation ID, connection data, and key has been completely removed.
* The deprecated `RelayServerData.ComputeNewNonce` method has also been removed. One can provide a custom nonce using the "low level" constructor of `RelayServerData`. Other constructors will select a new one automatically.
### Fixes
* Fixed an issue where a duplicated reliable packet wouldn't be processed correctly, which could possibly lead to the entire reliable pipeline stage stalling (not being able to send new packets).
* Fixed an issue where a warning about having too many pipeline updates would be spammed after a connection was closed.
* Fixed an issue where pipeline updates would be run too many times, which would waste CPU and could lead to the warning about having too many pipeline updates being erroneously logged.
* Fixed issues with `ReliableSequencePipelineStage` that would, in rare circumstances, lead to failure to deliver a reliable packet.
* Fixed an issue where sending maximally-sized packets to the Relay when using DTLS would fail with an error about failing to encrypt the packet.
* Fixed an issue when using secure WebSockets where the stream would become corrupted, resulting in failure to decrypt packets (and eventually potentially a crash of the server).
---
CHANGELOG.md | 173 +++++++++++++++++-
Documentation~/filter.yml | 8 +-
Runtime/Base64.cs | 145 ---------------
Runtime/Base64.cs.meta | 3 -
Runtime/Layers/DTLSLayer.cs | 42 +++--
Runtime/Layers/LogLayer.cs | 13 +-
Runtime/Layers/RelayLayer.cs | 9 -
Runtime/Layers/SimpleConnectionLayer.cs | 108 +++++------
Runtime/Layers/StreamSegmentationLayer.cs | 83 +++++++++
.../Layers/StreamSegmentationLayer.cs.meta | 2 +-
Runtime/Layers/TLSLayer.cs | 128 +++++++++----
Runtime/NetworkDriver.cs | 5 +-
Runtime/NetworkDriverIdentifierParameter.cs | 37 ++++
.../NetworkDriverIdentifierParameter.cs.meta | 11 ++
Runtime/NetworkParams.cs | 7 +-
Runtime/NetworkPipeline.cs | 23 ++-
Runtime/NetworkSettings.cs | 3 +
Runtime/NetworkStack.cs | 26 +--
.../ReliableSequencedPipelineStage.cs | 30 ++-
Runtime/Pipelines/ReliableUtility.cs | 101 +++++-----
Runtime/Pipelines/SimulatorUtility.cs | 24 ++-
Runtime/Relay/NetworkDriverRelayExtensions.cs | 47 +++++
.../NetworkDriverRelayExtensions.cs.meta | 11 ++
Runtime/Relay/RelayAllocationId.cs | 16 +-
Runtime/Relay/RelayConnectionData.cs | 22 ++-
Runtime/Relay/RelayConnectionStatus.cs | 25 ---
Runtime/Relay/RelayHMACKey.cs | 16 +-
Runtime/Relay/RelayNetworkParameter.cs | 5 -
Runtime/Relay/RelayServerData.cs | 144 ++++++++++++---
Runtime/StreamSegmentationParameters.cs | 23 +++
Runtime/StreamSegmentationParameters.cs.meta | 11 ++
Runtime/UDPNetworkInterface.cs | 12 +-
Runtime/Unity.Networking.Transport.asmdef | 12 +-
Runtime/Utilities.cs | 52 +++---
Runtime/WebSocketNetworkInterface.cs | 2 +-
Samples~/RelayPing/Scripts/Code.asmdef | 4 +-
.../RelayPing/Scripts/PingClientBehaviour.cs | 36 +---
.../Scripts/PingClientUIBehaviour.cs | 5 -
.../RelayPing/Scripts/PingServerBehaviour.cs | 40 +---
Samples~/RelayPing/Scripts/RelayUtilities.cs | 43 -----
package.json | 6 +-
41 files changed, 934 insertions(+), 579 deletions(-)
delete mode 100644 Runtime/Base64.cs
delete mode 100644 Runtime/Base64.cs.meta
create mode 100644 Runtime/Layers/StreamSegmentationLayer.cs
rename Samples~/RelayPing/Scripts/RelayUtilities.cs.meta => Runtime/Layers/StreamSegmentationLayer.cs.meta (83%)
create mode 100644 Runtime/NetworkDriverIdentifierParameter.cs
create mode 100644 Runtime/NetworkDriverIdentifierParameter.cs.meta
create mode 100644 Runtime/Relay/NetworkDriverRelayExtensions.cs
create mode 100644 Runtime/Relay/NetworkDriverRelayExtensions.cs.meta
create mode 100644 Runtime/StreamSegmentationParameters.cs
create mode 100644 Runtime/StreamSegmentationParameters.cs.meta
delete mode 100644 Samples~/RelayPing/Scripts/RelayUtilities.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 648033c..7cc3fe9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,35 @@
# Change log
+## [2.0.0-exp.7] - 2022-09-29
+
+### New features
+* It is now possible to obtain `RelayAllocationId`, `RelayConnectionData`, and `RelayHMACKey` structures from byte arrays using their static `FromByteArray` method.
+* A new constructor for `RelayServerData` is now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use this constructor.
+* New constructors for `RelayServerData` are now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use them constructor.
+* `NetworkSettings` now has a `IsCreated` property which can be used to check if it's been disposed of or not.
+
+### Changes
+* Reverted the fix for the `SimulatorPipelineStage` always using the same random seed, reverting its behavior to always be deterministic. If non-determinism is desired, use a dynamic random seed (e.g. `Stopwatch.GetTimestamp`).
+* The default network interface (`UDPNetworkInterface`) does not enable address reuse anymore. This means `NetworkDriver.Bind` will now always fail if something else is listening on the same port, even if that something else is bound to a wildcard address and we are trying to bind to a specific one.
+* Added: `NetworkDriverIdentifierParameter` struct and `NetworkSettings.WithDriverIdentifierParameters()` method that can be use to identify the NetworkDriver instances with a custom label. Currently this method serves no purpose, but might be used in future releases to make debugging easier.
+* The `InitialEventQueueSize`, `InvalidConnectionId`, and `DriverDataStreamSize` fields were removed from `NetworkParameterConstants`. They all served no purpose anymore.
+* If using Relay, it is now possible to call `Connect` without an endpoint (the endpoint would be ignored anyway). This extension to `NetworkDriver` is provided in the `Unity.Networking.Transport.Relay` namespace.
+* The `RelayServerData.HMAC` field is now internal. There was no use to this being available publicly.
+* The deprecated constructor for `RelayServerData` that was taking strings for the allocation ID, connection data, and key has been completely removed.
+* The deprecated `RelayServerData.ComputeNewNonce` method has also been removed. One can provide a custom nonce using the "low level" constructor of `RelayServerData`. Other constructors will select a new one automatically.
+
+### Fixes
+* Fixed an issue where a duplicated reliable packet wouldn't be processed correctly, which could possibly lead to the entire reliable pipeline stage stalling (not being able to send new packets).
+* Fixed an issue where a warning about having too many pipeline updates would be spammed after a connection was closed.
+* Fixed an issue where pipeline updates would be run too many times, which would waste CPU and could lead to the warning about having too many pipeline updates being erroneously logged.
+* Fixed issues with `ReliableSequencePipelineStage` that would, in rare circumstances, lead to failure to deliver a reliable packet.
+* Fixed an issue where sending maximally-sized packets to the Relay when using DTLS would fail with an error about failing to encrypt the packet.
+* Fixed an issue when using secure WebSockets where the stream would become corrupted, resulting in failure to decrypt packets (and eventually potentially a crash of the server).
+
## [2.0.0-exp.6] - 2022-09-02
### Fixes
-* Fixed changelog.
+* Fixed changelog.
## [2.0.0-exp.5] - 2022-09-01
@@ -137,6 +163,151 @@
* For custom implementations of `INetworkInterface` that are managed types, use the `INetworkInterface.WrapToUnmanaged()` configuring the `NetworkDriver`.
* For custom implementations of `INetworkInterface`: Remove `CreateInterfaceEndPoint` and `GetGenericEndPoint` implementations and update `NetworkInterfaceEndPoint` usages to `NetworkEndPoint`.
+## [1.3.0] - 2022-09-27
+
+### New features
+* It is now possible to obtain `RelayAllocationId`, `RelayConnectionData`, and `RelayHMACKey` structures from byte arrays using their static `FromByteArray` method.
+* A new constructor for `RelayServerData` is now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use this constructor.
+* New constructors for `RelayServerData` are now provided with argument types that better match those available in the models returned by the Relay SDK. The "RelayPing" sample has been updated to use them constructor.
+* `NetworkSettings` now has a `IsCreated` property which can be used to check if it's been disposed of or not.
+* New versions of `NetworkSettings.WithSecureClientParameters` and `NetworkSettins.WithSecureServerParameters` are provided that take strings as parameters instead of references to fixed strings. The older versions are still available and fully supported.
+* A new version of `NetworkSettings.WithSecureClientParameters` is provided that only takes the server name as a parameter. This can be used when the server is using certificates from a recognized CA.
+
+### Changes
+* A warning is now emitted if binding to a port where another application is listening. The binding operation still succeeds in that scenario, but this will fail in Unity Transport 2.0 (which disables address reuse on the sockets used by the default interface).
+* The constructor for `RelayServerData` that was taking strings for the allocation ID, connection data, and key is now deprecated. Use the new constructor (see above) or the existing lower-level constructor instead.
+* The `RelayServerData.ComputeNewNonce` method is now deprecated. One can provide a custom nonce using the "low level" constructor of `RelayServerData`. The new constructor will select a new one automatically.
+* If using Relay, it is now possible to call `Connect` without an endpoint (the endpoint would be ignored anyway). This extension to `NetworkDriver` is provided in the `Unity.Networking.Transport.Relay` namespace.
+
+### Fixes
+* Fixed a possible stack overflow if the receive or send queue parameters were configured with very large values (>15,000).
+* Prevented an issue where a warning about having too many pipeline updates would be spammed after a connection was closed.
+* Fixed an issue where a duplicated reliable packet wouldn't be processed correctly, which could possibly lead to the entire reliable pipeline stage stalling (not being able to send new packets).
+* Fixed an issue where pipeline updates would be run too many times, which would waste CPU and could lead to the warning about having too many pipeline updates being erroneously logged.
+* Fixed issues with `ReliableSequencePipelineStage` that would, in rare circumstances, lead to failure to deliver a reliable packet.
+
+## [1.2.0] - 2022-08-10
+
+### New features
+* If using the default network interface, the transport will attempt to transparently recreate the underlying network socket if it fails. This should increase robustness, especially on mobile where the OS might close sockets when an application is sent to the background.
+
+### Changes
+* A new `NetworkSocketError` value has been added to `Error.StatusCode`. This will be returned through `NetworkDriver.ReceiveErrorCode` when the automatic socket recreation mentioned above has failed (indicating an unrecoverable network failure).
+
+### Fixes
+* On iOS, communications will restart correctly if the application was in the background. Note that if using Relay, it's still possible for the allocation to have timed out while in the background. Recreation of a new allocation with a new `NetworkDriver` is still required in that scenario.
+* Fixed a possible stack overflow if the receive queue parameter was configured with a very large value (>10,000).
+
+## [1.1.0] - 2022-06-14
+
+### New features
+* A `DataStreamReader` can now be passed to another job without triggering the job safety system.
+* A `GetRelayConnectionStatus` method has been added to `NetworkDriver` to query the status of the connection to the Relay server.
+
+### Changes
+* `NetworkSettings.WithDataStreamParameters` is now obsolete. The functionality still works and will remain supported for version 1.X of the package, but will be removed in version 2.0. The reason for the removal is that in 2.0 the data stream size is always dynamically-sized to avoid out-of-memory errors.
+* `NetworkSettings.WithPipelineParameters` is now obsolete. The functionality still works and will remain supported for version 1.X of the package, but will be removed in version 2.0, where pipeline buffer sizing is handled internally.
+* Updated Burst dependency to 1.6.6.
+* Updated Collections dependency to 1.2.4.
+* Updated Mathematics dependency to 1.2.6.
+
+### Fixes
+* `BeginSend` would not return an error if called on a closed connection before the next `ScheduleUpdate` call.
+* Fixed a warning if using the default maximum payload size with DTLS.
+* Removed an error log when receiving messages on a closed DTLS connection (this scenario is common if there were in-flight messages at the moment of disconnection).
+* Fix broken link in package documentation.
+
+## [1.0.0] - 2022-03-28
+
+### Changes
+* Changed version to 1.0.0.
+
+## [1.0.0-pre.16] - 2022-03-24
+
+### Changes
+* Don't warn when overwriting settings in `NetworkSettings` (e.g. when calling the same `WithFooParameters` method twice).
+* Added new methods to set security parameters: `NetworkSettings.WithSecureClientParameters` and `NetworkSettings.WithSecureServerParameters`. These replace the existing `WithSecureParameters`, which is now obsolete.
+* Updated Collections dependency to 1.2.3.
+
+### Fixes
+* Fixed client certificate not being passed to UnityTLS on secure connections. This prevented client authentication from properly working.
+* Fixed: Reliable pipeline drop statistics inaccurate.
+
+## [1.0.0-pre.15] - 2022-03-11
+
+### Changes
+* An error is now logged if failing to decrypt a DTLS message when using Relay.
+* Decreased default Relay keep-alive period to 3 seconds (was 9 seconds). The value can still be configured through the `relayConnectionTimeMS` parameter of `NetworkSettings.WithRelayParameters`.
+
+### Fixes
+* Updated Relay sample to the most recent Relay SDK APIs (would fail to compile with latest packages).
+
+## [1.0.0-pre.14] - 2022-03-01
+
+### Changes
+* `IValidatableNetworkParameter.Validate()` method is now part of `INetworkParameter`.
+* Added: `NetworkDriver.Create<>()` generic methods.
+
+### Fixes
+* Fixed compilation on WebGL. Note that the platform is still unsupported, but at least including the package in a WebGL project will not create compilation errors anymore. Creating a `NetworkDriver` in WebGL projects will now produce a warning.
+
+## [1.0.0-pre.13] - 2022-02-14
+
+### New features
+* When using the Relay protocol, error messages sent by the Relay server are now properly captured and logged.
+
+### Fixes
+* Fixed: Issue where an overflow of the `ReliableSequencedPipelineStage` sequence numbers would not be handled properly.
+
+## [1.0.0-pre.12] - 2022-01-24
+
+### Fixes
+* Clean up changelog for package promotion.
+
+## [1.0.0-pre.11] - 2022-01-24
+
+### Changes
+* Updated to Burst 1.6.4.
+* Updated to Mathematics 1.2.5.
+* Documentation has been moved to the [offical multiplayer documentation site](https://docs-multiplayer.unity3d.com/transport/1.0.0/introduction).
+
+### Fixes
+* Fixed a division by zero in `SimulatorPipelineStage` when `PacketDropInterval` is set.
+* Don't warn when receiving repeated connection accept messages (case 1370591).
+* Fixed an exception when receiving a data message from an unknown connection.
+
+## [1.0.0-pre.10] - 2021-12-02
+
+### Fixes
+* On fragmented and reliable pipelines, sending a large packet when the reliable window was almost full could result in the packet being lost.
+* Fixed "pending sends" warning being emitted very often when sending to remote hosts.
+* Revert decrease of MTU to 1384 on Xbox platforms (now back at 1400). It would cause issues for cross-platform communications.
+
+## [1.0.0-pre.9] - 2021-11-26
+
+### Changes
+* Disabled Roslyn Analyzers provisionally
+
+### Fixes
+* Fixed: Compiler error due to Roslyn Analyzers causing a wrong compiler argument
+
+## [1.0.0-pre.8] - 2021-11-18
+
+### Changes
+* Creating a pipeline with `FragmentationPipelineStage` _after_ `ReliableSequencedPipelineStage` is now forbidden (will throw an exception if collections checks are enabled). That order never worked properly to begin with. The reverse order is fully supported and is the recommended way to configure a reliable pipeline with support for large packets.
+* Added `NetworkSettings` struct and API for defining network parameters. See [NetworkSettings documentation](https://docs-multiplayer.unity3d.com/transport/1.0.0/network-settings) for more information.
+* Added Roslyn Analyzers for ensuring proper extension of NetworkParameters and NetworkSettings API.
+* Update Collections package to 1.1.0
+
+### Fixes
+* Fixed: Error message when scheduling an update on an unbound `NetworkDriver` (case 1370584)
+* Fixed: `BeginSend` wouldn't return an error if the required payload size was larger than the supported payload size when close to the MTU
+* Fixed: Removed boxing in `NetworkDriver` initialization by passing `NetworkSettings` parameter instead of `INetworkParameter[]`
+* Fixed a crash on XboxOne(X/S) when using the fragmentation pipeline (case 1370473)
+
+### Upgrade guide
+* `INetworkPipelineStage` and `INetworkInterface` initialization methods now receive a `NetworkSettings` parameter instead of `INetworkParameter[]`.
+
## [1.0.0-pre.7] - 2021-10-21
### Changes
diff --git a/Documentation~/filter.yml b/Documentation~/filter.yml
index 0e846ca..52d8803 100644
--- a/Documentation~/filter.yml
+++ b/Documentation~/filter.yml
@@ -2,11 +2,11 @@
apiRules:
- exclude:
- uidRegex: Tests
+ uidRegex: .*\.Tests.*
type: Namespace
- exclude:
- uidRegex: Editor$
+ uidRegex: .*\.Editor$
type: Namespace
- exclude:
- uidRegex: Samples$
- type: Namespace
\ No newline at end of file
+ uidRegex: .*\.Samples$
+ type: Namespace
diff --git a/Runtime/Base64.cs b/Runtime/Base64.cs
deleted file mode 100644
index 156226d..0000000
--- a/Runtime/Base64.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using System;
-using Unity.Collections.LowLevel.Unsafe;
-using UnityEngine;
-using UnityEngine.Assertions;
-
-namespace Unity.Networking.Transport
-{
- internal static class Base64
- {
- ///
- /// Decode characters representing a Base64 encoding into bytes.
- ///
- /// Pointer to first input char in UTF16 string (C# strings)
- /// Number of input chars
- /// Pointer to location for teh first result byte
- /// Max length of the preallocated result buffer
- ///
- /// Actually written bytes to startDestPtr that is less or equal than destLength. -1 on error.
- ///
- private static unsafe int FromBase64_Decode_UTF16(byte* startInputPtr, int inputLength, byte* startDestPtr, int destLength)
- {
- if (inputLength == 0)
- return 0;
-
- const int sizeCharUTF16 = 2;
-
- // 3 bytes == 4 chars in the input base64 string
- if (inputLength % 4 != 0)
- {
- Debug.LogError("Base64 string's length must be multiple of 4");
- return -1;
- }
-
- if (destLength < inputLength / 4 * 3 - 2)
- {
- Debug.LogError("Dest array is too small");
- return -1;
- }
-
- var originalStartDestPtr = startDestPtr;
-
- var n = inputLength / 4;
-
- const string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
- var lookup = stackalloc byte[256];
- UnsafeUtility.MemSet(lookup, 0xFF, 256);
- for (byte i = 0; i < table.Length; i++)
- lookup[table[i]] = i;
- lookup['='] = 0;
-
- // skip last 4 chars
- for (var i = 0; i < n - 1; i++)
- {
- byte a = lookup[startInputPtr[0 * sizeCharUTF16]];
- byte b = lookup[startInputPtr[1 * sizeCharUTF16]];
- byte c = lookup[startInputPtr[2 * sizeCharUTF16]];
- byte d = lookup[startInputPtr[3 * sizeCharUTF16]];
-
- if (a == 0xFF || b == 0xFF || c == 0xFF || d == 0xFF)
- {
- Debug.LogError("Invalid Base64 symbol");
- return -1;
- }
-
- *startDestPtr++ = (byte)((a << 2) | (b >> 4));
- *startDestPtr++ = (byte)((b << 4) | (c >> 2));
- *startDestPtr++ = (byte)((c << 6) | d);
-
- startInputPtr += 4 * sizeCharUTF16;
- }
-
- // last 4 chars
- var cc = startInputPtr[2 * sizeCharUTF16];
- var dd = startInputPtr[3 * sizeCharUTF16];
-
- var la = lookup[startInputPtr[0 * sizeCharUTF16]];
- var lb = lookup[startInputPtr[1 * sizeCharUTF16]];
- var lc = lookup[cc];
- var ld = lookup[dd];
-
- if (la == 0xFF || lb == 0xFF || lc == 0xFF || ld == 0xFF)
- {
- Debug.LogError("Invalid Base64 symbol");
- return -1;
- }
-
- *startDestPtr++ = (byte)((la << 2) | (lb >> 4));
-
- if (cc != '=') // == means 4 chars == 1 byte, we already wrote that
- {
- if (dd == '=') // = means 4 chars == 2 bytes, 1 more
- {
- if (destLength < inputLength / 4 * 3 - 1)
- {
- Debug.LogError("Dest array is too small");
- return -1;
- }
-
- *startDestPtr++ = (byte)((lb << 4) | (lc >> 2));
- }
- else // no padding, 4 chars == 3 bytes, 2 more
- {
- if (destLength < inputLength / 4 * 3)
- {
- Debug.LogError("Dest array is too small");
- return -1;
- }
-
- *startDestPtr++ = (byte)((lb << 4) | (lc >> 2));
- *startDestPtr++ = (byte)((lc << 6) | ld);
- }
- }
-
- return (int)(startDestPtr - originalStartDestPtr);
- }
-
- ///
- /// Decodes base64 string and writes binary data into dest
- ///
- /// Input base64 string to decode
- /// Decoded base64 will be written here
- /// Max length that dest can handle.
- ///
- /// Actual length of data that was written to dest. Less or equal than destLength.
- /// On error, will throw if ENABLE_UNITY_COLLECTIONS_CHECK is defined, or return -1 otherwise.
- ///
- public static unsafe int FromBase64String(string base64, byte* dest, int destMaxLength)
- {
- var result = 0;
- fixed(char* ptr = base64)
- {
- result = FromBase64_Decode_UTF16((byte*)ptr, base64.Length, dest, destMaxLength);
- }
-
-#if ENABLE_UNITY_COLLECTIONS_CHECKS
- if (result == -1)
- {
- throw new ArgumentException("Invalid base64 string or too short destination array");
- }
-#endif
- return result;
- }
- }
-}
diff --git a/Runtime/Base64.cs.meta b/Runtime/Base64.cs.meta
deleted file mode 100644
index 2426b71..0000000
--- a/Runtime/Base64.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: 834303e1da63424091c3a7d11d764c2a
-timeCreated: 1623792607
\ No newline at end of file
diff --git a/Runtime/Layers/DTLSLayer.cs b/Runtime/Layers/DTLSLayer.cs
index e0905de..68e801f 100644
--- a/Runtime/Layers/DTLSLayer.cs
+++ b/Runtime/Layers/DTLSLayer.cs
@@ -16,7 +16,9 @@ internal unsafe struct DTLSLayer : INetworkLayer
// The deferred queue has to be quite large because if we fill it up, a bug in UnityTLS
// causes the session to fail. Once MTT-3971 is fixed, we can lower this value.
private const int k_DeferredSendsQueueSize = 64;
- private const int k_DTLSPadding = 29;
+
+ // TODO When not using Relay, this could be reduced to 29. See MTT-4748.
+ private const int k_DTLSPadding = 37;
private struct DTLSConnectionData
{
@@ -28,7 +30,7 @@ private struct DTLSConnectionData
[NativeDisableUnsafePtrRestriction]
public Binding.unitytls_client* ReconnectionClientPtr;
- // Tracks the last time progress was made on the DTLS handshake. Used to delete
+ // Tracks the last time progress was made on the DTLS handshake. Used to delete
// half-open connections after a while (important for servers since an attacker could
// just send a ton of client hellos to fill up the connection list).
public long LastHandshakeUpdate;
@@ -63,9 +65,9 @@ public int Initialize(ref NetworkSettings settings, ref ConnectionList connectio
m_DeferredSends.SetDefaultDataOffset(packetPadding);
var netConfig = settings.GetNetworkConfigParameters();
- // We pick the maximum handshake timeout as our half-open disconnect timeout since after
- // that point, there is no progress possible anymore on the handshake.
- m_HalfOpenDisconnectTimeout = netConfig.maxConnectAttempts * netConfig.connectTimeoutMS;
+ // We pick a value just past the maximum handshake timeout as our half-open disconnect
+ // timeout since after that point, there is no handshake progress possible anymore.
+ m_HalfOpenDisconnectTimeout = (netConfig.maxConnectAttempts + 1) * netConfig.connectTimeoutMS;
m_ReconnectionTimeout = netConfig.reconnectionTimeoutMS;
packetPadding += k_DTLSPadding;
@@ -145,7 +147,6 @@ private void ProcessReceivedMessages()
}
UpdateLastReceiveTime(connectionId);
-
HandlePossibleReconnection(connectionId, ref packetProcessor);
packetProcessor.ConnectionRef = connectionId;
@@ -217,7 +218,7 @@ private void ProcessDataMessage(ref PacketProcessor packetProcessor)
var decryptedLength = new UIntPtr();
var result = Binding.unitytls_client_read_data(ConnectionsData[connectionId].UnityTLSClientPtr,
- (byte*)tempBuffer.GetUnsafePtr(), new UIntPtr((uint)tempBuffer.Length), &decryptedLength);
+ (byte*)tempBuffer.GetUnsafePtr(), new UIntPtr((uint)tempBuffer.Length), &decryptedLength);
if (result == Binding.UNITYTLS_SUCCESS)
{
@@ -227,7 +228,9 @@ private void ProcessDataMessage(ref PacketProcessor packetProcessor)
else
{
// Probably irrelevant garbage. Drop the packet silently. We don't want to log
- // here since this could be used to flood the logs with errors.
+ // here since this could be used to flood the logs with errors. If this is an
+ // actual failure in UnityTLS that would require disconnecting, then all future
+ // receives will also fail and the connection will eventually timeout.
packetProcessor.Drop();
}
}
@@ -317,8 +320,20 @@ private void CheckForFailedClient(ConnectionId connection)
if (clientPtr == null)
return;
- if (Binding.unitytls_client_get_state(clientPtr) == Binding.UnityTLSClientState_Fail)
+ ulong dummy;
+ var errorState = Binding.unitytls_client_get_errorsState(clientPtr, &dummy);
+ var clientState = Binding.unitytls_client_get_state(clientPtr);
+
+ if (errorState != Binding.UNITYTLS_SUCCESS || clientState == Binding.UnityTLSClientState_Fail)
{
+ // The only way to get a failed client is because of a failed handshake.
+ if (clientState == Binding.UnityTLSClientState_Fail)
+ {
+ // TODO Would be nice to translate the numerical step in a string.
+ var handshakeStep = Binding.unitytls_client_get_handshake_state(clientPtr);
+ Debug.LogError($"DTLS handshake failed at step {handshakeStep}. Closing connection.");
+ }
+
Connections.StartDisconnecting(ref connection);
// TODO Not the ideal disconnect reason. If we ever have a better one, use it.
Disconnect(connection, Error.DisconnectReason.ClosedByRemote);
@@ -375,7 +390,7 @@ private void CheckForReconnection(ConnectionId connection)
private void AdvanceHandshake(Binding.unitytls_client* clientPtr)
{
- while (Binding.unitytls_client_handshake(clientPtr) == Binding.UNITYTLS_HANDSHAKE_STEP);
+ while (Binding.unitytls_client_handshake(clientPtr) == Binding.UNITYTLS_HANDSHAKE_STEP) ;
}
private void UpdateLastReceiveTime(ConnectionId connection)
@@ -451,8 +466,7 @@ public void Execute()
var clientPtr = ConnectionsData[connectionId].UnityTLSClientPtr;
var packetPtr = (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset;
- // Only way this can happen is if we're reconnecting or if a previous send
- // caused the UnityTLS session to fail for some reason.
+ // Only way this can happen is if we're reconnecting.
var clientState = Binding.unitytls_client_get_state(clientPtr);
if (clientState != Binding.UnityTLSClientState_Messaging)
{
@@ -463,7 +477,7 @@ public void Execute()
var result = Binding.unitytls_client_send_data(clientPtr, packetPtr, new UIntPtr((uint)packetProcessor.Length));
if (result != Binding.UNITYTLS_SUCCESS)
{
- Debug.LogError($"Failed to encrypt packet (TLS error: {result}). Packet will not be sent.");
+ Debug.LogError($"Failed to encrypt packet (error: {result}). Likely internal DTLS failure. Closing connection.");
packetProcessor.Drop();
}
}
@@ -506,4 +520,4 @@ public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependen
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/Runtime/Layers/LogLayer.cs b/Runtime/Layers/LogLayer.cs
index 5c97c2a..15f89f3 100644
--- a/Runtime/Layers/LogLayer.cs
+++ b/Runtime/Layers/LogLayer.cs
@@ -6,15 +6,16 @@ namespace Unity.Networking.Transport
{
internal struct LogLayer : INetworkLayer
{
- static private int s_InstancesCount;
-
- private int m_InstanceId;
+ private FixedString32Bytes m_DriverIdentifier;
public void Dispose() {}
public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding)
{
- m_InstanceId = ++s_InstancesCount;
+ if (settings.TryGet(out var identifier))
+ m_DriverIdentifier = identifier.Label;
+ else
+ m_DriverIdentifier = "unidentified";
return 0;
}
@@ -22,7 +23,7 @@ public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle de
{
return new LogJob
{
- Label = $"[{m_InstanceId}] Received",
+ Label = $"[{m_DriverIdentifier}] Received",
Queue = arguments.ReceiveQueue,
}.Schedule(dependency);
}
@@ -31,7 +32,7 @@ public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependen
{
return new LogJob
{
- Label = $"[{m_InstanceId}] Sent",
+ Label = $"[{m_DriverIdentifier}] Sent",
Queue = arguments.SendQueue,
}.Schedule(dependency);
}
diff --git a/Runtime/Layers/RelayLayer.cs b/Runtime/Layers/RelayLayer.cs
index f05115c..9fd70f7 100644
--- a/Runtime/Layers/RelayLayer.cs
+++ b/Runtime/Layers/RelayLayer.cs
@@ -442,15 +442,6 @@ private void ProcessConnecting(ref ConnectionId connectionId)
connectionData.LastConnectAttempt = Time;
ConnectionsData[connectionId] = connectionData;
- var endpoint = Connections.GetConnectionEndpoint(connectionId);
- if (endpoint != protocolData.ServerData.Endpoint)
- {
-#if ENABLE_UNITY_COLLECTIONS_CHECKS
- UnityEngine.Debug.LogError("A relay connection can be requested only to the Relay Server endpoint");
-#endif
- return;
- }
-
// Send a ConnectRequest message
if (DeferredSendQueue.EnqueuePacket(out var packetProcessor))
{
diff --git a/Runtime/Layers/SimpleConnectionLayer.cs b/Runtime/Layers/SimpleConnectionLayer.cs
index 8845dbf..b5eba48 100644
--- a/Runtime/Layers/SimpleConnectionLayer.cs
+++ b/Runtime/Layers/SimpleConnectionLayer.cs
@@ -32,8 +32,7 @@ internal enum MessageType : byte
{
Data = 1,
Disconnect = 2,
- Ping = 3,
- Pong = 4,
+ Heartbeat = 3,
}
private struct SimpleConnectionData
@@ -42,7 +41,7 @@ private struct SimpleConnectionData
public ConnectionToken Token;
public ConnectionState State;
public long LastReceiveTime;
- public long LastNonDataSend;
+ public long LastSendTime;
public int ConnectionAttempts;
public Error.DisconnectReason DisconnectReason;
}
@@ -221,6 +220,9 @@ public void Execute()
packetProcessor.PrependToPayload((byte)MessageType.Data);
packetProcessor.ConnectionRef = connectionData.UnderlyingConnection;
+
+ connectionData.LastSendTime = Time;
+ ConnectionsData[connection] = connectionData;
}
// Send all control messages
@@ -239,7 +241,7 @@ private void SendControlCommand(ref ControlPacketCommand controlCommand)
controlCommand.CopyTo(ref packetProcessor);
var connectionData = ConnectionsData[controlCommand.Connection];
- connectionData.LastNonDataSend = Time;
+ connectionData.LastSendTime = Time;
ConnectionsData[controlCommand.Connection] = connectionData;
packetProcessor.ConnectionRef = connectionData.UnderlyingConnection;
@@ -357,7 +359,7 @@ private void ProcessConnecting(ref ConnectionId connectionId)
// The connection was just created, we need to initialize it.
connectionData.State = ConnectionState.AwaitingAccept;
connectionData.Token = RandomHelpers.GetRandomConnectionToken();
- connectionData.LastNonDataSend = Time;
+ connectionData.LastSendTime = Time;
connectionData.ConnectionAttempts++;
TokensHashMap.Add(connectionData.Token, connectionId);
@@ -370,8 +372,8 @@ private void ProcessConnecting(ref ConnectionId connectionId)
}
// Check for connect timeout and connection attempts.
- // Note that while connecting, LastNonDataSend can only track connection requests.
- if (Time - connectionData.LastNonDataSend > ConnectTimeout)
+ // Note that while connecting, LastSendTime can only track connection requests.
+ if (Time - connectionData.LastSendTime > ConnectTimeout)
{
if (connectionData.ConnectionAttempts >= MaxConnectionAttempts)
{
@@ -385,7 +387,7 @@ private void ProcessConnecting(ref ConnectionId connectionId)
else
{
connectionData.ConnectionAttempts++;
- connectionData.LastNonDataSend = Time;
+ connectionData.LastSendTime = Time;
ConnectionsData[connectionId] = connectionData;
@@ -412,11 +414,9 @@ private void ProcessConnected(ref ConnectionId connectionId)
}
// Check for the heartbeat timeout.
- if (HeartbeatTimeout > 0 &&
- Time - connectionData.LastReceiveTime > HeartbeatTimeout &&
- Time - connectionData.LastNonDataSend > HeartbeatTimeout)
+ if (HeartbeatTimeout > 0 && Time - connectionData.LastSendTime > HeartbeatTimeout)
{
- ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Ping, ref connectionData.Token));
+ ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Heartbeat, ref connectionData.Token));
}
}
@@ -473,15 +473,7 @@ private void ProcessReceivedMessages()
packetProcessor.ConnectionRef = connectionId;
break;
}
- case MessageType.Ping:
- {
- PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef);
- packetProcessor.Drop();
-
- ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Pong, ref connectionToken));
- break;
- }
- case MessageType.Pong:
+ case MessageType.Heartbeat:
{
PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef);
packetProcessor.Drop();
@@ -557,22 +549,21 @@ private bool ProcessHandshakeReceive(ref PacketProcessor packetProcessor)
{
case HandshakeType.ConnectionRequest:
{
- // Received a duplicated connection request. Which is OK as client could be retrying.
- if (connectionId.IsCreated)
+ // Whole new connection request for a new connection.
+ if (!connectionId.IsCreated)
{
- ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken));
- return true;
+ connectionId = Connections.StartConnecting(ref packetProcessor.EndpointRef);
+ Connections.FinishConnectingFromRemote(ref connectionId);
+ connectionData = new SimpleConnectionData
+ {
+ State = ConnectionState.Established,
+ Token = connectionToken,
+ UnderlyingConnection = packetProcessor.ConnectionRef,
+ };
+ TokensHashMap.Add(connectionToken, connectionId);
}
- connectionId = Connections.StartConnecting(ref packetProcessor.EndpointRef);
- Connections.FinishConnectingFromRemote(ref connectionId);
- connectionData = new SimpleConnectionData
- {
- State = ConnectionState.Established,
- Token = connectionToken,
- UnderlyingConnection = packetProcessor.ConnectionRef,
- };
- TokensHashMap.Add(connectionToken, connectionId);
+ connectionData.LastSendTime = Time;
ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken));
break;
}
@@ -720,7 +711,7 @@ private void ProcessConnecting(ref ConnectionId connectionId)
// The connection was just created, we need to initialize it.
connectionData.State = ConnectionState.AwaitingAccept;
connectionData.Token = RandomHelpers.GetRandomConnectionToken();
- connectionData.LastNonDataSend = Time;
+ connectionData.LastSendTime = Time;
connectionData.ConnectionAttempts++;
TokensHashMap.Add(connectionData.Token, connectionId);
@@ -733,8 +724,8 @@ private void ProcessConnecting(ref ConnectionId connectionId)
}
// Check for connect timeout and connection attempts.
- // Note that while connecting, LastNonDataSend can only track connection requests.
- if (Time - connectionData.LastNonDataSend > ConnectTimeout)
+ // Note that while connecting, LastSendTime can only track connection requests.
+ if (Time - connectionData.LastSendTime > ConnectTimeout)
{
if (connectionData.ConnectionAttempts >= MaxConnectionAttempts)
{
@@ -748,7 +739,7 @@ private void ProcessConnecting(ref ConnectionId connectionId)
else
{
connectionData.ConnectionAttempts++;
- connectionData.LastNonDataSend = Time;
+ connectionData.LastSendTime = Time;
ConnectionsData[connectionId] = connectionData;
@@ -775,11 +766,9 @@ private void ProcessConnected(ref ConnectionId connectionId)
}
// Check for the heartbeat timeout.
- if (HeartbeatTimeout > 0 &&
- Time - connectionData.LastReceiveTime > HeartbeatTimeout &&
- Time - connectionData.LastNonDataSend > HeartbeatTimeout)
+ if (HeartbeatTimeout > 0 && Time - connectionData.LastSendTime > HeartbeatTimeout)
{
- ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Ping, ref connectionData.Token));
+ ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Heartbeat, ref connectionData.Token));
}
}
@@ -836,15 +825,7 @@ private void ProcessReceivedMessages()
packetProcessor.ConnectionRef = connectionId;
break;
}
- case MessageType.Ping:
- {
- PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef);
- packetProcessor.Drop();
-
- ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Pong, ref connectionToken));
- break;
- }
- case MessageType.Pong:
+ case MessageType.Heartbeat:
{
PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef);
packetProcessor.Drop();
@@ -920,22 +901,21 @@ private bool ProcessHandshakeReceive(ref PacketProcessor packetProcessor)
{
case HandshakeType.ConnectionRequest:
{
- // Received a duplicated connection request. Which is OK as client could be retrying.
- if (connectionId.IsCreated)
+ // Whole new connection request for a new connection.
+ if (!connectionId.IsCreated)
{
- ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken));
- return true;
+ connectionId = Connections.StartConnecting(ref packetProcessor.EndpointRef);
+ Connections.FinishConnectingFromRemote(ref connectionId);
+ connectionData = new SimpleConnectionData
+ {
+ State = ConnectionState.Established,
+ Token = connectionToken,
+ UnderlyingConnection = packetProcessor.ConnectionRef,
+ };
+ TokensHashMap.Add(connectionToken, connectionId);
}
- connectionId = Connections.StartConnecting(ref packetProcessor.EndpointRef);
- Connections.FinishConnectingFromRemote(ref connectionId);
- connectionData = new SimpleConnectionData
- {
- State = ConnectionState.Established,
- Token = connectionToken,
- UnderlyingConnection = packetProcessor.ConnectionRef,
- };
- TokensHashMap.Add(connectionToken, connectionId);
+ connectionData.LastSendTime = Time;
ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken));
break;
}
diff --git a/Runtime/Layers/StreamSegmentationLayer.cs b/Runtime/Layers/StreamSegmentationLayer.cs
new file mode 100644
index 0000000..0d4e868
--- /dev/null
+++ b/Runtime/Layers/StreamSegmentationLayer.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Diagnostics;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+
+namespace Unity.Networking.Transport
+{
+ ///
+ /// Force message segmentation to and from an underlying TCP stream for testing purposes.
+ ///
+ ///
+ /// This layer is meant to be used on top of a TCPNetworkInterface to induce scenarios of message spliting in
+ /// the receive queue of each peer by splitting outgoing packets larger than a certain segment size. If a host
+ /// then sends enough messages in a burst before the remote peer's update, compaction should naturally occur in
+ /// the remote peer's TCP RCVBUF and packets of MTU size produced by the TCPNetworkInterface will have a higher
+ /// probability of containing multiple messages. When both peers are updated concurrently with a fast enough
+ /// channel (e.g. loopback) the remote peer would see a higher probability of partial messages given the original
+ /// ones were larger than the segmentation.
+ ///
+ internal struct StreamSegmentationLayer : INetworkLayer
+ {
+ [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
+ static void Warn(string msg) => UnityEngine.Debug.LogWarning(msg);
+
+ // TODO: Does this need to be configurable?
+ const int k_SegmentSize = NetworkParameterConstants.MTU / 5;
+
+ public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding)
+ {
+ return 0;
+ }
+
+ public void Dispose() { }
+
+ public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep)
+ {
+ return new SendJob
+ {
+ SendQueue = arguments.SendQueue,
+ }.Schedule(dep);
+ }
+
+ [BurstCompile]
+ unsafe struct SendJob : IJob
+ {
+ public PacketsQueue SendQueue;
+
+ public void Execute()
+ {
+ // Process all data messages
+ var count = SendQueue.Count;
+ for (int i = 0; i < count; i++)
+ {
+ var packetProcessor = SendQueue[i];
+
+ // Split the packet into smaller segments while possible
+ while (packetProcessor.Length > 0)
+ {
+ if (!SendQueue.EnqueuePacket(out var segment))
+ {
+ Warn("Send queue overflow");
+ break;
+ }
+ segment.ConnectionRef = packetProcessor.ConnectionRef;
+ segment.EndpointRef = packetProcessor.EndpointRef;
+ segment.SetUnsafeMetadata(0);
+
+ var nbytes = Math.Min(packetProcessor.Length, k_SegmentSize);
+ segment.AppendToPayload((byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset, nbytes);
+
+ packetProcessor.SetUnsafeMetadata(packetProcessor.Length - nbytes, packetProcessor.Offset + nbytes);
+ }
+
+ packetProcessor.Drop();
+ }
+ }
+ }
+
+ public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) => dep;
+ }
+}
diff --git a/Samples~/RelayPing/Scripts/RelayUtilities.cs.meta b/Runtime/Layers/StreamSegmentationLayer.cs.meta
similarity index 83%
rename from Samples~/RelayPing/Scripts/RelayUtilities.cs.meta
rename to Runtime/Layers/StreamSegmentationLayer.cs.meta
index 62f4662..7fb0146 100644
--- a/Samples~/RelayPing/Scripts/RelayUtilities.cs.meta
+++ b/Runtime/Layers/StreamSegmentationLayer.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: e92180c0b4b16420886b400a93a4dc59
+guid: 4b6e972ce430c3d41a21e8a3fff2f906
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Runtime/Layers/TLSLayer.cs b/Runtime/Layers/TLSLayer.cs
index a159e44..a30784d 100644
--- a/Runtime/Layers/TLSLayer.cs
+++ b/Runtime/Layers/TLSLayer.cs
@@ -5,6 +5,7 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
+using Unity.Mathematics;
using Unity.Networking.Transport.TLS;
using Unity.TLS.LowLevel;
using UnityEngine;
@@ -40,6 +41,16 @@ private struct TLSConnectionData
// Used to delete old half-open connections.
public long LastHandshakeUpdate;
+
+ // UnityTLS only returns full records when decrypting, so it's possible to receive more
+ // than an MTU's worth of data when decrypting a single packet (e.g. if the received
+ // packet contains the end of a previous record). This means we can't just replace the
+ // received packet's data with its decrypted equivalent (might not be enough room). Thus
+ // we decrypt everything in this buffer. What can be fitted inside the packet is then
+ // copied there, and any leftovers will be copied in the next packet (which might need
+ // to be a newly-enqueued one).
+ public fixed byte DecryptBuffer[NetworkParameterConstants.MTU * 2];
+ public int DecryptBufferLength;
}
internal ConnectionList m_ConnectionList;
@@ -207,44 +218,46 @@ private void ProcessHandshakeMessage(ref PacketProcessor packetProcessor)
private void ProcessDataMessage(ref PacketProcessor packetProcessor)
{
- var connectionId = packetProcessor.ConnectionRef;
var originalPacketOffset = packetProcessor.Offset;
+ var connectionId = packetProcessor.ConnectionRef;
+ var data = ConnectionsData[connectionId];
- // UnityTLS is going to read the encrypted packet directly from the packet
- // processor. Since it can't write the decrypted data in the same place, we need to
- // create a temporary buffer to store that data.
- var tempBuffer = new NativeArray(NetworkParameterConstants.MTU, Allocator.Temp);
- var tempBufferLength = 0;
-
- // The function unitytls_client_read_data exits once it has read a single record
- // (and presumably also if it can't even do that without blocking on I/O). So if the
- // packet contains multiple records, we need to call it multiple times, hence the
- // looping until the packet is empty.
+ // The function unitytls_client_read_data exits after reading a single record (or
+ // failing to read an full one). Since there could be multiple records per packet,
+ // we need to call it until we've fully read the received packet.
while (packetProcessor.Length > 0)
{
- // Adjust the buffer pointer/length to where the data should be written next.
- var bufferPtr = (byte*)tempBuffer.GetUnsafePtr() + tempBufferLength;
- var bufferLength = NetworkParameterConstants.MTU - tempBufferLength;
+ // Pointer and length of next available space in decryption buffer.
+ var bufferPtr = data.DecryptBuffer + data.DecryptBufferLength;
+ var bufferLength = (NetworkParameterConstants.MTU * 2) - data.DecryptBufferLength;
var decryptedLength = new UIntPtr();
- var result = Binding.unitytls_client_read_data(ConnectionsData[connectionId].UnityTLSClientPtr,
- bufferPtr, new UIntPtr((uint)bufferLength), &decryptedLength);
+ var result = Binding.unitytls_client_read_data(
+ data.UnityTLSClientPtr, bufferPtr, new UIntPtr((uint)bufferLength), &decryptedLength);
- // Decrypted length shouldn't be 0, but we're playing it safe.
- if (result != Binding.UNITYTLS_SUCCESS || decryptedLength.ToUInt32() == 0)
+ var wouldBlock = result == Binding.UNITYTLS_USER_WOULD_BLOCK;
+ if (wouldBlock || (result == Binding.UNITYTLS_SUCCESS && decryptedLength.ToUInt32() == 0))
{
- // Bad record? Unknown what could be causing this, but in any case don't log
- // an error since this could be used to flood the logs. Just stop processing
- // and return what we were able to decrypt (if anything). If the situation
- // is bad enough, UnityTLS client will have failed and we'll clean up later.
+ // The "would block" error (or a successful return with nothing decrypted)
+ // means UnityTLS saw the beginning of a record but couldn't read it in
+ // full. There can't be anything more to read at this point.
+ break;
+ }
+ else if (result != Binding.UNITYTLS_SUCCESS)
+ {
+ // The error will be picked up in CheckForFailedClient.
+ Debug.LogError($"Failed to decrypt packet (error: {result}). Likely internal TLS failure. Closing connection.");
break;
}
- tempBufferLength += (int)decryptedLength.ToUInt32();
+ data.DecryptBufferLength += (int)decryptedLength.ToUInt32();
}
+ // We've now read the entire packet, now copy the decrypted data back in it.
packetProcessor.SetUnsafeMetadata(0, originalPacketOffset);
- packetProcessor.AppendToPayload(tempBuffer.GetUnsafePtr(), tempBufferLength);
+ CopyDecryptBufferToPacket(ref data, ref packetProcessor);
+
+ ConnectionsData[connectionId] = data;
}
private void ProcessUnderlyingConnectionList()
@@ -281,6 +294,9 @@ private void ProcessConnectionList()
switch (connectionState)
{
+ case NetworkConnection.State.Connected:
+ HandleConnectedState(connectionId);
+ break;
case NetworkConnection.State.Connecting:
HandleConnectingState(connectionId);
break;
@@ -294,6 +310,24 @@ private void ProcessConnectionList()
}
}
+ private void HandleConnectedState(ConnectionId connection)
+ {
+ var data = ConnectionsData[connection];
+
+ // Only thing we need to check in the connected state is if we need to enqueue any
+ // leftover decrypted data. Note that we don't do anything if we can't enqueue a new
+ // packet because it's safe to leave the data in its decryption buffer (we'll just
+ // receive it on the next update).
+ if (data.DecryptBufferLength > 0 && ReceiveQueue.EnqueuePacket(out var packetProcessor))
+ {
+ packetProcessor.ConnectionRef = connection;
+ packetProcessor.EndpointRef = Connections.GetConnectionEndpoint(connection);
+ CopyDecryptBufferToPacket(ref data, ref packetProcessor);
+
+ ConnectionsData[connection] = data;
+ }
+ }
+
private void HandleConnectingState(ConnectionId connection)
{
var endpoint = Connections.GetConnectionEndpoint(connection);
@@ -344,12 +378,25 @@ private void HandleDisconnectingState(ConnectionId connection)
private void CheckForFailedClient(ConnectionId connection)
{
var data = ConnectionsData[connection];
+ var clientPtr = data.UnityTLSClientPtr;
- if (data.UnityTLSClientPtr == null)
+ if (clientPtr == null)
return;
- if (Binding.unitytls_client_get_state(data.UnityTLSClientPtr) == Binding.UnityTLSClientState_Fail)
+ ulong dummy;
+ var errorState = Binding.unitytls_client_get_errorsState(clientPtr, &dummy);
+ var clientState = Binding.unitytls_client_get_state(clientPtr);
+
+ if (errorState != Binding.UNITYTLS_SUCCESS || clientState == Binding.UnityTLSClientState_Fail)
{
+ // The only way to get a failed client is because of a failed handshake.
+ if (clientState == Binding.UnityTLSClientState_Fail)
+ {
+ // TODO Would be nice to translate the numerical step in a string.
+ var handshakeStep = Binding.unitytls_client_get_handshake_state(clientPtr);
+ Debug.LogError($"TLS handshake failed at step {handshakeStep}. Closing connection.");
+ }
+
UnderlyingConnections.StartDisconnecting(ref data.UnderlyingConnection);
Connections.StartDisconnecting(ref connection);
@@ -380,6 +427,22 @@ private void CheckForHalfOpenConnection(ConnectionId connection)
}
}
+ private void CopyDecryptBufferToPacket(ref TLSConnectionData data, ref PacketProcessor packetProcessor)
+ {
+ fixed(byte* decryptBuffer = data.DecryptBuffer)
+ {
+ var copyLength = math.min(packetProcessor.BytesAvailableAtEnd, data.DecryptBufferLength);
+ packetProcessor.AppendToPayload(decryptBuffer, copyLength);
+ data.DecryptBufferLength -= copyLength;
+
+ // If there's still data in the decryption buffer, copy it to the beginning.
+ if (data.DecryptBufferLength > 0)
+ {
+ UnsafeUtility.MemMove(decryptBuffer, decryptBuffer + copyLength, data.DecryptBufferLength);
+ }
+ }
+ }
+
private void AdvanceHandshake(Binding.unitytls_client* clientPtr)
{
while (Binding.unitytls_client_handshake(clientPtr) == Binding.UNITYTLS_HANDSHAKE_STEP);
@@ -439,23 +502,14 @@ public void Execute()
UnityTLSCallbackContext->SendQueueIndex = i;
var connectionId = packetProcessor.ConnectionRef;
- var clientPtr = ConnectionsData[connectionId].UnityTLSClientPtr;
-
- // Only way this can happen is if a previous send caused a failure.
- var clientState = Binding.unitytls_client_get_state(clientPtr);
- if (clientState != Binding.UnityTLSClientState_Messaging)
- {
- packetProcessor.Drop();
- continue;
- }
-
packetProcessor.ConnectionRef = ConnectionsData[connectionId].UnderlyingConnection;
+ var clientPtr = ConnectionsData[connectionId].UnityTLSClientPtr;
var packetPtr = (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset;
var result = Binding.unitytls_client_send_data(clientPtr, packetPtr, new UIntPtr((uint)packetProcessor.Length));
if (result != Binding.UNITYTLS_SUCCESS)
{
- Debug.LogError($"Failed to encrypt packet (TLS error: {result}). Packet will not be sent.");
+ Debug.LogError($"Failed to encrypt packet (error: {result}). Likely internal TLS failure. Closing connection.");
packetProcessor.Drop();
}
}
diff --git a/Runtime/NetworkDriver.cs b/Runtime/NetworkDriver.cs
index f23a0dc..96ead3d 100644
--- a/Runtime/NetworkDriver.cs
+++ b/Runtime/NetworkDriver.cs
@@ -675,8 +675,7 @@ void InternalUpdate()
m_PipelineProcessor.UpdateReceive(ref this, out var updateCount);
- // TODO: Find a good way to establish a good limit (connections*pipelines/2?)
- if (updateCount > (m_NetworkStack.Connections.Count - m_NetworkStack.Connections.FreeList.Count) * 64)
+ if (updateCount > m_NetworkStack.Connections.Count * 64)
{
UnityEngine.Debug.LogWarning(
FixedString.Format("A lot of pipeline updates have been queued, possibly too many being scheduled in pipeline logic, queue count: {0}", updateCount));
@@ -684,7 +683,7 @@ void InternalUpdate()
m_DefaultHeaderFlags = UdpCHeader.HeaderFlags.HasPipeline;
m_PipelineProcessor.UpdateSend(ToConcurrentSendOnly(), out updateCount);
- if (updateCount > (m_NetworkStack.Connections.Count - m_NetworkStack.Connections.FreeList.Count) * 64)
+ if (updateCount > m_NetworkStack.Connections.Count * 64)
{
UnityEngine.Debug.LogWarning(
FixedString.Format("A lot of pipeline updates have been queued, possibly too many being scheduled in pipeline logic, queue count: {0}", updateCount));
diff --git a/Runtime/NetworkDriverIdentifierParameter.cs b/Runtime/NetworkDriverIdentifierParameter.cs
new file mode 100644
index 0000000..f6a82ad
--- /dev/null
+++ b/Runtime/NetworkDriverIdentifierParameter.cs
@@ -0,0 +1,37 @@
+using Unity.Collections;
+
+namespace Unity.Networking.Transport
+{
+ public struct NetworkDriverIdentifierParameter : INetworkParameter
+ {
+ public FixedString32Bytes Label;
+
+ public bool Validate()
+ {
+ var valid = true;
+
+ if (Label.IsEmpty)
+ {
+ valid = false;
+ UnityEngine.Debug.LogError($"The NetworkDriver identifier must be not empty");
+ }
+
+ return valid;
+ }
+ }
+
+ public static class NetworkDriverIdentifierParameterExtensions
+ {
+ public static ref NetworkSettings WithDriverIdentifierParameters(ref this NetworkSettings settings, FixedString32Bytes label)
+ {
+ var parameter = new NetworkDriverIdentifierParameter
+ {
+ Label = label,
+ };
+
+ settings.AddRawParameterStruct(ref parameter);
+
+ return ref settings;
+ }
+ }
+}
diff --git a/Runtime/NetworkDriverIdentifierParameter.cs.meta b/Runtime/NetworkDriverIdentifierParameter.cs.meta
new file mode 100644
index 0000000..26433d0
--- /dev/null
+++ b/Runtime/NetworkDriverIdentifierParameter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 86af0fc7e527c44478805c9f2d2c45d1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/NetworkParams.cs b/Runtime/NetworkParams.cs
index f3d3752..8f605a9 100644
--- a/Runtime/NetworkParams.cs
+++ b/Runtime/NetworkParams.cs
@@ -21,13 +21,8 @@ public interface INetworkParameter
public struct NetworkParameterConstants
{
/// The default size of the event queue.
- public const int InitialEventQueueSize = 100;
- public const int InvalidConnectionId = -1;
+ internal const int InitialEventQueueSize = 100;
- ///
- /// The default size of the DataStreamWriter. This value can be overridden using the .
- ///
- public const int DriverDataStreamSize = 64 * 1024;
/// The default connection timeout value. This value can be overridden using the
public const int ConnectTimeoutMS = 1000;
/// The default max connection attempts value. This value can be overridden using the
diff --git a/Runtime/NetworkPipeline.cs b/Runtime/NetworkPipeline.cs
index 9f26ff6..1dbf142 100644
--- a/Runtime/NetworkPipeline.cs
+++ b/Runtime/NetworkPipeline.cs
@@ -297,6 +297,11 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta
// If the call comes from update, the sendHandle is set to default.
bool inUpdateCall = sendHandle.data == IntPtr.Zero;
+ // Save the latest error returned by a pipeline send. We need to do so because we
+ // can't rely on an error being bubbled up immediately once encountered, since a
+ // successful resume call could overwrite it.
+ int savedErrorCode = 0;
+
var resumeQ = new NativeList(16, Allocator.Temp);
int resumeQStart = 0;
@@ -363,7 +368,10 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta
if (inboundBuffer.bufferWithHeadersLength == 0)
{
if ((requests & NetworkPipelineStage.Requests.Error) != 0 && !inUpdateCall)
+ {
retval = sendResult;
+ savedErrorCode = sendResult;
+ }
break;
}
@@ -457,7 +465,9 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta
}
if (sendHandle.data != IntPtr.Zero)
driver.AbortSend(sendHandle);
- return retval;
+
+ // If we encountered an error, it takes precendence over the last returned value.
+ return savedErrorCode < 0 ? savedErrorCode : retval;
}
private unsafe int ProcessSendStage(int startStage, int internalBufferOffset, int internalSharedBufferOffset,
@@ -822,23 +832,24 @@ internal unsafe void UpdateSend(NetworkDriver.Concurrent driver, out int updateC
for (int connectionOffset = 0; connectionOffset < m_SendBuffer.Length; connectionOffset += sizePerConnection[SendSizeOffset])
sendBufferLock[connectionOffset / 4] = 0;
- NativeArray sendUpdates = new NativeArray(m_SendStageNeedsUpdateRead.Count + m_SendStageNeedsUpdate.Length, Allocator.Temp);
+ NativeList sendUpdates = new NativeList(m_SendStageNeedsUpdateRead.Count + m_SendStageNeedsUpdate.Length, Allocator.Temp);
UpdatePipeline updateItem;
- updateCount = 0;
while (m_SendStageNeedsUpdateRead.TryDequeue(out updateItem))
{
if (driver.GetConnectionState(updateItem.connection) == NetworkConnection.State.Connected)
- sendUpdates[updateCount++] = updateItem;
+ AddSendUpdate(updateItem.connection, updateItem.stage, updateItem.pipeline, sendUpdates);
}
- int startLength = updateCount;
for (int i = 0; i < m_SendStageNeedsUpdate.Length; i++)
{
+ updateItem = m_SendStageNeedsUpdate[i];
if (driver.GetConnectionState(m_SendStageNeedsUpdate[i].connection) == NetworkConnection.State.Connected)
- sendUpdates[updateCount++] = m_SendStageNeedsUpdate[i];
+ AddSendUpdate(updateItem.connection, updateItem.stage, updateItem.pipeline, sendUpdates);
}
+ updateCount = sendUpdates.Length;
+
NativeList currentUpdates = new NativeList(128, Allocator.Temp);
// Move the updates requested in this iteration to the concurrent queue so it can be read/parsed in update routine
for (int i = 0; i < updateCount; ++i)
diff --git a/Runtime/NetworkSettings.cs b/Runtime/NetworkSettings.cs
index 82bcbfa..004482b 100644
--- a/Runtime/NetworkSettings.cs
+++ b/Runtime/NetworkSettings.cs
@@ -25,6 +25,9 @@ private struct ParameterSlice
private byte m_Initialized;
private byte m_ReadOnly;
+ /// If the settings have been created (e.g. not disposed).
+ public bool IsCreated => m_Initialized == 0 || m_Parameters.IsCreated;
+
private bool EnsureInitializedOrError()
{
if (m_Initialized == 0)
diff --git a/Runtime/NetworkStack.cs b/Runtime/NetworkStack.cs
index 1203fae..d831344 100644
--- a/Runtime/NetworkStack.cs
+++ b/Runtime/NetworkStack.cs
@@ -161,23 +161,27 @@ internal static NetworkStack CreateForSettings
// stack.AddLayer(new LogLayer(), ref networkSettings); // This will print packets for debugging
#if !UNITY_WEBGL || UNITY_EDITOR
-#if ENABLE_MANAGED_UNITYTLS
- // If using the TCP interface or WebSocket interface (on non-WebGL platforms), we need
- // to add the TLS layer before the simulator layer, since it expects a reliable stream.
if (networkInterface is TCPNetworkInterface || networkInterface is WebSocketNetworkInterface)
{
+ // Uncomment to induce message splitting for debugging.
+ if (networkSettings.TryGet(out _))
+ stack.AddLayer(new StreamSegmentationLayer(), ref networkSettings);
+
+#if ENABLE_MANAGED_UNITYTLS
+ // If using the TCP interface or WebSocket interface (on non-WebGL platforms), we need
+ // to add the TLS layer before the simulator layer, since it expects a reliable stream.
if (networkSettings.TryGet(out _))
stack.AddLayer(new TLSLayer(), ref networkSettings);
- }
#endif // ENABLE_MANAGED_UNITYTLS
- // TCP interface requires a layer to manage the stream to datagram transition.
- if (networkInterface is TCPNetworkInterface)
- stack.AddLayer(new StreamToDatagramLayer(), ref networkSettings);
+ // TCP interface requires a layer to manage the stream to datagram transition.
+ if (networkInterface is TCPNetworkInterface)
+ stack.AddLayer(new StreamToDatagramLayer(), ref networkSettings);
- // On non-WebGL platforms, add the WebSocket layer if using WebSocket interface.
- if (networkInterface is WebSocketNetworkInterface)
- stack.AddLayer(new WebSocketLayer(), ref networkSettings);
+ // On non-WebGL platforms, add the WebSocket layer if using WebSocket interface.
+ if (networkInterface is WebSocketNetworkInterface)
+ stack.AddLayer(new WebSocketLayer(), ref networkSettings);
+ }
#endif // !UNITY_WEBGL || UNITY_EDITOR
// Now we can add the simulator layer, which should be as low in the stack as possible.
@@ -193,7 +197,7 @@ internal static NetworkStack CreateForSettings
? networkSettings.GetRelayParameters().ServerData.IsSecure == 1
: networkSettings.TryGet(out _);
- if (isSecure && networkInterface is UDPNetworkInterface)
+ if (isSecure && !(networkInterface is TCPNetworkInterface || networkInterface is WebSocketNetworkInterface))
stack.AddLayer(new DTLSLayer(), ref networkSettings);
#endif
diff --git a/Runtime/Pipelines/ReliableSequencedPipelineStage.cs b/Runtime/Pipelines/ReliableSequencedPipelineStage.cs
index 88b4d0b..f05002f 100644
--- a/Runtime/Pipelines/ReliableSequencedPipelineStage.cs
+++ b/Runtime/Pipelines/ReliableSequencedPipelineStage.cs
@@ -102,14 +102,12 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in
{
// Request an update to see if a queued packet needs to be resent later or if an ack packet should be sent
requests = NetworkPipelineStage.Requests.Update;
- bool needsResume = false;
var header = new ReliableUtility.PacketHeader();
var reliable = (ReliableUtility.Context*)ctx.internalProcessBuffer;
- needsResume = ReliableUtility.ReleaseOrResumePackets(ctx);
- if (needsResume)
- requests |= NetworkPipelineStage.Requests.Resume;
+ // Release any packets that might have been acknowledged since the last call.
+ ReliableUtility.ReleaseAcknowledgedPackets(ctx);
if (inboundBuffer.bufferLength > 0)
{
@@ -122,27 +120,38 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in
requests |= NetworkPipelineStage.Requests.Error;
return (int)Error.StatusCode.NetworkSendQueueFull;
}
- ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf());
- if (reliable->Resume != ReliableUtility.NullEntry)
- requests |= NetworkPipelineStage.Requests.Resume;
+ ctx.header.Clear();
+ ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf());
reliable->PreviousTimestamp = ctx.timestamp;
return (int)Error.StatusCode.Success;
}
+ // At this point we know we're either in a resume or update call.
+
if (reliable->Resume != ReliableUtility.NullEntry)
{
reliable->LastSentTime = ctx.timestamp;
- inboundBuffer = ReliableUtility.ResumeSend(ctx, out header, ref needsResume);
- if (needsResume)
+ inboundBuffer = ReliableUtility.ResumeSend(ctx, out header);
+
+ // Check if we need to resume again after this packet.
+ reliable->Resume = ReliableUtility.GetNextSendResumeSequence(ctx);
+ if (reliable->Resume != ReliableUtility.NullEntry)
requests |= NetworkPipelineStage.Requests.Resume;
- ctx.header.Clear();
+ ctx.header.Clear();
ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf());
reliable->PreviousTimestamp = ctx.timestamp;
return (int)Error.StatusCode.Success;
}
+ // At this point we know we're in an update call.
+
+ // Check if we need to resume (e.g. resent packets).
+ reliable->Resume = ReliableUtility.GetNextSendResumeSequence(ctx);
+ if (reliable->Resume != ReliableUtility.NullEntry)
+ requests |= NetworkPipelineStage.Requests.Resume;
+
if (ReliableUtility.ShouldSendAck(ctx))
{
reliable->LastSentTime = ctx.timestamp;
@@ -158,6 +167,7 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in
inboundBuffer.SetBufferFromBufferWithHeaders();
return (int)Error.StatusCode.Success;
}
+
reliable->PreviousTimestamp = ctx.timestamp;
return (int)Error.StatusCode.Success;
}
diff --git a/Runtime/Pipelines/ReliableUtility.cs b/Runtime/Pipelines/ReliableUtility.cs
index 85211c9..bc1c2ec 100644
--- a/Runtime/Pipelines/ReliableUtility.cs
+++ b/Runtime/Pipelines/ReliableUtility.cs
@@ -397,58 +397,86 @@ private static unsafe int GetIndex(byte* self, int index)
}
///
- /// Acknowledge the reception of packets which have been sent. The reliability
- /// shared context/state is updated when packets are received from the other end
- /// of the connection. The other side will update it's ackmask with which packets
- /// have been received (starting from last received sequence ID) each time it sends
- /// a packet back. This checks the resend timers on each non-acknowledged packet
- /// and notifies if it's time to resend yet.
+ /// Get a sequence number that will not wrap if we substract the window size from it, but
+ /// that will still map to the correct index in the packet storage (the returned sequence
+ /// number may not match the actual sequence number, but it's fine to use it like the
+ /// actual sequence number if we're just accessing the packet storage with it).
///
- /// Pipeline context, contains the buffer slices this pipeline connection owns.
- ///
- internal static unsafe bool ReleaseOrResumePackets(NetworkPipelineContext context)
+ private static unsafe ushort GetNonWrappingLastAckedSequenceNumber(NetworkPipelineContext context)
+ {
+ SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer;
+
+ // Last sequence ID acked by the remote.
+ var lastOwnSequenceIdAckedByRemote = (ushort)reliable->SentPackets.Acked;
+
+ // To deal with wrapping, chop off the upper half of the sequence ID and multiply by
+ // window size. It will then never wrap but will map to the correct index in the packet
+ // storage. Wrapping would only happen on low sequence IDs since we substract the window
+ // size from them.
+ return (ushort)(reliable->WindowSize * ((1 - lastOwnSequenceIdAckedByRemote) >> 15));
+ }
+
+ /// Release packets which have been acknowledged by the remote.
+ internal static unsafe void ReleaseAcknowledgedPackets(NetworkPipelineContext context)
{
SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer;
- Context* ctx = (Context*)context.internalProcessBuffer;
- // Last sequence ID and ackmask we received from the remote peer, these are confirmed delivered packets
+ // Last sequence ID and ackmask we received from the remote peer.
var lastReceivedAckMask = reliable->SentPackets.AckMask;
var lastOwnSequenceIdAckedByRemote = (ushort)reliable->SentPackets.Acked;
- // To deal with wrapping, chop off the upper half of the sequence ID and multiply by window size, it
- // will then never wrap but will map to the correct index in the packet storage, wrapping happens when
- // sending low sequence IDs (since it checks sequence IDs backwards in time).
- var sequence = (ushort)(reliable->WindowSize * ((1 - lastOwnSequenceIdAckedByRemote) >> 15));
+ var sequence = GetNonWrappingLastAckedSequenceNumber(context);
- // Check each slot in the window, starting from the sequence ID calculated above (this isn't the
- // latest sequence ID though as it was adjusted to avoid wrapping)
+ // Check each slot in the window, starting from the sequence ID calculated above (this
+ // isn't the latest sequence ID though as it was adjusted to avoid wrapping).
for (int i = 0; i < reliable->WindowSize; i++)
{
var info = GetPacketInformation(context.internalProcessBuffer, sequence);
if (info->SequenceId >= 0)
{
- // Check the bit for this sequence ID against the ackmask. Bit 0 in the ackmask is the latest
- // ackedSeqId, bit 1 latest ackedSeqId - 1 (one older) and so on. If bit X is 1 then ackedSeqId-X is acknowledged
+ // Check the bit for this sequence ID against the ackmask. Bit 0 in the ackmask
+ // is the latest acked sequence ID, bit 1 latest minus 1 (one older) and so on.
+ // If bit X is 1 then last acked sequence ID minus X is acknowledged.
var ackBits = 1 << (lastOwnSequenceIdAckedByRemote - info->SequenceId);
- // Release if this seqId has been flipped on in the ackmask (so it's acknowledged)
- // Ignore if sequence ID is out of window range of the last acknowledged id
- if (SequenceHelpers.AbsDistance((ushort)lastOwnSequenceIdAckedByRemote, (ushort)info->SequenceId) < reliable->WindowSize && (ackBits & lastReceivedAckMask) != 0)
+ // Release if this ID has been flipped on in the ackmask (it's acknowledged).
+ // Ignore if sequence ID is out of window range of the last acknowledged ID.
+ var distance = SequenceHelpers.AbsDistance(lastOwnSequenceIdAckedByRemote, (ushort)info->SequenceId);
+ if (distance < reliable->WindowSize && (ackBits & lastReceivedAckMask) != 0)
{
Release(context.internalProcessBuffer, info->SequenceId);
info->SendTime = -1;
- sequence = (ushort)(sequence - 1);
- continue;
}
+ }
+ sequence = (ushort)(sequence - 1);
+ }
+ }
+
+ /// Get the next sequence ID that needs to be resumed (NullEntry if none).
+ internal static unsafe int GetNextSendResumeSequence(NetworkPipelineContext context)
+ {
+ SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer;
+
+ var sequence = GetNonWrappingLastAckedSequenceNumber(context);
+ var resume = NullEntry;
+
+ // Check each slot in the window, starting from the sequence ID calculated above (this
+ // isn't the latest sequence ID though as it was adjusted to avoid wrapping).
+ for (int i = 0; i < reliable->WindowSize; i++)
+ {
+ var info = GetPacketInformation(context.internalProcessBuffer, sequence);
+ if (info->SequenceId >= 0)
+ {
var timeToResend = CurrentResendTime(context.internalSharedProcessBuffer);
if (context.timestamp > info->SendTime + timeToResend)
{
- ctx->Resume = info->SequenceId;
+ resume = info->SequenceId;
}
}
sequence = (ushort)(sequence - 1);
}
- return ctx->Resume != NullEntry;
+
+ return resume;
}
///
@@ -497,10 +525,9 @@ internal static unsafe InboundRecvBuffer ResumeReceive(NetworkPipelineContext co
///
/// Pipeline context, we'll use both the shared reliability context and send context.
/// Packet header for the packet payload we're resending.
- /// Indicates if a pipeline resume is needed again.
/// Buffer slice to packet payload.
///
- internal static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context, out PacketHeader header, ref bool needsResume)
+ internal static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context, out PacketHeader header)
{
SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer;
Context* ctx = (Context*)context.internalProcessBuffer;
@@ -533,20 +560,6 @@ internal static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext conte
inbound.SetBufferFromBufferWithHeaders();
reliable->stats.PacketsResent++;
- needsResume = false;
- ctx->Resume = NullEntry;
-
- // Check if another packet needs to be resent right after this one
- for (int i = sequence + 1; i < reliable->ReceivedPackets.Sequence + 1; i++)
- {
- var timeToResend = CurrentResendTime(context.internalSharedProcessBuffer);
- information = GetPacketInformation(context.internalProcessBuffer, i);
- if (information->SequenceId >= 0 && information->SendTime + timeToResend > context.timestamp)
- {
- needsResume = true;
- ctx->Resume = i;
- }
- }
return inbound;
}
@@ -739,6 +752,10 @@ internal static unsafe int Read(NetworkPipelineContext context, PacketHeader hea
var ackBit = 1 << distance;
if ((ackBit & reliable->ReceivedPackets.AckMask) != 0)
{
+ // Still valuable to check ACKs in a duplicated packet, since there might be
+ // more information than on the original packet if it's a resend.
+ ReadAckPacket(context, header);
+
reliable->stats.PacketsDuplicated++;
return (int)ErrorCodes.Duplicated_Packet;
}
diff --git a/Runtime/Pipelines/SimulatorUtility.cs b/Runtime/Pipelines/SimulatorUtility.cs
index 47d28e1..5bb1762 100644
--- a/Runtime/Pipelines/SimulatorUtility.cs
+++ b/Runtime/Pipelines/SimulatorUtility.cs
@@ -9,6 +9,18 @@ namespace Unity.Networking.Transport.Utilities
{
public static class SimulatorStageParameterExtensions
{
+ /// Set the parameters of the simulator pipeline stage.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static ref NetworkSettings WithSimulatorStageParameters(
ref this NetworkSettings settings,
int maxPacketCount,
@@ -124,8 +136,8 @@ public struct Parameters : INetworkParameter
public int MaxPacketSize;
///
- /// The random seed is used to set the initial seed of the random number generator. This is useful to get
- /// deterministic runs in tests for example that are dependant on the random number generator.
+ /// Value to use to seed the random number generator. For non-deterministic behavior, use a
+ /// dynamic value here (e.g. the result of a call to Stopwatch.GetTimestamp).
///
public uint RandomSeed;
@@ -206,11 +218,9 @@ internal static unsafe void InitializeContext(Parameters param, byte* sharedProc
ctx->PacketDropCount = 0;
ctx->Random = new Random();
if (param.RandomSeed > 0)
- {
ctx->Random.InitState(param.RandomSeed);
- }
else
- ctx->Random.InitState((uint)TimerHelpers.GetTicks());
+ ctx->Random.InitState();
}
private static unsafe bool GetEmptyDataSlot(NetworkPipelineContext ctx, byte* processBufferPtr, ref int packetPayloadOffset,
@@ -333,9 +343,7 @@ internal static unsafe bool TryDelayPacket(ref NetworkPipelineContext ctx, ref P
if (!foundSlot)
{
- UnityEngine.Debug.LogWarning($"Simulator has no space left in the delayed packets queue ({param.MaxPacketCount} packets already in queue) so must drop this packet! Increase MaxPacketCount during driver construction.");
- simCtx->PacketDropCount++;
- inboundBuffer = default;
+ UnityEngine.Debug.LogWarning($"Simulator has no space left in the delayed packets queue ({param.MaxPacketCount} packets already in queue). Letting packet go through. Increase MaxPacketCount during driver construction.");
return false;
}
diff --git a/Runtime/Relay/NetworkDriverRelayExtensions.cs b/Runtime/Relay/NetworkDriverRelayExtensions.cs
new file mode 100644
index 0000000..9f26846
--- /dev/null
+++ b/Runtime/Relay/NetworkDriverRelayExtensions.cs
@@ -0,0 +1,47 @@
+using System;
+using Unity.Networking.Transport;
+using UnityEngine;
+
+namespace Unity.Networking.Transport.Relay
+{
+ public static class NetworkDriverRelayExtensions
+ {
+ /// Get the current status of the connection to the relay server.
+ public static RelayConnectionStatus GetRelayConnectionStatus(this NetworkDriver driver)
+ {
+ if (driver.m_NetworkStack.TryGetLayer(out var layer))
+ {
+ return layer.ConnectionStatus;
+ }
+ else
+ {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ throw new InvalidOperationException("Can't call GetRelayConnectionStatus when not using the Relay.");
+#else
+ Debug.LogError("Can't call GetRelayConnectionStatus when not using the Relay.");
+ return RelayConnectionStatus.NotEstablished;
+#endif
+ }
+ }
+
+ /// Connect to the Relay server.
+ /// This method can only be used when using the Relay service.
+ /// The new (or default if failed).
+ public static NetworkConnection Connect(this NetworkDriver driver)
+ {
+ if (driver.CurrentSettings.TryGet(out var parameters))
+ {
+ return driver.Connect(parameters.ServerData.Endpoint);
+ }
+ else
+ {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ throw new InvalidOperationException("Can't call Connect without an endpoint when not using the Relay.");
+#else
+ Debug.LogError("Can't call Connect without an endpoint when not using the Relay.");
+ return default;
+#endif
+ }
+ }
+ }
+}
diff --git a/Runtime/Relay/NetworkDriverRelayExtensions.cs.meta b/Runtime/Relay/NetworkDriverRelayExtensions.cs.meta
new file mode 100644
index 0000000..30698d6
--- /dev/null
+++ b/Runtime/Relay/NetworkDriverRelayExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 74308f0e9b6269648b180e4cdf58156f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Relay/RelayAllocationId.cs b/Runtime/Relay/RelayAllocationId.cs
index 4c1af5e..0a2d572 100644
--- a/Runtime/Relay/RelayAllocationId.cs
+++ b/Runtime/Relay/RelayAllocationId.cs
@@ -12,7 +12,10 @@ public unsafe struct RelayAllocationId : IEquatable, ICompara
public const int k_Length = 16;
public fixed byte Value[k_Length];
- // Used by Relay SDK
+ /// Convert a raw buffer to a
+ /// Raw pointer to buffer to convert.
+ /// Length of the buffer to convert.
+ /// New .
public static RelayAllocationId FromBytePointer(byte* dataPtr, int length)
{
if (length != k_Length)
@@ -30,6 +33,17 @@ public static RelayAllocationId FromBytePointer(byte* dataPtr, int length)
return allocationId;
}
+ /// Convert a byte array to a
+ /// Array to convert.
+ /// New .
+ public static RelayAllocationId FromByteArray(byte[] data)
+ {
+ fixed(byte* ptr = data)
+ {
+ return RelayAllocationId.FromBytePointer(ptr, data.Length);
+ }
+ }
+
internal NetworkEndpoint ToNetworkEndpoint()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
diff --git a/Runtime/Relay/RelayConnectionData.cs b/Runtime/Relay/RelayConnectionData.cs
index 712e8c8..5add62f 100644
--- a/Runtime/Relay/RelayConnectionData.cs
+++ b/Runtime/Relay/RelayConnectionData.cs
@@ -12,15 +12,18 @@ public unsafe struct RelayConnectionData
public const int k_Length = 255;
public fixed byte Value[k_Length];
- // Used by Relay SDK
+ /// Convert a raw buffer to a
+ /// Raw pointer to buffer to convert.
+ /// Length of the buffer to convert.
+ /// New .
public static RelayConnectionData FromBytePointer(byte* dataPtr, int length)
{
- if (length != k_Length)
+ if (length > k_Length)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
- throw new ArgumentException($"Provided byte array length is invalid, must be {k_Length} but got {length}.");
+ throw new ArgumentException($"Provided byte array length is invalid, must be less or equal to {k_Length} but got {length}.");
#else
- UnityEngine.Debug.LogError($"Provided byte array length is invalid, must be {k_Length} but got {length}.");
+ UnityEngine.Debug.LogError($"Provided byte array length is invalid, must be less or equal to {k_Length} but got {length}.");
return default;
#endif
}
@@ -29,5 +32,16 @@ public static RelayConnectionData FromBytePointer(byte* dataPtr, int length)
UnsafeUtility.MemCpy(connectionData.Value, dataPtr, length);
return connectionData;
}
+
+ /// Convert a byte array to a
+ /// Array to convert.
+ /// New .
+ public static RelayConnectionData FromByteArray(byte[] data)
+ {
+ fixed(byte* ptr = data)
+ {
+ return RelayConnectionData.FromBytePointer(ptr, data.Length);
+ }
+ }
}
}
diff --git a/Runtime/Relay/RelayConnectionStatus.cs b/Runtime/Relay/RelayConnectionStatus.cs
index efc9ffa..a58f7d2 100644
--- a/Runtime/Relay/RelayConnectionStatus.cs
+++ b/Runtime/Relay/RelayConnectionStatus.cs
@@ -1,7 +1,3 @@
-using System;
-using Unity.Networking.Transport;
-using UnityEngine;
-
namespace Unity.Networking.Transport.Relay
{
/// State of the connection to the relay server.
@@ -37,25 +33,4 @@ public enum RelayConnectionStatus
///
AllocationInvalid
}
-
- public static class NetworkDriverRelayExtensions
- {
- /// Get the current status of the connection to the relay server.
- public static RelayConnectionStatus GetRelayConnectionStatus(this NetworkDriver driver)
- {
- if (driver.m_NetworkStack.TryGetLayer(out var layer))
- {
- return layer.ConnectionStatus;
- }
- else
- {
-#if ENABLE_UNITY_COLLECTIONS_CHECKS
- throw new InvalidOperationException("Can't call GetRelayConnectionStatus when not using the Relay.");
-#else
- Debug.LogError("Can't call GetRelayConnectionStatus when not using the Relay.");
- return RelayConnectionStatus.NotEstablished;
-#endif
- }
- }
- }
}
diff --git a/Runtime/Relay/RelayHMACKey.cs b/Runtime/Relay/RelayHMACKey.cs
index 66f421f..74ab557 100644
--- a/Runtime/Relay/RelayHMACKey.cs
+++ b/Runtime/Relay/RelayHMACKey.cs
@@ -9,7 +9,10 @@ public unsafe struct RelayHMACKey
public fixed byte Value[k_Length];
- // Used by Relay SDK
+ /// Convert a raw buffer to a
+ /// Raw pointer to buffer to convert.
+ /// Length of the buffer to convert.
+ /// New .
public static RelayHMACKey FromBytePointer(byte* data, int length)
{
if (length != k_Length)
@@ -26,5 +29,16 @@ public static RelayHMACKey FromBytePointer(byte* data, int length)
UnsafeUtility.MemCpy(hmacKey.Value, data, length);
return hmacKey;
}
+
+ /// Convert a byte array to a
+ /// Array to convert.
+ /// New .
+ public static RelayHMACKey FromByteArray(byte[] data)
+ {
+ fixed(byte* ptr = data)
+ {
+ return RelayHMACKey.FromBytePointer(ptr, data.Length);
+ }
+ }
}
}
diff --git a/Runtime/Relay/RelayNetworkParameter.cs b/Runtime/Relay/RelayNetworkParameter.cs
index b55f712..5d72c6d 100644
--- a/Runtime/Relay/RelayNetworkParameter.cs
+++ b/Runtime/Relay/RelayNetworkParameter.cs
@@ -49,11 +49,6 @@ public unsafe bool Validate()
valid = false;
UnityEngine.Debug.LogError($"{nameof(ServerData.Endpoint)} value ({ServerData.Endpoint}) must be a valid value");
}
- if (ServerData.Nonce == default)
- {
- valid = false;
- UnityEngine.Debug.LogError($"{nameof(ServerData.Nonce)} value ({ServerData.Nonce}) must be a valid value");
- }
if (ServerData.AllocationId == default)
{
valid = false;
diff --git a/Runtime/Relay/RelayServerData.cs b/Runtime/Relay/RelayServerData.cs
index 67f83f9..b826bc4 100644
--- a/Runtime/Relay/RelayServerData.cs
+++ b/Runtime/Relay/RelayServerData.cs
@@ -1,7 +1,12 @@
using System;
+using System.Linq;
+using System.Net;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
-using Unity.Networking.Transport.Utilities;
+
+#if RELAY_SDK_INSTALLED
+using Unity.Services.Relay.Models;
+#endif
namespace Unity.Networking.Transport.Relay
{
@@ -13,25 +18,24 @@ public unsafe struct RelayServerData
public RelayConnectionData HostConnectionData;
public RelayAllocationId AllocationId;
public RelayHMACKey HMACKey;
- public fixed byte HMAC[32]; // TODO: this shouldn't be here and should be computed on connection binding but today it's not Burst compatible.
+
public readonly byte IsSecure;
- public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, RelayAllocationId allocationId, string connectionData, string hostConnectionData, string key, bool isSecure)
- {
- Endpoint = endpoint;
- AllocationId = allocationId;
- Nonce = nonce;
+ // TODO Should be computed on connection binding (but not Burst compatible today).
+ internal fixed byte HMAC[32];
- IsSecure = isSecure ? (byte)1 : (byte)0;
+ // Common code of all byte array-based constructors.
+ private RelayServerData(byte[] allocationId, byte[] connectionData, byte[] hostConnectionData, byte[] key)
+ {
+ Nonce = 0;
+ AllocationId = RelayAllocationId.FromByteArray(allocationId);
+ ConnectionData = RelayConnectionData.FromByteArray(connectionData);
+ HostConnectionData = RelayConnectionData.FromByteArray(hostConnectionData);
+ HMACKey = RelayHMACKey.FromByteArray(key);
- fixed(byte* connPtr = ConnectionData.Value)
- fixed(byte* hostPtr = HostConnectionData.Value)
- fixed(byte* keyPtr = HMACKey.Value)
- {
- Base64.FromBase64String(connectionData, connPtr, RelayConnectionData.k_Length);
- Base64.FromBase64String(hostConnectionData, hostPtr, RelayConnectionData.k_Length);
- Base64.FromBase64String(key, keyPtr, RelayHMACKey.k_Length);
- }
+ // Assign temporary values to those. Chained constructors will set them.
+ Endpoint = default;
+ IsSecure = 0;
fixed(byte* hmacPtr = HMAC)
{
@@ -39,6 +43,78 @@ public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, RelayAllocati
}
}
+#if RELAY_SDK_INSTALLED
+ /// Create a new Relay server data structure from an allocation.
+ /// Allocation from which to create the server data.
+ /// Type of connection to use ("udp", "dtls", "ws", or "wss").
+ public RelayServerData(Allocation allocation, string connectionType)
+ : this(allocation.AllocationIdBytes, allocation.ConnectionData, allocation.ConnectionData, allocation.Key)
+ {
+ // We check against a hardcoded list of strings instead of just trying to find the
+ // connection type in the endpoints since it may contains things we don't support
+ // (e.g. they provide a "tcp" endpoint which we don't support).
+ var supportedConnectionTypes = new string[] { "udp", "dtls", "ws", "wss" };
+ if (!supportedConnectionTypes.Contains(connectionType))
+ throw new ArgumentException($"Invalid connection type: {connectionType}. Must be udp, dtls, ws, or wss.");
+
+ var serverEndpoint = allocation.ServerEndpoints.First(ep => ep.ConnectionType == connectionType);
+
+ Endpoint = HostToEndpoint(serverEndpoint.Host, (ushort)serverEndpoint.Port);
+ IsSecure = serverEndpoint.Secure ? (byte)1 : (byte)0;
+ }
+
+ /// Create a new Relay server data structure from a join allocation.
+ /// Allocation from which to create the server data.
+ /// Type of connection to use ("udp", "dtls", "ws", or "wss").
+ public RelayServerData(JoinAllocation allocation, string connectionType)
+ : this(allocation.AllocationIdBytes, allocation.ConnectionData, allocation.HostConnectionData, allocation.Key)
+ {
+ // We check against a hardcoded list of strings instead of just trying to find the
+ // connection type in the endpoints since it may contains things we don't support
+ // (e.g. they provide a "tcp" endpoint which we don't support).
+ var supportedConnectionTypes = new string[] { "udp", "dtls", "ws", "wss" };
+ if (!supportedConnectionTypes.Contains(connectionType))
+ throw new ArgumentException($"Invalid connection type: {connectionType}. Must be udp, dtls, ws, or wss.");
+
+ var serverEndpoint = allocation.ServerEndpoints.First(ep => ep.ConnectionType == connectionType);
+
+ Endpoint = HostToEndpoint(serverEndpoint.Host, (ushort)serverEndpoint.Port);
+ IsSecure = serverEndpoint.Secure ? (byte)1 : (byte)0;
+ }
+
+#endif
+
+ /// Create a new Relay server data structure.
+ ///
+ /// If a hostname is provided as the "host" parameter, this constructor will perform a DNS
+ /// resolution to map it to an IP address. If the hostname is not in the OS cache, this
+ /// operation can possibly block for a long time (between 20 and 120 milliseconds). If this
+ /// is a concern, perform the DNS resolution asynchronously and pass in the resulting IP
+ /// address directly (see ).
+ ///
+ /// IP address or hostname of the Relay server.
+ /// Port of the Relay server.
+ /// ID of the Relay allocation.
+ /// Connection data of the allocation.
+ /// Connection data of the host (same as previous for hosts).
+ /// HMAC signature of the allocation.
+ /// Whether the Relay connection is to be secured or not.
+ public RelayServerData(string host, ushort port, byte[] allocationId, byte[] connectionData,
+ byte[] hostConnectionData, byte[] key, bool isSecure)
+ : this(allocationId, connectionData, hostConnectionData, key)
+ {
+ Endpoint = HostToEndpoint(host, port);
+ IsSecure = isSecure ? (byte)1 : (byte)0;
+ }
+
+ /// Create a new Relay server data structure (low level constructor).
+ /// Endpoint of the Relay server.
+ /// Nonce used in connection handshake (preferably random).
+ /// ID of the Relay allocation.
+ /// Connection data of the allocation.
+ /// Connection data of the host (use default for hosts).
+ /// HMAC signature of the allocation.
+ /// Whether the Relay connection is to be secured or not.
public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, ref RelayAllocationId allocationId,
ref RelayConnectionData connectionData, ref RelayConnectionData hostConnectionData, ref RelayHMACKey key, bool isSecure)
{
@@ -57,16 +133,6 @@ public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, ref RelayAllo
}
}
- public void ComputeNewNonce()
- {
- Nonce = (ushort)(new Unity.Mathematics.Random((uint)TimerHelpers.GetTicks())).NextUInt(1, 0xefff);
-
- fixed(byte* hmacPtr = HMAC)
- {
- ComputeBindHMAC(hmacPtr, Nonce, ref ConnectionData, ref HMACKey);
- }
- }
-
private static void ComputeBindHMAC(byte* result, ushort nonce, ref RelayConnectionData connectionData, ref RelayHMACKey key)
{
var keyArray = new byte[64];
@@ -97,5 +163,31 @@ private static void ComputeBindHMAC(byte* result, ushort nonce, ref RelayConnect
HMACSHA256.ComputeHash(keyValue, keyArray.Length, messageBytes, messageLength, result);
}
}
+
+ private static NetworkEndpoint HostToEndpoint(string host, ushort port)
+ {
+ NetworkEndpoint endpoint;
+
+ if (NetworkEndpoint.TryParse(host, port, out endpoint, NetworkFamily.Ipv4))
+ return endpoint;
+
+ if (NetworkEndpoint.TryParse(host, port, out endpoint, NetworkFamily.Ipv6))
+ return endpoint;
+
+ // If IPv4 and IPv6 parsing didn't work, we're dealing with a hostname. In this case,
+ // perform a DNS resolution to figure out what its underlying IP address is.
+ var addresses = Dns.GetHostEntry(host).AddressList;
+ if (addresses.Length > 0)
+ {
+ var address = addresses[0].ToString();
+ var family = addresses[0].AddressFamily;
+ return NetworkEndpoint.Parse(address, port, (NetworkFamily)family);
+ }
+ else
+ {
+ UnityEngine.Debug.LogError($"Couldn't map hostname {host} to an IP address.");
+ return default;
+ }
+ }
}
}
diff --git a/Runtime/StreamSegmentationParameters.cs b/Runtime/StreamSegmentationParameters.cs
new file mode 100644
index 0000000..4f0d82f
--- /dev/null
+++ b/Runtime/StreamSegmentationParameters.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Unity.Networking.Transport
+{
+ ///
+ /// Settings used as a tag to indicate the network stack should include a StreamSegmentationLayer.
+ ///
+ internal struct StreamSegmentationParameter : INetworkParameter
+ {
+ public bool Validate() => true;
+ }
+
+ internal static class StreamSegmentationParameterExtensions
+ {
+ public static ref NetworkSettings WithStreamSegmentationParameters(
+ ref this NetworkSettings settings)
+ {
+ var parameter = new StreamSegmentationParameter();
+ settings.AddRawParameterStruct(ref parameter);
+ return ref settings;
+ }
+ }
+}
diff --git a/Runtime/StreamSegmentationParameters.cs.meta b/Runtime/StreamSegmentationParameters.cs.meta
new file mode 100644
index 0000000..79b669f
--- /dev/null
+++ b/Runtime/StreamSegmentationParameters.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8c93b0d5e2617504db26322f9a959f53
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/UDPNetworkInterface.cs b/Runtime/UDPNetworkInterface.cs
index ae259a9..c2e6d6d 100644
--- a/Runtime/UDPNetworkInterface.cs
+++ b/Runtime/UDPNetworkInterface.cs
@@ -563,10 +563,11 @@ public unsafe int Bind(NetworkEndpoint endpoint)
{
var state = m_InternalState.Value;
+ CloseSocket(state.Socket);
+
var result = CreateSocket(state.SendQueueCapacity, state.ReceiveQueueCapacity, endpoint, out var newSocket);
if (result == 0)
{
- CloseSocket(state.Socket);
state.Socket = newSocket;
state.SocketStatus = SocketStatus.SocketNormal;
@@ -597,7 +598,7 @@ private static unsafe int CreateSocket(int sendQueueCapacity, int receiveQueueCa
var error = default(ErrorState);
socket = Binding.Baselib_RegisteredNetwork_Socket_UDP_Create(
&endpoint.rawNetworkAddress,
- Binding.Baselib_NetworkAddress_AddressReuse.Allow,
+ Binding.Baselib_NetworkAddress_AddressReuse.DoNotAllow,
checked((uint)sendQueueCapacity),
checked((uint)receiveQueueCapacity),
&error);
@@ -639,14 +640,15 @@ private void RecreateSocket(long updateTime, ref PacketsQueue receiveQueue)
else
{
UnityEngine.Debug.LogWarning("Socket error encountered; attempting recovery by creating a new one.");
+ state.LastSocketRecreateTime = updateTime;
+ state.NumSocketRecreate++;
+
+ CloseSocket(state.Socket);
var result = CreateSocket(state.SendQueueCapacity, state.ReceiveQueueCapacity, state.LocalEndpoint, out var newSocket);
if (result == 0)
{
- CloseSocket(state.Socket);
state.Socket = newSocket;
state.SocketStatus = SocketStatus.SocketNormal;
- state.LastSocketRecreateTime = updateTime;
- state.NumSocketRecreate++;
}
}
diff --git a/Runtime/Unity.Networking.Transport.asmdef b/Runtime/Unity.Networking.Transport.asmdef
index a29627e..5b74824 100644
--- a/Runtime/Unity.Networking.Transport.asmdef
+++ b/Runtime/Unity.Networking.Transport.asmdef
@@ -4,7 +4,8 @@
"references": [
"Unity.Burst",
"Unity.Collections",
- "Unity.Mathematics"
+ "Unity.Mathematics",
+ "Unity.Services.Relay"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -13,5 +14,12 @@
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
- "noEngineReferences": false
+ "noEngineReferences": false,
+ "versionDefines": [
+ {
+ "name": "com.unity.services.relay",
+ "expression": "1.0",
+ "define": "RELAY_SDK_INSTALLED"
+ }
+ ]
}
diff --git a/Runtime/Utilities.cs b/Runtime/Utilities.cs
index 5fa4cfa..f5b3e85 100644
--- a/Runtime/Utilities.cs
+++ b/Runtime/Utilities.cs
@@ -1,8 +1,12 @@
using System;
using System.Runtime.CompilerServices;
+using System.Threading;
using Unity.Baselib.LowLevel;
+using Unity.Burst;
using Unity.Collections;
+using Random = Unity.Mathematics.Random;
+
namespace Unity.Networking.Transport.Utilities
{
///
@@ -254,44 +258,48 @@ public static void ResizeUninitializedTillPowerOf2(this NativeList list, i
public static class RandomHelpers
{
+ private static readonly SharedStatic s_SharedSeed = SharedStatic.GetOrCreate(16);
+ private class SharedRandomKey {}
+
+ static RandomHelpers()
+ {
+ s_SharedSeed.Data = 0;
+ }
+
+ internal static Unity.Mathematics.Random GetRandomGenerator()
+ {
+ // if the seed has not been initialized we set it to the current ticks.
+ if (s_SharedSeed.Data == 0)
+ Interlocked.CompareExchange(ref s_SharedSeed.Data, (long)TimerHelpers.GetTicks(), 0);
+
+ // otherwise we just increment it, ensuring we get a different value for every call.
+ var seed = (Interlocked.Increment(ref s_SharedSeed.Data) % uint.MaxValue) + 1;
+
+ return new Mathematics.Random((uint)seed);
+ }
+
// returns ushort in [1..ushort.MaxValue] range
public static ushort GetRandomUShort()
{
-#if UNITY_WEBGL && !UNITY_EDITOR
- return (ushort)UnityEngine.Random.Range(0, ushort.MaxValue);
-#else
- var rnd = new Unity.Mathematics.Random((uint)TimerHelpers.GetTicks());
- return (ushort)rnd.NextUInt(1, ushort.MaxValue - 1);
-#endif
+ return (ushort)GetRandomGenerator().NextUInt(1, ushort.MaxValue - 1);
}
// returns ulong in [1..ulong.MaxValue] range
public static ulong GetRandomULong()
{
-#if UNITY_WEBGL && !UNITY_EDITOR
- var high = (ulong)UnityEngine.Random.Range(int.MinValue, int.MaxValue);
- var low = (ulong)UnityEngine.Random.Range(int.MinValue, int.MaxValue);
-#else
- var rnd = new Unity.Mathematics.Random((uint)TimerHelpers.GetTicks());
- var high = rnd.NextUInt(0, uint.MaxValue - 1);
- var low = rnd.NextUInt(1, uint.MaxValue - 1);
-#endif
+ var random = GetRandomGenerator();
+ var high = random.NextUInt(0, uint.MaxValue - 1);
+ var low = random.NextUInt(1, uint.MaxValue - 1);
return ((ulong)high << 32) | (ulong)low;
}
internal unsafe static ConnectionToken GetRandomConnectionToken()
{
var token = new ConnectionToken();
-
-#if UNITY_WEBGL && !UNITY_EDITOR
- for (int i = 0; i < ConnectionToken.k_Length; i++)
- token.Value[i] = (byte)UnityEngine.Random.Range(0, 0xFF);
-#else
- var rnd = new Unity.Mathematics.Random((uint)TimerHelpers.GetTicks());
+ var random = GetRandomGenerator();
for (int i = 0; i < ConnectionToken.k_Length; i++)
- token.Value[i] = (byte)(rnd.NextUInt() & 0xFF);
-#endif
+ token.Value[i] = (byte)(random.NextUInt() & 0xFF);
return token;
}
diff --git a/Runtime/WebSocketNetworkInterface.cs b/Runtime/WebSocketNetworkInterface.cs
index deefb06..fb69f0d 100644
--- a/Runtime/WebSocketNetworkInterface.cs
+++ b/Runtime/WebSocketNetworkInterface.cs
@@ -139,7 +139,7 @@ public unsafe int Bind(NetworkEndpoint endpoint)
}
public unsafe int Listen()
- => throw new InvalidOperationException("WebGL does not support listening for connections");
+ => throw new InvalidOperationException("Web browsers do not support listening for new WebSocket connections.");
public unsafe void Dispose()
{
diff --git a/Samples~/RelayPing/Scripts/Code.asmdef b/Samples~/RelayPing/Scripts/Code.asmdef
index bcea0cd..dc9b13f 100644
--- a/Samples~/RelayPing/Scripts/Code.asmdef
+++ b/Samples~/RelayPing/Scripts/Code.asmdef
@@ -21,8 +21,8 @@
],
"versionDefines": [
{
- "name": "Unity.Services.Relay",
- "expression": "0.0",
+ "name": "com.unity.services.relay",
+ "expression": "0",
"define": "ENABLE_RELAY"
}
],
diff --git a/Samples~/RelayPing/Scripts/PingClientBehaviour.cs b/Samples~/RelayPing/Scripts/PingClientBehaviour.cs
index 1c87113..dfa7808 100644
--- a/Samples~/RelayPing/Scripts/PingClientBehaviour.cs
+++ b/Samples~/RelayPing/Scripts/PingClientBehaviour.cs
@@ -7,7 +7,6 @@
using System.Collections.Generic;
using Unity.Networking.Transport.Relay;
using Unity.Services.Relay;
-using Unity.Services.Relay.Allocations;
using Unity.Services.Relay.Models;
using Unity.Services.Core;
using Unity.Services.Authentication;
@@ -65,37 +64,12 @@ public IEnumerator ConnectAndBind(string joinCode)
var allocation = joinTask.Result;
- var allocationId = RelayUtilities.ConvertFromAllocationIdBytes(allocation.AllocationIdBytes);
-
- var connectionData = RelayUtilities.ConvertConnectionData(allocation.ConnectionData);
- var hostConnectionData = RelayUtilities.ConvertConnectionData(allocation.HostConnectionData);
- var key = RelayUtilities.ConvertFromHMAC(allocation.Key);
-
Debug.Log($"client: {allocation.ConnectionData[0]} {allocation.ConnectionData[1]}");
Debug.Log($"host: {allocation.HostConnectionData[0]} {allocation.HostConnectionData[1]}");
Debug.Log($"client: {allocation.AllocationId}");
- RelayServerEndpoint defaultEndpoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp,
- true, false, allocation.RelayServer.IpV4, allocation.RelayServer.Port);
-
- foreach (var endPoint
- in allocation.ServerEndpoints)
- {
-#if ENABLE_MANAGED_UNITYTLS
- if (endPoint.Secure == true && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
- defaultEndpoint = endPoint;
-#else
- if (endPoint.Secure == false && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
- defaultEndpoint = endPoint;
-#endif
- }
-
- var serverEndpoint = NetworkEndpoint.Parse(defaultEndpoint.Host, (ushort)defaultEndpoint.Port);
-
- var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, defaultEndpoint.Secure);
- relayServerData.ComputeNewNonce();
-
+ var relayServerData = new RelayServerData(allocation, "udp");
InitDriver(ref relayServerData);
if (m_ClientDriver.Bind(NetworkEndpoint.AnyIpv4) != 0)
@@ -109,7 +83,7 @@ public IEnumerator ConnectAndBind(string joinCode)
yield return null;
}
- m_clientToServerConnection[0] = m_ClientDriver.Connect(serverEndpoint);
+ m_clientToServerConnection[0] = m_ClientDriver.Connect();
while (m_ClientDriver.GetConnectionState(m_clientToServerConnection[0]) == NetworkConnection.State.Connecting)
{
@@ -139,7 +113,7 @@ void OnDestroy()
}
[BurstCompile]
- struct PingJob : IJob
+ struct RelayPingJob : IJob
{
public NetworkDriver driver;
public NativeArray connection;
@@ -192,7 +166,7 @@ private void Update()
{
m_ClientDriver.ScheduleUpdate().Complete();
- var pingJob = new PingJob
+ var pingJob = new RelayPingJob
{
driver = m_ClientDriver,
connection = m_clientToServerConnection,
@@ -224,7 +198,7 @@ void FixedUpdate()
// Update the ping client UI with the ping statistics computed by teh job scheduled previous frame since that
// is now guaranteed to have completed
PingClientUIBehaviour.UpdateStats(m_pingStats[0], m_pingStats[1]);
- var pingJob = new PingJob
+ var pingJob = new RelayPingJob
{
driver = m_ClientDriver,
connection = m_clientToServerConnection,
diff --git a/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs b/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs
index 99ba429..f1e6693 100644
--- a/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs
+++ b/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs
@@ -1,12 +1,9 @@
-#if ENABLE_RELAY
-
using System;
using System.Collections;
using UnityEngine;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using Unity.Services.Relay;
-using Unity.Services.Relay.Allocations;
using Unity.Services.Relay.Models;
using Unity.Services.Core;
using Unity.Services.Authentication;
@@ -112,5 +109,3 @@ void UpdatePingClientUI()
}
}
}
-
-#endif
diff --git a/Samples~/RelayPing/Scripts/PingServerBehaviour.cs b/Samples~/RelayPing/Scripts/PingServerBehaviour.cs
index 4accf12..2278f14 100644
--- a/Samples~/RelayPing/Scripts/PingServerBehaviour.cs
+++ b/Samples~/RelayPing/Scripts/PingServerBehaviour.cs
@@ -9,7 +9,6 @@
using System.Runtime.InteropServices;
using System.Text;
using Unity.Services.Relay;
-using Unity.Services.Relay.Allocations;
using Unity.Services.Relay.Models;
using Unity.Services.Core;
using Unity.Services.Authentication;
@@ -77,32 +76,7 @@ public IEnumerator ConnectAndBind()
PingClientUIBehaviour.m_JoinCode = joinCodeTask.Result;
- RelayServerEndpoint defaultEndpoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp,
- true, false, allocation.RelayServer.IpV4, allocation.RelayServer.Port);
-
- foreach (var endPoint
- in allocation.ServerEndpoints)
- {
-#if ENABLE_MANAGED_UNITYTLS
- if (endPoint.Secure == true && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
- defaultEndpoint = endPoint;
-#else
- if (endPoint.Secure == false && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp)
- defaultEndpoint = endPoint;
-#endif
- }
-
- var serverEndpoint = NetworkEndpoint.Parse(defaultEndpoint.Host, (ushort)defaultEndpoint.Port);
-
- var allocationId = RelayUtilities.ConvertFromAllocationIdBytes(allocation.AllocationIdBytes);
- var connectionData = RelayUtilities.ConvertConnectionData(allocation.ConnectionData);
- var key = RelayUtilities.ConvertFromHMAC(allocation.Key);
-
- var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData,
- ref connectionData, ref key, defaultEndpoint.Secure);
-
- relayServerData.ComputeNewNonce();
-
+ var relayServerData = new RelayServerData(allocation, "udp");
InitDriver(ref relayServerData);
if (m_ServerDriver.Bind(NetworkEndpoint.AnyIpv4) != 0)
@@ -137,7 +111,7 @@ void OnDestroy()
}
[BurstCompile]
- struct DriverUpdateJob : IJob
+ struct RelayDriverUpdateJob : IJob
{
public NetworkDriver driver;
public NativeList connections;
@@ -199,7 +173,7 @@ static NetworkConnection ProcessSingleConnection(NetworkDriver.Concurrent driver
#if ENABLE_IL2CPP
[BurstCompile]
- struct PongJob : IJob
+ struct RelayPongJob : IJob
{
public NetworkDriver.Concurrent driver;
public NativeList connections;
@@ -212,7 +186,7 @@ public void Execute()
}
#else
[BurstCompile]
- struct PongJob : IJobParallelForDefer
+ struct RelayPongJob : IJobParallelForDefer
{
public NetworkDriver.Concurrent driver;
public NativeArray connections;
@@ -231,7 +205,7 @@ private void Update()
{
m_ServerDriver.ScheduleUpdate().Complete();
- var updateJob = new DriverUpdateJob {driver = m_ServerDriver, connections = m_connections};
+ var updateJob = new RelayDriverUpdateJob {driver = m_ServerDriver, connections = m_connections};
updateJob.Schedule().Complete();
}
}
@@ -251,8 +225,8 @@ void FixedUpdate()
// Wait for the previous frames ping to complete before starting a new one, the Complete in LateUpdate is not
// enough since we can get multiple FixedUpdate per frame on slow clients
m_updateHandle.Complete();
- var updateJob = new DriverUpdateJob {driver = m_ServerDriver, connections = m_connections};
- var pongJob = new PongJob
+ var updateJob = new RelayDriverUpdateJob {driver = m_ServerDriver, connections = m_connections};
+ var pongJob = new RelayPongJob
{
// PongJob is a ParallelFor job, it must use the concurrent NetworkDriver
driver = m_ServerDriver.ToConcurrent(),
diff --git a/Samples~/RelayPing/Scripts/RelayUtilities.cs b/Samples~/RelayPing/Scripts/RelayUtilities.cs
deleted file mode 100644
index 396c15e..0000000
--- a/Samples~/RelayPing/Scripts/RelayUtilities.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using Unity.Networking.Transport.Relay;
-
-namespace Unity.Networking.Transport.Samples
-{
- public static class RelayUtilities
- {
- public static RelayAllocationId ConvertFromAllocationIdBytes(byte[] allocationIdBytes)
- {
- unsafe
- {
- fixed(byte* ptr = allocationIdBytes)
- {
- return RelayAllocationId.FromBytePointer(ptr, allocationIdBytes.Length);
- }
- }
- }
-
- public static RelayHMACKey ConvertFromHMAC(byte[] hmac)
- {
- unsafe
- {
- fixed(byte* ptr = hmac)
- {
- return RelayHMACKey.FromBytePointer(ptr, RelayHMACKey.k_Length);
- }
- }
- }
-
- public static RelayConnectionData ConvertConnectionData(byte[] connectionData)
- {
- unsafe
- {
- fixed(byte* ptr = connectionData)
- {
- return RelayConnectionData.FromBytePointer(ptr, RelayConnectionData.k_Length);
- }
- }
- }
- }
-}
diff --git a/package.json b/package.json
index 50539a3..ef8350d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.unity.transport",
"displayName": "Unity Transport",
- "version": "2.0.0-exp.6",
+ "version": "2.0.0-exp.7",
"unity": "2022.2",
"unityRelease": "0a18",
"description": "Unity network transport layer - the low-level interface for connecting and sending data through a network",
@@ -14,12 +14,12 @@
"com.unity.transport.tests": "0.0.0"
},
"upmCi": {
- "footprint": "ae0c94fd1e9687f90a185f24a4518a96261572a0"
+ "footprint": "462808f15d236493cb0da5308b1dacc0da3c2a8c"
},
"repository": {
"url": "https://github.cds.internal.unity3d.com/unity/com.unity.transport.git",
"type": "git",
- "revision": "23d0879f8d0b103358842e05e1a40c951b712dfe"
+ "revision": "cdc3da7767c6d4981931968b246055980585ef30"
},
"samples": [
{