diff --git a/.buginfo b/.buginfo deleted file mode 100644 index 679adcc..0000000 --- a/.buginfo +++ /dev/null @@ -1,4 +0,0 @@ -system: jira -server: jira.unity3d.com -project: UTRANS -issuetype: Bug diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index f79b94f..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Build Roslyn Analyzers", - "command": "dotnet", - "type": "shell", - "options": { - "cwd": "${workspaceFolder}/Tools~/utp-analyzers/Unity.Transport.Analyzers/" - }, - "args": [ - "build", - // Ask dotnet build to generate full paths for file names. - "/property:GenerateFullPaths=true", - // Do not generate summary otherwise it leads to duplicate errors in Problems panel - "/consoleloggerparameters:NoSummary", - ], - "group": "build", - "presentation": { - "reveal": "silent" - }, - "problemMatcher": "$msCompile" - } - ] -} diff --git a/Analyzers/Unity.Transport.Analyzers.dll b/Analyzers/Unity.Transport.Analyzers.dll index c7d4245..4fc1f02 100644 Binary files a/Analyzers/Unity.Transport.Analyzers.dll and b/Analyzers/Unity.Transport.Analyzers.dll differ diff --git a/Analyzers/Unity.Transport.Analyzers.dll.meta b/Analyzers/Unity.Transport.Analyzers.dll.meta index 2c415a0..7cc4716 100644 --- a/Analyzers/Unity.Transport.Analyzers.dll.meta +++ b/Analyzers/Unity.Transport.Analyzers.dll.meta @@ -1,6 +1,7 @@ fileFormatVersion: 2 guid: 4ba9f285a3ef7cf47b7dfd5e299377f4 -labels: [] +labels: +- RoslynAnalyzer PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index ad17e42..648033c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,126 +1,141 @@ # Change log -## [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). +## [2.0.0-exp.6] - 2022-09-02 ### 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). +* Fixed changelog. -## [1.1.0] - 2022-06-14 +## [2.0.0-exp.5] - 2022-09-01 ### 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. +* Preliminary WebSocket support. To have a `NetworkDriver` use WebSockets, create it with the appropriate network interface (e.g. `NetworkDriver.Create(new WebSocketNetworkInterface())`). To enable TLS support, create the driver with `NetworkSettings` configured with `WithSecureClientParameters`/`WithSecureServerParameters` (on the client, only the hostname needs to be provided). +* `NetworkSettings.WithSecureClientParameters` and `NetworkSettings.WithSecureServerParameters` now have versions where the certificates and hostnames are provided as normal strings, instead of fixed strings. ### 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. +* `Protocol` field was removed from the `SecureNetworkProtocolParameter` structure. The protocol is now determined automatically from the network interface being used. +* Updated to Collections 2.1.0-exp.1 +* `FragmentationPipelineStage.FragContext` was made internal as it is an internal implementation detail that serves no purpose being exposed publicly. +* Multiple APIs were removed or made internal in `ReliableUtility` (more than is practical to list here). These were all internal implementation details that served no purpose being exposed publicly. The only remaining public APIs in `ReliableUtility` are those used to gather statistics from a reliable pipeline (as demonstrated in the Soaker sample). +* All APIs except `Parameters` and `Context` in `SimulatorUtility` were made internal as they are implementation details that serve no purpose being exposed publicly. +* It is no longer possible to configure the read timeout in the secure parameters as values other than the default (0) were never properly supported. +* It is no longer possible to configure the handshake minimum/maximum timeouts in the secure parameters. These values are now derived from the `connectTimeoutMS` and `maxConnectAttempts` values configured with `NetworkSettings.WithNetworkConfigParameters`. +* Hostnames in secure parameters are now `FixedString512Bytes` instead of `FixedString32Bytes`, allowing any possibly hostname to be used instead of only short ones. +* `NetworkSendQueueHandle` was removed. It was not used for anything anymore (previously it was used for custom implementations of `INetworkInterface`). +* `NetworkInterfaceSendHandle` and `SendHandleFlags` were made internal. With the removal of `NetworkSendInterface`, these served no purpose anymore. +* `INetworkInterface.Initialize` now receives a `ref packetPadding` parameter that can be increased to reserve space for headers. +* `BaselibNetworkInterface` was renamed to `UDPNetworkInterface`. + +### Fixes +* Fixed an issue where when sending data on a connection and closing that connection in the same update, the data message would not be sent properly. +* Fixed a stack overflow exception when send/receive queue capacity was set very high (>10,000). +* Fixed an issue where `SimulatorPipelineStage` would always use the same seed for its random number generator. -## [1.0.0] - 2022-03-28 - -### Changes -* Changed version to 1.0.0. +### Upgrade guide -## [1.0.0-pre.16] - 2022-03-24 +## [2.0.0-exp.4] - 2022-08-03 -### 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. +### New features +* A new global network simulator has been added, configurable through `NetworkSettings.WithNetworkSimulatorParameters` (settings can be modified at runtime with `NetworkDriver.ModifyNetworkSimulatorParameters`). Unlike `SimulatorPipelineStage`, it applies its parameters to _all_ traffic (including control messages). Note that it is currently _much_ less featureful than `SimulatorPipelineStage` (only supports dropping packets for now), so we still recommend using the latter for all network simulation. +* Added a new `NetworkDriver.ModifySimulatorStageParameters` API to modify the parameters of the `SimulatorPipelineStage` at runtime. +* `NetworkDriver` now exposes the `NetworkSettings` currently in use through the `CurrentSettings` property. These settings are read-only. +* To implement the above functionality, `NetworkSettings` now provides a `AsReadOnly` method that returns a read-only copy of the settings. -### 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. +## [2.0.0-exp.3] - 2022-07-11 -## [1.0.0-pre.15] - 2022-03-11 +### New features ### 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`. +* Updated to Burst 1.7.3. +* Changed: A call to `NetworkDriver.Disconnect` now requires a subsequent call to `NetworkDriver.Update` for the disconnect packet to be effectively sent (Previously `NetworkDriver.FlushSend` was enough). +* Changed: The protocol used to establish connections now supports protocol versioning. This should help maintain compatibility for future releases, but unfortunately it's now incompatible with the protocol used in version 1.X. ### 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. +### Upgrade guide +* For `NetworkDriver.FlushSend` calls that follows a call to `NetworkDriver.Disconnect`, change it to `NetworkDriver.Update`. +* The communication protocol used to establish connections has had a breaking change and is now incompatible with Unity Transport 1.X. Clients and servers will need to be updated at the same time to maintain compatibility. -## [1.0.0-pre.13] - 2022-02-14 +## [2.0.0-exp.2] - 2022-06-07 ### 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 +* Added a new version of `NetworkDriver.CreatePipeline` that takes a `NativeArray` of `NetworkPipelineStageId` as an argument. The old version taking an array of `Type` objects is still fully supported. ### 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). +* Removed: `NetworkSettings.WithDataStreamParameters` has been deleted. The data stream size (the only parameter this API controlled) is now always dynamically-sized to avoid out-of-memory errors. +* Removed: `NetworkSettings.WithPipelineParameters` has been deleted. Initial sizing of the pipeline buffers is now handled internally. +* Removed: `NetworkPipelineStageCollection` has been deleted. See upgrade guide below for details of how to replace its usages. +* Updated to Collections 2.0.0-pre.32. +* Updated to Burst 1.7.2. +* Removed: `NetworkDriver.LastUpdateTime` has been deleted. This value was an internal detail not meant to be consumed by users, and its time reference couldn't be reliably related to typical C# timestamps. ### 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 +* 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). +* `BeginSend` would not return an error if called on a closed connection before the next `ScheduleUpdate` call. +* Fix broken link in package documentation. +* On iOS, recreate the socket used for communications when coming back from app suspension. This solves an issue where communications would fail after the app was in the background for a few seconds and iOS decided to reclaim its resources. -### Changes -* Disabled Roslyn Analyzers provisionally +### Upgrade guide +* Registering custom pipeline stages is now done on a per-`NetworkDriver` basis rather than globally through `NetworkPipelineStageCollection`. Concretely, that means replacing calls to `NetworkPipelineStageCollection.RegisterPipelineStage` with calls to `NetworkDriver.RegisterPipelineStage` for each instance of `NetworkDriver` that will make use of the custom pipeline stage. +* `NetworkPipelineStageId` is now obtained through the static `NetworkPipelineStageId.Get` method, rather than with `NetworkPipelineStageCollection.GetStageId`. Updating only requires replacing calls like `NetworkPipelineStageCollection.GetStageId(typeof(Foo))` with `NetworkPipelineStageId.Get()`. +* `NetworkDriver.LocalEndPoint` and `NetworkDriver.RemoteEndPoint` were renamed to `NetworkDriver.GetLocalEndpoint` and `NetworkDriver.RemoteEndpoint`, respectively. This should be updated automatically. +* `INetworkInterface.LocalEndPoint` has been renamed to `INetworkInterface.LocalEndpoint` for consistency with other usages of the term in the API. Since this is an interface property, it must be manually updated (see upgrade guide below). +* Custom implementations of `INetworkInterface` must now implement the `LocalEndpoint` property instead of `LocalEndPoint`. This is purely a change in naming, the behavior should remain the same as before. -### Fixes -* Fixed: Compiler error due to Roslyn Analyzers causing a wrong compiler argument +## [2.0.0-exp.1] - 2022-04-29 -## [1.0.0-pre.8] - 2021-11-18 +### New features +* Added automatic device reconnection (enabled by default). This feature will attempt to re-establish the connection after some inactivity. This feature is intended to handle IP address changes on mobile devices. The inactivity timeout can be controlled by the new parameter `reconnectionTimeoutMS` in `NetworkConfigParameter`. Setting it to 0 disabled the feature. +* When using the Relay protocol, error messages sent by the Relay server are now properly captured and logged. +* `PacketsQueue` and `PacketProcessor` APIs were added for sending and operating over packets in the `INetworkInterface`. +* A `GetRelayConnectionStatus` method has been added to `NetworkDriver` to query the status of the connection to the Relay server. ### Changes +* Updated to Collections 2.0.0-pre.15 +* Updated to Burst 1.7.1. +* Updated to Mathematics 1.2.6. +* Minimal Unity Editor version supported is now 2022.2.0a11. +* Added `NetworkSettings` struct and API for defining network parameters. +* Added `reconnectionTimeoutMS` in `NetworkConfigParameter` to support device reconnection (see above). * 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 +* If collections checks are enabled, trying to create an IPv6 `NetworkEndPoint` will now throw an exception on consoles that don't support IPv6 (PS4, PS5, Switch). +* Documentation has been moved to the [offical multiplayer documentation site](https://docs-multiplayer.unity3d.com/transport/1.0.0/introduction). +* The `INetworkInterface.ScheduleSend()` method signature was modified to receive a `SendJobArguments` struct instead of a `NativeQueue`. A `PacketsQueue` parameter is passed in this new struct. +* `sendQueueCapacity` and `receiveQueueCapacity` parameters moved from `BaselibNetworkParameter` to `NetworkConfigParameter`. +* Removed: `BaselibNetworkParameter.maximumPayloadSize` is not needed anymore as it is handled internally. +* Removed: `INetworkInterface.CreateSendInterface` is not needed anymore, the send queue is managed internally by the `NetworkDriver`. Operations from the `INetworkInterface` must be done through the `ScheduleSend` and `ScheduleReceive` methods. This removes the need of function pointers which where casing GC allocations on `BeginSend`, `EndSend` and `AbortSend` when burst is not enabled. +* Added: `SendJobArguments` and `ReceiveJobArguments` structs to pass arguments to the send and receive jobs of the `INetworkInterface`. +* Obsolete: `NetworkDriver` constructor is now obsolete, instead use `NetworkDriver.Create` methods. This improves burst compatibility as generic methods allows to know the INeworkInterface type at compilation time. +* Obsolete: `NetworkPacketReceiver` is now deprecated. Use the `ReceiveJobArguments.ReceiveQueue` and `PacketProcessor` instead. +* `NetworkDriver.LastUpdateTime` is now consistent across different copies of a driver. It is now also set by the job scheduled with `ScheduleUpdate`, so any job scheduled before it will not see the updated value. This also means the value will not be updated right after `ScheduleUpdate` returns (only once its jobs completes). +* 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`. +* `NetworkDriver` now requires that the `INetworkInterface` provided is an unmanaged type. Managed `INetworkInterfaces` are still supported but are required to be wrapped into an unmanaged type: `myInterface.WrapToUnmanaged()`. +* Instantiating a `NetworkDriver` is now only supported through the `NetworkDriver.Create` methods. +* 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`. +* `NetworkInterfaceEndPoint` usage replaced with `NetworkEndPoint`. +* Removed: `INetworkInterface.CreateInterfaceEndPoint` and `INetworkInterface.GetGenericEndPoint` removed as interfaces use now `NetworkEndPoint`. +* Renamed `NetworkEndPoint` to `NetworkEndpoint`. This should be automatically updated. ### 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) +* 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: Issue where an overflow of the `ReliableSequencedPipelineStage` sequence numbers would not be handled properly. +* Updated Relay sample to the most recent Relay SDK APIs (would fail to compile with latest packages). +* Fixed client certificate not being passed to UnityTLS on secure connections. This prevented client authentication from properly working. +* Fixed: Reliable pipeline drop statistics inaccurate. ### Upgrade guide * `INetworkPipelineStage` and `INetworkInterface` initialization methods now receive a `NetworkSettings` parameter instead of `INetworkParameter[]`. +* `SimulatorPipelineStageInSend` is no longer required and can be safely removed from your pipeline construction. To replace it, `SimulatorPipelineStage` now supports handling both sending and receiving via `ApplyMode.AllPackets`. +* On fragmented and reliable pipelines, sending a large packet when the reliable window was almost full could result in the packet being lost. +* Revert decrease of MTU to 1384 on Xbox platforms (now back at 1400). It would cause issues for cross-platform communications. +* For custom implementation of the `INetworkInterface`: Remove the `CreateSendInterface` and update the `ScheduleSend` and `ScheduleReceive` signature; to iterate over the send/receive queue use the `PacketsQueue[]` operator. +* Move the definition of the `sendQueueCapacity` and `receiveQueueCapacity` parameters from the `WithBaselibNetworkParameters()` to the `WithNetworkConfigParameters()`. +* Update all `new NetworkDriver()` usages to `NetworkDriver.Create()`. +* 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.0.0-pre.7] - 2021-10-21 @@ -171,8 +186,8 @@ * Fixed: Updated collection types in `SecureNetworkProtocol.cs` * Fixed: Fixed race condition between UTP and Relay disconnects * Fixed: Relay not being able to use the fragmentation pipelinestage -### Upgrade guide +### Upgrade guide ## [1.0.0-pre.3] - 2021-09-01 ### New features * Removed references of TransportSamples from readme as they are not currently included in the package @@ -202,6 +217,19 @@ ### Fixes ### Upgrade guide +## [0.9.0] - 2021-05-10 +### New features +* Added support for long serialization and delta compression. +* Upgraded collections to 1.0.0-pre.1 +* Added a new network interface for WebSockets, can be used in both native and web builds. + +### Changes +* Minimum required Unity version has changed to 2020.3.0f1. +* The transport package can be compiled with the tiny c# profile and for WebGL, but WebGL builds only support IPC - not sockets. + +### Fixes +### Upgrade guide + ## [0.8.0] - 2021-03-23 ### New features * Added overloads of `PopEvent` and `PopEventForConnection` which return the pipeline used as an out parameter. diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 71d2b33..0000000 --- a/DESIGN.md +++ /dev/null @@ -1,20 +0,0 @@ -# Unity Transport Design Rules - -## All features are optional -Unity transport is conceptually a thin layer on UDP adding a connection concept. All additional features on top of UDP + connection are optional, when not used they have zero performance or complexity overhead. If possible features are implemented as pipeline stages. - -Features that have a limited audience are implemented outside the package - either in game code or other packages. - -## Full control over processing time and when packets are sent/received -UTP is optimized for making games. It can be used without creating any additional threads - only using the JobSystem. The layer on top has full control over when the transport schedules jobs. The layer on top also has full control over when packets are sent on the wire. There are no internal buffers delaying messages (except possibly in pipelines). - -There is generally no need to continuously poll for messages since incoming data needs to be read right before simulation starts, and we cannot start using new data in the middle of the simulation - -## Written in HPC# -All code is jobified and burst compiled, there is no garbage collection. The transport does not spend any processing time outside setup on the main thread, and it allows the layer on top to not sync on the main thread. - -## Follows the DOTS principles, is usable in DOTS Runtime and always compatible with the latest versions of the DOTS packages -There should always be a version compatible with the latest verions of the DOTS dependencies such as Unity Collections. - -## The protocol is well defined and documented -Other implementations can communicate with games written with Unity Transport, without reverse engineering or reading the transport source code diff --git a/DESIGN.md.meta b/DESIGN.md.meta deleted file mode 100644 index d32b361..0000000 --- a/DESIGN.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 99b333ad37d614e42b1a4776de09e34e -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Documentation~/index.md b/Documentation~/index.md index 94c27e0..e4ce055 100644 --- a/Documentation~/index.md +++ b/Documentation~/index.md @@ -12,7 +12,7 @@ The full documentation for the package can be found on the [Unity multiplayer do This version of `com.unity.transport` is compatible with the following Unity versions and platforms: -* 2020.1.2 and later. +* 2020.3 and later. * All platforms supported by Unity are supported, except WebGL. ## Document revision history diff --git a/Editor.meta b/Editor.meta index 29e73ad..1e2e1b4 100644 --- a/Editor.meta +++ b/Editor.meta @@ -5,4 +5,4 @@ DefaultImporter: externalObjects: {} userData: assetBundleName: - assetBundleVariant: \ No newline at end of file + assetBundleVariant: diff --git a/Editor/Unity.Networking.Editor.asmdef b/Editor/Unity.Networking.Editor.asmdef index 3379f73..497bf48 100644 --- a/Editor/Unity.Networking.Editor.asmdef +++ b/Editor/Unity.Networking.Editor.asmdef @@ -13,4 +13,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 460b30d..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Welcome - -Welcome to the Unity Transport repository! - -The new Unity Transport Package which will replace the UNet low-level API. -The preview of the transport package supports establishing connections and sending messages to a -remote host. It also contains utilities for serializing data streams to send -over the network. - -## Transport CI summary -[![](https://badge-proxy.cds.internal.unity3d.com/c59df3b8-7f64-4158-9ef7-4c99748185cb)](https://badges.cds.internal.unity3d.com/packages/com.unity.transport/build-info?branch=master) [![](https://badge-proxy.cds.internal.unity3d.com/65a2af76-0337-4ec3-a20c-5f9a09ed62eb)](https://badges.cds.internal.unity3d.com/packages/com.unity.transport/dependencies-info?branch=master) [![](https://badge-proxy.cds.internal.unity3d.com/5cd5fb42-a61f-4502-b75a-b8d80deb41f2)](https://badges.cds.internal.unity3d.com/packages/com.unity.transport/dependants-info) [![](https://badge-proxy.cds.internal.unity3d.com/cad278d5-2dba-4434-aac2-1466a4bd2ea6)](https://badges.cds.internal.unity3d.com/packages/com.unity.transport/warnings-info?branch=master) ![ReleaseBadge](https://badge-proxy.cds.internal.unity3d.com/f2096d78-45e6-4402-978b-0058b1e3277c) ![ReleaseBadge](https://badge-proxy.cds.internal.unity3d.com/fb5e4d88-0b2f-4883-ad0d-1b69b33e7861) - -## Documentation - -For more information about the Transport package, please see the [Unity Transport Documentation](https://docs-multiplayer.unity3d.com/transport/current/about). The site includes guides, API reference, and release notes. - -A [changelog](CHANGELOG.md) is also available in the package. - -## Connect - -See the [Multiplayer forum](https://forum.unity.com/forums/multiplayer.26/) to ask questions and connect with Transport. - diff --git a/README.md.meta b/README.md.meta deleted file mode 100644 index 5593e1a..0000000 --- a/README.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: dfb79a61342174be1ba04fa4efdfdc6d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs index 498ba73..2ab8748 100644 --- a/Runtime/AssemblyInfo.cs +++ b/Runtime/AssemblyInfo.cs @@ -1,9 +1,15 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Unity.Networking.Transport.EditorTests")] -[assembly: InternalsVisibleTo("Unity.Networking.Transport.RuntimeTests")] +[assembly: InternalsVisibleTo("Unity.Networking.Transport.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.Networking.Transport.Runtime.Tests")] +[assembly: InternalsVisibleTo("Unity.Networking.Transport.Tests.Integration")] +[assembly: InternalsVisibleTo("Unity.Networking.Transport.Tests.Utilities")] [assembly: InternalsVisibleTo("Unity.Networking.Transport.PlayTests.Performance")] // We are making certain things visible for certain projects that require // access to Network Protocols thus not requiring the API be visible -[assembly: InternalsVisibleTo("Unity.InternalAPINetworkingBridge.001")] \ No newline at end of file +[assembly: InternalsVisibleTo("Unity.InternalAPINetworkingBridge.001")] + +// NetCode needs access to writeable Pipeline buffers, but the feature needs further thought. +// Thus, exception is made here. +[assembly: InternalsVisibleTo("Unity.NetCode")] diff --git a/Runtime/AtomicFreeList.cs b/Runtime/AtomicFreeList.cs index 1804a01..0e4de39 100644 --- a/Runtime/AtomicFreeList.cs +++ b/Runtime/AtomicFreeList.cs @@ -12,6 +12,7 @@ internal unsafe struct UnsafeAtomicFreeList : IDisposable // free indices... [NativeDisableUnsafePtrRestriction] private int* m_Buffer; + private int m_BufferSize; private int m_Length; private Allocator m_Allocator; @@ -29,9 +30,9 @@ public UnsafeAtomicFreeList(int capacity, Allocator allocator) { m_Allocator = allocator; m_Length = capacity; - var size = UnsafeUtility.SizeOf() * (capacity + 2); - m_Buffer = (int*)UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); - UnsafeUtility.MemClear(m_Buffer, size); + m_BufferSize = UnsafeUtility.SizeOf() * (capacity + 2); + m_Buffer = (int*)UnsafeUtility.Malloc(m_BufferSize, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemClear(m_Buffer, m_BufferSize); } public void Dispose() @@ -40,6 +41,11 @@ public void Dispose() UnsafeUtility.Free(m_Buffer, m_Allocator); } + public void Reset() + { + UnsafeUtility.MemClear(m_Buffer, m_BufferSize); + } + /// /// Inserts an item on top of the stack. /// diff --git a/Runtime/Base64.cs b/Runtime/Base64.cs index 082b3ed..156226d 100644 --- a/Runtime/Base64.cs +++ b/Runtime/Base64.cs @@ -5,9 +5,6 @@ namespace Unity.Networking.Transport { - /// - /// Utility class used to Decode a base64 string - /// internal static class Base64 { /// diff --git a/Runtime/BaselibNetworkArray.cs b/Runtime/BaselibNetworkArray.cs index 91063cd..e152195 100644 --- a/Runtime/BaselibNetworkArray.cs +++ b/Runtime/BaselibNetworkArray.cs @@ -1,9 +1,3 @@ -#if UNITY_STANDALONE_WIN || UNITY_GAMECORE || UNITY_XBOXONE || UNITY_EDITOR_WIN - #define BASELIB_USE_SPLIT_BUFFERS -#else - #undef BASELIB_USE_SPLIT_BUFFERS -#endif - using System; using Unity.Baselib.LowLevel; using Unity.Mathematics; @@ -14,25 +8,26 @@ namespace Unity.Networking.Transport { - using size_t = UIntPtr; - internal unsafe struct UnsafeBaselibNetworkArray : IDisposable { [NativeDisableUnsafePtrRestriction] UnsafePtrList m_BufferPool; + private uint m_ElementSize; + + public uint ElementSize => m_ElementSize; /// /// Initializes a new instance of the UnsafeBaselibNetworkArray struct. /// /// /// + /// /// Thrown if the capacity is less then 0 or if the value exceeds /// Thrown on internal baselib errors public UnsafeBaselibNetworkArray(int capacity, int typeSize) { + m_ElementSize = (uint)typeSize; + var totalSize = (long)typeSize; -#if !BASELIB_USE_SPLIT_BUFFERS - totalSize = capacity * typeSize; -#endif #if ENABLE_UNITY_COLLECTIONS_CHECKS if (typeSize < 0) @@ -43,18 +38,15 @@ public UnsafeBaselibNetworkArray(int capacity, int typeSize) #endif var poolSize = capacity; -#if !BASELIB_USE_SPLIT_BUFFERS - poolSize = 1; -#endif m_BufferPool = new UnsafePtrList(poolSize, Allocator.Persistent); + var pageInfo = stackalloc Binding.Baselib_Memory_PageSizeInfo[1]; + Binding.Baselib_Memory_GetPageSizeInfo(pageInfo); + var defaultPageSize = (ulong)pageInfo->defaultPageSize; + for (int i = 0; i < poolSize; i++) { - var pageInfo = stackalloc Binding.Baselib_Memory_PageSizeInfo[1]; - Binding.Baselib_Memory_GetPageSizeInfo(pageInfo); - var defaultPageSize = (ulong)pageInfo->defaultPageSize; - var pageCount = (ulong)1; if ((ulong)totalSize > defaultPageSize) { @@ -111,15 +103,13 @@ public void Dispose() } /// - /// Gets a element at the specified index, with the size of . + /// Gets a element at the specified index. /// /// A pointing to the index supplied. - public Binding.Baselib_RegisteredNetwork_BufferSlice AtIndexAsSlice(int index, uint elementSize) + public Binding.Baselib_RegisteredNetwork_BufferSlice AtIndexAsSlice(int index) { - uint offset = 0; IntPtr data; Binding.Baselib_RegisteredNetwork_Buffer* buffer = null; -#if BASELIB_USE_SPLIT_BUFFERS #if ENABLE_UNITY_COLLECTIONS_CHECKS if (index >= m_BufferPool.Length) { @@ -128,18 +118,18 @@ public Binding.Baselib_RegisteredNetwork_BufferSlice AtIndexAsSlice(int index, u #endif buffer = m_BufferPool[index]; data = (IntPtr)((byte*)buffer->allocation.ptr); -#else // BASELIB_USE_SPLIT_BUFFERS - buffer = m_BufferPool[0]; - offset = elementSize * (uint)index; - data = (IntPtr)((byte*)buffer->allocation.ptr + offset); -#endif // BASELIB_USE_SPLIT_BUFFERS Binding.Baselib_RegisteredNetwork_BufferSlice slice; slice.id = buffer->id; slice.data = data; - slice.offset = offset; - slice.size = elementSize; + slice.offset = 0; + slice.size = m_ElementSize; return slice; } + + public IntPtr GetBufferPtr(int index) + { + return m_BufferPool[index]->allocation.ptr; + } } -} \ No newline at end of file +} diff --git a/Runtime/BaselibNetworkInterface.cs b/Runtime/BaselibNetworkInterface.cs deleted file mode 100644 index 60ab9b6..0000000 --- a/Runtime/BaselibNetworkInterface.cs +++ /dev/null @@ -1,804 +0,0 @@ -#if !UNITY_WEBGL -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Unity.Baselib.LowLevel; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using Unity.Networking.Transport.Utilities.LowLevel.Unsafe; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; -using ErrorState = Unity.Baselib.LowLevel.Binding.Baselib_ErrorState; -using ErrorCode = Unity.Baselib.LowLevel.Binding.Baselib_ErrorCode; - -namespace Unity.Networking.Transport -{ - using NetworkRequest = Binding.Baselib_RegisteredNetwork_Request; - using NetworkEndpoint = Binding.Baselib_RegisteredNetwork_Endpoint; - using NetworkSocket = Binding.Baselib_RegisteredNetwork_Socket_UDP; - - public static class BaselibNetworkParameterExtensions - { - internal const int k_defaultRxQueueSize = 64; - internal const int k_defaultTxQueueSize = 64; - internal const uint k_defaultMaximumPayloadSize = 2000; - - /// - /// Sets the values for the - /// - /// - /// - /// - public static ref NetworkSettings WithBaselibNetworkInterfaceParameters( - ref this NetworkSettings settings, - int receiveQueueCapacity = k_defaultRxQueueSize, - int sendQueueCapacity = k_defaultTxQueueSize, - uint maximumPayloadSize = k_defaultMaximumPayloadSize - ) - { - var parameter = new BaselibNetworkParameter - { - receiveQueueCapacity = receiveQueueCapacity, - sendQueueCapacity = sendQueueCapacity, - maximumPayloadSize = maximumPayloadSize, - }; - - settings.AddRawParameterStruct(ref parameter); - - return ref settings; - } - - /// - /// Gets the - /// - /// Returns the values for the - public static BaselibNetworkParameter GetBaselibNetworkInterfaceParameters(ref this NetworkSettings settings) - { - if (!settings.TryGet(out var baselibParameters)) - { - baselibParameters.receiveQueueCapacity = k_defaultRxQueueSize; - baselibParameters.sendQueueCapacity = k_defaultTxQueueSize; - baselibParameters.maximumPayloadSize = k_defaultMaximumPayloadSize; - } - - return baselibParameters; - } - } - - /// - /// Network Parameters used to set queue and payload sizes for - /// - public struct BaselibNetworkParameter : INetworkParameter - { - /// - /// The maximum number of receiving packets that the can process in a single update iteration. - /// - public int receiveQueueCapacity; - /// - /// The maximum number of sending packets that the can process in a single update iteration. - /// - public int sendQueueCapacity; - /// - /// The maximum payload size. - /// - public uint maximumPayloadSize; - - public bool Validate() - { - var valid = true; - - if (receiveQueueCapacity <= 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(receiveQueueCapacity)} value ({receiveQueueCapacity}) must be greater than 0"); - } - if (sendQueueCapacity <= 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(sendQueueCapacity)} value ({sendQueueCapacity}) must be greater than 0"); - } - if (maximumPayloadSize <= 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(maximumPayloadSize)} value ({maximumPayloadSize}) must be greater than 0"); - } - - return valid; - } - } - - /// - /// Default NetworkInterface implementation based on Unity's internal Baselib UDP sockets this is ensure to work on all - /// platforms except for Unity's WebGL. - /// - [BurstCompile] - public struct BaselibNetworkInterface : INetworkInterface - { - /// - /// Default Parameters for - /// - public static BaselibNetworkParameter DefaultParameters = new BaselibNetworkParameter - { - receiveQueueCapacity = k_defaultRxQueueSize, - sendQueueCapacity = k_defaultTxQueueSize, - maximumPayloadSize = k_defaultMaximumPayloadSize - }; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - private class SocketList - { - public struct SocketId - { - public NetworkSocket socket; - } - public List OpenSockets = new List(); - - ~SocketList() - { - foreach (var socket in OpenSockets) - { - Binding.Baselib_RegisteredNetwork_Socket_UDP_Close(socket.socket); - } - } - } - private static SocketList AllSockets = new SocketList(); -#endif - internal struct Payloads : IDisposable - { - public UnsafeAtomicFreeList m_Handles; - public UnsafeBaselibNetworkArray m_PayloadArray; - public UnsafeBaselibNetworkArray m_EndpointArray; - private uint m_PayloadSize; - - public int InUse => m_Handles.InUse; - public int Capacity => m_Handles.Capacity; - - public Payloads(int capacity, uint maxPayloadSize) - { - m_PayloadSize = maxPayloadSize; - m_Handles = new UnsafeAtomicFreeList(capacity, Allocator.Persistent); - m_PayloadArray = new UnsafeBaselibNetworkArray(capacity, (int)maxPayloadSize); - m_EndpointArray = new UnsafeBaselibNetworkArray(capacity, (int)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); - } - - public bool IsCreated => m_Handles.IsCreated; - public void Dispose() - { - m_Handles.Dispose(); - m_PayloadArray.Dispose(); - m_EndpointArray.Dispose(); - } - - public NetworkRequest GetRequestFromHandle(int handle) - { - return new NetworkRequest - { - payload = m_PayloadArray.AtIndexAsSlice(handle, m_PayloadSize), - remoteEndpoint = new NetworkEndpoint - { - slice = m_EndpointArray.AtIndexAsSlice(handle, (uint)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize) - } - }; - } - - public int AcquireHandle() - { - return m_Handles.Pop(); - } - - public void ReleaseHandle(int handle) - { - m_Handles.Push(handle); - } - } - - private BaselibNetworkParameter configuration; - - private const int k_defaultRxQueueSize = 64; - private const int k_defaultTxQueueSize = 64; - private const int k_defaultMaximumPayloadSize = 2000; - - // Safety value for the maximum number of times we can recreate a socket. Recreating a - // socket so many times would indicate some deeper issue that we won't solve by opening - // new sockets all the time. This also prevents logging endlessly if we get stuck in a - // loop of recreating sockets very frequently. - private const uint k_MaxNumSocketRecreate = 1000; - - // We process the results in batches because if we allocate a big number of results, - // it can cause a stack overflow. - const uint k_RequestsBatchSize = 64; - - internal enum SocketStatus - { - SocketNormal, - SocketNeedsRecreate, - SocketFailed, - } - - internal unsafe struct BaselibData - { - public NetworkSocket m_Socket; - public SocketStatus m_SocketStatus; - public Payloads m_PayloadsTx; - public NetworkInterfaceEndPoint m_LocalEndpoint; - public long m_LastUpdateTime; - public long m_LastSocketRecreateTime; - public uint m_NumSocketRecreate; - } - - [ReadOnly] - internal NativeArray m_Baselib; - - [NativeDisableContainerSafetyRestriction] - private Payloads m_PayloadsRx; - [NativeDisableContainerSafetyRestriction] - private Payloads m_PayloadsTx; - - private UnsafeBaselibNetworkArray m_LocalAndTempEndpoint; - - /// - /// Returns the local endpoint the is bound to. - /// - /// NetworkInterfaceEndPoint - public unsafe NetworkInterfaceEndPoint LocalEndPoint => m_Baselib[0].m_LocalEndpoint; - - /// - /// Gets if the interface has been created. - /// - public bool IsCreated => m_Baselib.IsCreated; - - /// - /// Converts a generic to its version for the . - /// - /// The endpoint to convert. - /// returns 0 on success and sets the converted endpoint value - public unsafe int CreateInterfaceEndPoint(NetworkEndPoint address, out NetworkInterfaceEndPoint endpoint) - { - return CreateInterfaceEndPoint(address.rawNetworkAddress, out endpoint); - } - - private unsafe int CreateInterfaceEndPoint(Binding.Baselib_NetworkAddress address, out NetworkInterfaceEndPoint endpoint) - { - var slice = m_LocalAndTempEndpoint.AtIndexAsSlice(0, (uint)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); - NetworkEndpoint local; - var error = default(ErrorState); - endpoint = default; - - local = Binding.Baselib_RegisteredNetwork_Endpoint_Create( - &address, - slice, - &error); - if (error.code != ErrorCode.Success) - return (int)error.code; - - endpoint.dataLength = (int)local.slice.size; - fixed(void* ptr = endpoint.data) - { - UnsafeUtility.MemCpy(ptr, (void*)local.slice.data, endpoint.dataLength); - } - return (int)Error.StatusCode.Success; - } - - private unsafe NetworkInterfaceEndPoint GetLocalEndPoint(NetworkSocket socket) - { - var error = default(ErrorState); - Binding.Baselib_NetworkAddress local; - Binding.Baselib_RegisteredNetwork_Socket_UDP_GetNetworkAddress(socket, &local, &error); - var ep = default(NetworkInterfaceEndPoint); - if (error.code != ErrorCode.Success) - return ep; - - CreateInterfaceEndPoint(local, out ep); - - return ep; - } - - /// - /// Converts a to its generic version. - /// - /// The endpoint to convert. - /// Returns the converted endpoint value. - public unsafe NetworkEndPoint GetGenericEndPoint(NetworkInterfaceEndPoint endpoint) - { - var address = default(NetworkEndPoint); - var error = default(ErrorState); - var slice = m_LocalAndTempEndpoint.AtIndexAsSlice(0, (uint)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); - NetworkEndpoint local; - local.slice = slice; - local.slice.size = (uint)endpoint.dataLength; - UnsafeUtility.MemCpy((void*)local.slice.data, endpoint.data, endpoint.dataLength); - Binding.Baselib_RegisteredNetwork_Endpoint_GetNetworkAddress(local, &address.rawNetworkAddress, &error); - if (error.code != ErrorCode.Success) - return default; - return address; - } - - /// - /// Initializes a instance of the struct. - /// - /// An array of INetworkParameter. If there is no present, the default values are used. - /// Returns 0 on succees. - public unsafe int Initialize(NetworkSettings settings) - { - configuration = settings.GetBaselibNetworkInterfaceParameters(); - - m_Baselib = new NativeArray(1, Allocator.Persistent); - var baselib = default(BaselibData); - - m_PayloadsTx = new Payloads(configuration.sendQueueCapacity, configuration.maximumPayloadSize); - m_PayloadsRx = new Payloads(configuration.receiveQueueCapacity, configuration.maximumPayloadSize); - m_LocalAndTempEndpoint = new UnsafeBaselibNetworkArray(2, (int)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); - - baselib.m_PayloadsTx = m_PayloadsTx; - - m_Baselib[0] = baselib; - - return 0; - } - - /// - /// Disposes this instance - /// - public void Dispose() - { - if (m_Baselib[0].m_Socket.handle != IntPtr.Zero) - { - #if ENABLE_UNITY_COLLECTIONS_CHECKS - AllSockets.OpenSockets.Remove(new SocketList.SocketId - {socket = m_Baselib[0].m_Socket}); - #endif - Binding.Baselib_RegisteredNetwork_Socket_UDP_Close(m_Baselib[0].m_Socket); - } - - m_LocalAndTempEndpoint.Dispose(); - if (m_PayloadsTx.IsCreated) - m_PayloadsTx.Dispose(); - if (m_PayloadsRx.IsCreated) - m_PayloadsRx.Dispose(); - m_Baselib.Dispose(); - } - - [BurstCompile] - struct FlushSendJob : IJob - { - public Payloads Tx; - [NativeDisableContainerSafetyRestriction] - public NativeArray Baselib; - public unsafe void Execute() - { - var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; - - var error = default(ErrorState); - - // We ensure we never process more than the actual capacity to prevent unexpected deadlocks - for (var sendCount = 0; sendCount < Tx.Capacity; sendCount++) - { - var status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessSend(Baselib[0].m_Socket, &error); - - if (error.code != ErrorCode.Success) - { - UnityEngine.Debug.LogError(string.Format("Error on baselib processing send ({0})", error.code)); - MarkSocketAsNeedingRecreate(Baselib); - return; - } - - if (status != Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) - break; - } - - var count = 0; - var resultBatchesCount = Tx.Capacity / k_RequestsBatchSize + 1; - while ((count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueSend(Baselib[0].m_Socket, results, k_RequestsBatchSize, &error)) > 0) - { - if (error.code != ErrorCode.Success) - { - MarkSocketAsNeedingRecreate(Baselib); - return; - } - for (int i = 0; i < count; ++i) - { - // return results[i].status through userdata, mask? or allocate space at beginning? - // pass through a new NetworkPacketSender.? - Tx.ReleaseHandle((int)results[i].requestUserdata - 1); - } - - if (resultBatchesCount-- < 0) // Deadlock guard - break; - } - } - } - - [BurstCompile] - struct ReceiveJob : IJob - { - public NetworkPacketReceiver Receiver; - public Payloads Rx; - [NativeDisableContainerSafetyRestriction] - public NativeArray Baselib; - - public unsafe void Execute() - { - var error = default(ErrorState); - - // Update last update time in baselib data. - var baselib = Baselib[0]; - baselib.m_LastUpdateTime = Receiver.LastUpdateTime; - Baselib[0] = baselib; - - var pollCount = 0; - var status = default(Binding.Baselib_RegisteredNetwork_ProcessStatus); - while ((status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessRecv(Baselib[0].m_Socket, &error)) == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending - && pollCount++ < Rx.Capacity) {} - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (status == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) - { - UnityEngine.Debug.LogWarning("There are pending receive packets after the baselib process receive"); - } -#endif - - var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; - var totalCount = 0; - var totalFailedCount = 0; - var count = 0; - - do - { - // Pop Completed Requests off the CompletionQ - count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueRecv(Baselib[0].m_Socket, results, (uint)k_RequestsBatchSize, &error); - if (error.code != ErrorCode.Success) - { - Receiver.ReceiveErrorCode = (int)Error.StatusCode.NetworkSocketError; - return; - } - - totalCount += count; - - // Copy and run Append on each Packet. - for (int i = 0; i < count; i++) - { - if (results[i].status == Binding.Baselib_RegisteredNetwork_CompletionStatus.Failed) - { - totalFailedCount++; - continue; - } - - var receivedBytes = (int)results[i].bytesTransferred; - if (receivedBytes <= 0) - continue; - - var index = (int)results[i].requestUserdata - 1; - - var packet = Rx.GetRequestFromHandle(index); - - var remote = packet.remoteEndpoint.slice; - var address = default(NetworkInterfaceEndPoint); - address.dataLength = (int)remote.size; - UnsafeUtility.MemCpy(address.data, (void*)remote.data, (int)remote.size); - - Receiver.AppendPacket(packet.payload.data, ref address, receivedBytes); - - Rx.ReleaseHandle(index); - } - } - while (count == k_RequestsBatchSize); - - // All receive requests being marked as failed is as close as we're going to get to - // a signal that the socket has failed with the current baselib API (at least on - // platforms that use the basic POSIX sockets implementation under the hood). Note - // that we can't do the same check on send requests, since there might be legit - // scenarios where sends are failing temporarily without the socket being borked. - if (totalCount > 0 && totalFailedCount == totalCount) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogError("All socket receive requests were marked as failed, likely because socket itself has failed."); -#endif - MarkSocketAsNeedingRecreate(Baselib); - } - - var result = ScheduleAllReceives(Baselib[0].m_Socket, ref Rx); - if (result < 0) - { - Receiver.ReceiveErrorCode = (int)result; - MarkSocketAsNeedingRecreate(Baselib); - } - } - } - - private static void MarkSocketAsNeedingRecreate(NativeArray baselib) - { - var data = baselib[0]; - data.m_SocketStatus = SocketStatus.SocketNeedsRecreate; - baselib[0] = data; - } - - private void RecreateSocket(long updateTime) - { - var baselib = m_Baselib[0]; - - // If we already recreated the socket in the last update or if we hit the limit of - // socket recreations, then something's wrong at the socket layer and recreating it - // likely won't solve the issue. Just fail the socket in that scenario. - if (baselib.m_LastSocketRecreateTime == baselib.m_LastUpdateTime || baselib.m_NumSocketRecreate >= k_MaxNumSocketRecreate) - { - UnityEngine.Debug.LogError("Unrecoverable socket failure. An unknown condition is preventing the application from reliably creating sockets."); - baselib.m_SocketStatus = SocketStatus.SocketFailed; - m_Baselib[0] = baselib; - } - else - { - UnityEngine.Debug.LogWarning("Socket error encountered; attempting recovery by creating a new one."); - Bind(baselib.m_LocalEndpoint); - - // Update last socket recreation time and number of socket recreations. - baselib = m_Baselib[0]; - baselib.m_LastSocketRecreateTime = updateTime; - baselib.m_NumSocketRecreate++; - m_Baselib[0] = baselib; - } - } - - /// - /// Schedule a ReceiveJob. This is used to read data from your supported medium and pass it to the AppendData function - /// supplied by - /// - /// A used to parse the data received. - /// A to any dependency we might have. - /// A to our newly created ScheduleReceive Job. - public JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep) - { - if (m_Baselib[0].m_SocketStatus == SocketStatus.SocketNeedsRecreate) - RecreateSocket(receiver.LastUpdateTime); - - if (m_Baselib[0].m_SocketStatus == SocketStatus.SocketFailed) - { - receiver.ReceiveErrorCode = (int)Error.StatusCode.NetworkSocketError; - return dep; - } - - var job = new ReceiveJob - { - Baselib = m_Baselib, - Rx = m_PayloadsRx, - Receiver = receiver - }; - return job.Schedule(dep); - } - - /// - /// Schedule a SendJob. This is used to flush send queues to your supported medium - /// - /// The send queue which can be used to emulate parallel send. - /// A to any dependency we might have. - /// A to our newly created ScheduleSend Job. - public JobHandle ScheduleSend(NativeQueue sendQueue, JobHandle dep) - { - if (m_Baselib[0].m_SocketStatus != SocketStatus.SocketNormal) - return dep; - - var job = new FlushSendJob - { - Baselib = m_Baselib, - Tx = m_PayloadsTx - }; - return job.Schedule(dep); - } - - /// - /// Binds the medium to a specific endpoint. - /// - /// - /// A valid . - /// - /// Returns 0 on success. - public unsafe int Bind(NetworkInterfaceEndPoint endpoint) - { - var baselib = m_Baselib[0]; - - var slice = m_LocalAndTempEndpoint.AtIndexAsSlice(0, (uint)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); - UnsafeUtility.MemCpy((void*)slice.data, endpoint.data, endpoint.dataLength); - - var error = default(ErrorState); - - NetworkEndpoint local; - local.slice = slice; - - Binding.Baselib_NetworkAddress localAddress; - Binding.Baselib_RegisteredNetwork_Endpoint_GetNetworkAddress(local, &localAddress, &error); - - var socket = Binding.Baselib_RegisteredNetwork_Socket_UDP_Create( - &localAddress, - Binding.Baselib_NetworkAddress_AddressReuse.Allow, - checked((uint)configuration.sendQueueCapacity), - checked((uint)configuration.receiveQueueCapacity), - &error); - if (error.code != ErrorCode.Success) - return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; - - // Close old socket now that new one has been successfully created. - if (m_Baselib[0].m_Socket.handle != IntPtr.Zero) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AllSockets.OpenSockets.Remove(new SocketList.SocketId - {socket = m_Baselib[0].m_Socket}); -#endif - Binding.Baselib_RegisteredNetwork_Socket_UDP_Close(m_Baselib[0].m_Socket); - - // Recreate the payloads to make sure we do not loose any items from the queue - m_PayloadsRx.Dispose(); - m_PayloadsRx = new Payloads(configuration.receiveQueueCapacity, configuration.maximumPayloadSize); - } - - // Schedule receive right away so we do not loose packets received before the first call to update - var result = ScheduleAllReceives(socket, ref m_PayloadsRx); - if (result < 0) - return result; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AllSockets.OpenSockets.Add(new SocketList.SocketId {socket = socket}); -#endif - - baselib.m_Socket = socket; - baselib.m_SocketStatus = SocketStatus.SocketNormal; - baselib.m_LocalEndpoint = GetLocalEndPoint(socket); - - m_Baselib[0] = baselib; - return 0; - } - - /// - /// Listens on the socket, currently this Interface doesn't support listening as its UDP based. - /// - /// Returns 0 - public int Listen() - { - return 0; - } - - static TransportFunctionPointer BeginSendMessageFunctionPointer = new TransportFunctionPointer(BeginSendMessage); - static TransportFunctionPointer EndSendMessageFunctionPointer = new TransportFunctionPointer(EndSendMessage); - static TransportFunctionPointer AbortSendMessageFunctionPointer = new TransportFunctionPointer(AbortSendMessage); - - /// - /// Creates the send interface - /// - /// The network send interface - public unsafe NetworkSendInterface CreateSendInterface() - { - return new NetworkSendInterface - { - BeginSendMessage = BeginSendMessageFunctionPointer, - EndSendMessage = EndSendMessageFunctionPointer, - AbortSendMessage = AbortSendMessageFunctionPointer, - UserData = (IntPtr)m_Baselib.GetUnsafePtr() - }; - } - - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.BeginSendMessageDelegate))] - private static unsafe int BeginSendMessage(out NetworkInterfaceSendHandle handle, IntPtr userData, int requiredPayloadSize) - { - var baselib = (BaselibData*)userData; - handle = default; - int index = baselib->m_PayloadsTx.AcquireHandle(); - if (index < 0) - return (int)Error.StatusCode.NetworkSendQueueFull; - - var message = baselib->m_PayloadsTx.GetRequestFromHandle(index); - if ((int)message.payload.size < requiredPayloadSize) - { - baselib->m_PayloadsTx.ReleaseHandle(index); - return (int)Error.StatusCode.NetworkPacketOverflow; - } - - handle.id = index; - handle.size = 0; - handle.data = (IntPtr)message.payload.data; - handle.capacity = (int)message.payload.size; - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.EndSendMessageDelegate))] - private static unsafe int EndSendMessage(ref NetworkInterfaceSendHandle handle, ref NetworkInterfaceEndPoint address, IntPtr userData, ref NetworkSendQueueHandle sendQueueHandle) - { - var baselib = (BaselibData*)userData; - int index = handle.id; - var message = baselib->m_PayloadsTx.GetRequestFromHandle(index); - message.requestUserdata = (IntPtr)(index + 1); - message.payload.size = (uint)handle.size; - - var addr = address; - UnsafeUtility.MemCpy((void*)message.remoteEndpoint.slice.data, addr.data, address.dataLength); - - NetworkRequest* messagePtr = &message; - - var error = default(ErrorState); - var count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleSend( - baselib->m_Socket, - messagePtr, - 1u, - &error); - if (error.code != ErrorCode.Success) - { - baselib->m_PayloadsTx.ReleaseHandle(index); - return (int)error.code == -1 ? -1 : -(int)error.code; - } - return handle.size; - } - - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.AbortSendMessageDelegate))] - private static unsafe void AbortSendMessage(ref NetworkInterfaceSendHandle handle, IntPtr userData) - { - var baselib = (BaselibData*)userData; - var id = handle.id; - baselib->m_PayloadsTx.ReleaseHandle(id); - } - - private static unsafe int ScheduleAllReceives(NetworkSocket socket, ref Payloads PayloadsRx) - { - var error = default(ErrorState); - - var requests = stackalloc Binding.Baselib_RegisteredNetwork_Request[(int)k_RequestsBatchSize]; - var count = 0; - do - { - count = 0; - while (count < k_RequestsBatchSize && PayloadsRx.InUse < PayloadsRx.Capacity) - { - int handle = PayloadsRx.AcquireHandle(); - requests[count] = PayloadsRx.GetRequestFromHandle(handle); - requests[count].requestUserdata = (IntPtr)handle + 1; - ++count; - } - if (count > 0) - { - Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleRecv(socket, requests, (uint)count, &error); - // how should this be handled? what are the cases? - if (error.code != ErrorCode.Success) - return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; - } - } - while (count == k_RequestsBatchSize); - - return 0; - } - - bool ValidateParameters(BaselibNetworkParameter param) - { - if (param.receiveQueueCapacity <= 0) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogWarning("Value for receiveQueueCapacity must be larger then zero."); -#endif - return false; - } - if (param.sendQueueCapacity <= 0) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogWarning("Value for sendQueueCapacity must be larger then zero."); -#endif - return false; - } - return true; - } - - bool TryExtractParameters(out BaselibNetworkParameter config, params INetworkParameter[] param) - { - for (int i = 0; i < param.Length; ++i) - { - if (param[i] is BaselibNetworkParameter && ValidateParameters((BaselibNetworkParameter)param[i])) - { - config = (BaselibNetworkParameter)param[i]; - return true; - } - } - config = default; - return false; - } - } -} -#endif // !UNITY_WEBGL diff --git a/Runtime/BaselibNetworkInterface.deprecated.cs b/Runtime/BaselibNetworkInterface.deprecated.cs new file mode 100644 index 0000000..104a008 --- /dev/null +++ b/Runtime/BaselibNetworkInterface.deprecated.cs @@ -0,0 +1,87 @@ +using Unity.Jobs; +using System; + +namespace Unity.Networking.Transport +{ + public static class BaselibNetworkParameterExtensions + { + [Obsolete("To set receiveQueueCapacity and sendQueueCapacity parameters use WithNetworkConfigParameters()", false)] + public static ref NetworkSettings WithBaselibNetworkInterfaceParameters( + ref this NetworkSettings settings, + int receiveQueueCapacity = 0, + int sendQueueCapacity = 0, + uint maximumPayloadSize = 0 + ) + { + var parameter = new BaselibNetworkParameter + { + receiveQueueCapacity = receiveQueueCapacity, + sendQueueCapacity = sendQueueCapacity, + maximumPayloadSize = maximumPayloadSize, + }; + + settings.AddRawParameterStruct(ref parameter); + + return ref settings; + } + } + + [Obsolete("To set receiveQueueCapacity and sendQueueCapacity parameters use NetworkConfigParameter", false)] + public struct BaselibNetworkParameter : INetworkParameter + { + public int receiveQueueCapacity; + public int sendQueueCapacity; + public uint maximumPayloadSize; + + public bool Validate() + { + var valid = true; + + if (receiveQueueCapacity <= 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(receiveQueueCapacity)} value ({receiveQueueCapacity}) must be greater than 0"); + } + if (sendQueueCapacity <= 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(sendQueueCapacity)} value ({sendQueueCapacity}) must be greater than 0"); + } + if (maximumPayloadSize <= 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(maximumPayloadSize)} value ({maximumPayloadSize}) must be greater than 0"); + } + + return valid; + } + } + + [Obsolete("BaselibNetworkInterface has been deprecated. Use UDPNetworkInterface instead (UnityUpgradable) -> UDPNetworkInterface")] + public struct BaselibNetworkInterface : INetworkInterface + { + public NetworkEndpoint LocalEndpoint + => throw new System.NotImplementedException(); + + public bool IsCreated + => throw new System.NotImplementedException(); + + public int Initialize(ref NetworkSettings settings, ref int packetPadding) + => throw new System.NotImplementedException(); + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + => throw new System.NotImplementedException(); + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + => throw new System.NotImplementedException(); + + public int Bind(NetworkEndpoint endpoint) + => throw new System.NotImplementedException(); + + public int Listen() + => throw new System.NotImplementedException(); + + public unsafe void Dispose() + => throw new System.NotImplementedException(); + } +} diff --git a/Runtime/NetworkProtocol.cs.meta b/Runtime/BaselibNetworkInterface.deprecated.cs.meta similarity index 83% rename from Runtime/NetworkProtocol.cs.meta rename to Runtime/BaselibNetworkInterface.deprecated.cs.meta index bcd966d..fa7154e 100644 --- a/Runtime/NetworkProtocol.cs.meta +++ b/Runtime/BaselibNetworkInterface.deprecated.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 92570cf71531f6e4f88558d4869992b3 +guid: 7eac0a18db0904933bcfda7cb5b44e11 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ConnectionDataMap.cs b/Runtime/ConnectionDataMap.cs new file mode 100644 index 0000000..f623356 --- /dev/null +++ b/Runtime/ConnectionDataMap.cs @@ -0,0 +1,118 @@ +using System; +using Unity.Collections; + +namespace Unity.Networking.Transport +{ + /// + /// A generic map where the key is a ConnectionId and stored values correspond to + /// the last valid connection version. Disconnected connections return the defaultDataValue. + /// + /// Type of the data to store per connection. + internal struct ConnectionDataMap : IDisposable where T : unmanaged + { + private struct ConnectionSlot + { + public int Version; + public T Value; + } + + private NativeList m_List; + private T m_DefaultDataValue; + + public bool IsCreated => m_List.IsCreated; + public int Length => m_List.Length; + + internal ConnectionDataMap(int initialCapacity, T defaultDataValue, Allocator allocator) + { + m_DefaultDataValue = defaultDataValue; + m_List = new NativeList(initialCapacity, allocator); + } + + public void Dispose() + { + m_List.Dispose(); + } + + internal T this[ConnectionId connection] + { + get + { + if (connection.Id >= m_List.Length || connection.Id < 0) + return m_DefaultDataValue; + + var slot = m_List[connection.Id]; + + if (slot.Version != connection.Version) + return m_DefaultDataValue; + + return slot.Value; + } + set + { + if (connection.Id >= m_List.Length) + m_List.Resize(connection.Id + 1, NativeArrayOptions.ClearMemory); + + ref var slot = ref m_List.ElementAt(connection.Id); + + if (slot.Version > connection.Version) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentOutOfRangeException("The provided connection is not valid"); +#else + UnityEngine.Debug.LogError("The provided connection is not valid"); + return; +#endif + } + + slot.Version = connection.Version; + slot.Value = value; + } + } + + internal void ClearData(ref ConnectionId connection) + { + this[connection] = m_DefaultDataValue; + } + + internal ConnectionId ConnectionAt(int index) + { + if (index < 0 || index > m_List.Length) + return default; + + return new ConnectionId + { + Id = index, + Version = m_List[index].Version, + }; + } + + internal T DataAt(int index) + { + if (index < 0 || index >= m_List.Length) + return m_DefaultDataValue; + + return m_List[index].Value; + } + + public override bool Equals(object obj) + { + return obj is ConnectionDataMap map && + this == map; + } + + public override unsafe int GetHashCode() + { + return ((int)m_List.GetUnsafeList()).GetHashCode(); + } + + public static unsafe bool operator==(ConnectionDataMap a, ConnectionDataMap b) + { + return a.m_List.GetUnsafeList() == b.m_List.GetUnsafeList(); + } + + public static unsafe bool operator!=(ConnectionDataMap a, ConnectionDataMap b) + { + return !(a == b); + } + } +} diff --git a/Runtime/DataStream.cs.meta b/Runtime/ConnectionDataMap.cs.meta similarity index 83% rename from Runtime/DataStream.cs.meta rename to Runtime/ConnectionDataMap.cs.meta index 432702c..c0541b4 100644 --- a/Runtime/DataStream.cs.meta +++ b/Runtime/ConnectionDataMap.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a006f31a7b465d946a601f1d26b9d13c +guid: 97157538566e246249957e4faa9d1774 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ConnectionId.cs b/Runtime/ConnectionId.cs new file mode 100644 index 0000000..0745c2f --- /dev/null +++ b/Runtime/ConnectionId.cs @@ -0,0 +1,48 @@ +using System; + +namespace Unity.Networking.Transport +{ + internal struct ConnectionId : IEquatable + { + public int Id; + public int Version; + + public bool IsCreated => Version > 0; + + internal ConnectionId(int id, int version) + { + Id = id; + Version = version; + } + + public static bool operator==(ConnectionId lhs, ConnectionId rhs) + { + return lhs.Id == rhs.Id && lhs.Version == rhs.Version; + } + + public static bool operator!=(ConnectionId lhs, ConnectionId rhs) + { + return lhs.Id != rhs.Id || lhs.Version != rhs.Version; + } + + public override bool Equals(object o) + { + return this == (ConnectionId)o; + } + + public bool Equals(ConnectionId o) + { + return this == o; + } + + public override int GetHashCode() + { + return (Id << 8) ^ Version; + } + + public override string ToString() + { + return $"ConnectionId[id{Id},v{Version}]"; + } + } +} diff --git a/Runtime/NetworkCompressionModel.cs.meta b/Runtime/ConnectionId.cs.meta similarity index 83% rename from Runtime/NetworkCompressionModel.cs.meta rename to Runtime/ConnectionId.cs.meta index 1977618..c866eb3 100644 --- a/Runtime/NetworkCompressionModel.cs.meta +++ b/Runtime/ConnectionId.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8f1beb626cc4d46cea7be796acb882b8 +guid: 851aca5dbc77f4203913b2b1c642a8cc MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ConnectionList.cs b/Runtime/ConnectionList.cs new file mode 100644 index 0000000..a293821 --- /dev/null +++ b/Runtime/ConnectionList.cs @@ -0,0 +1,293 @@ +using System; +using Unity.Collections; + +namespace Unity.Networking.Transport +{ + /// + /// Provides an API for managing the NetworkDriver connections. + /// + internal struct ConnectionList : IDisposable + { + private struct ConnectionData + { + public NetworkEndpoint Endpoint; + public NetworkConnection.State State; + } + + internal struct CompletedDisconnection + { + public ConnectionId Connection; + public Error.DisconnectReason Reason; + } + + private ConnectionDataMap m_Connections; + + + /// + /// Stores all connections that completed the disconnection. + /// + private NativeQueue m_FinishedDisconnections; + + /// + /// Stores all connections (not requested by the remote endpoint) that completed the connection. + /// + private NativeQueue m_FinishedConnections; + + /// + /// Stores all connections (requested by the remote endpoint) that completed the connection. + /// + private NativeQueue m_IncomingConnections; + + /// + /// Stores all connections that can be created by reusing a previously released slot. + /// + private NativeQueue m_FreeList; + + /// + /// The current count of connections. + /// + public int Count => m_Connections.Length; + + public bool IsCreated => m_Connections.IsCreated; + + internal NativeQueue FreeList => m_FreeList; + + internal ConnectionId ConnectionAt(int index) => m_Connections.ConnectionAt(index); + internal NetworkEndpoint GetConnectionEndpoint(ConnectionId connectionId) => m_Connections[connectionId].Endpoint; + internal NetworkConnection.State GetConnectionState(ConnectionId connectionId) => m_Connections[connectionId].State; + + internal NativeArray QueryFinishedConnections(Allocator allocator) => m_FinishedConnections.ToArray(allocator); + internal NativeArray QueryIncomingConnections(Allocator allocator) => m_IncomingConnections.ToArray(allocator); + internal NativeArray QueryFinishedDisconnections(Allocator allocator) => m_FinishedDisconnections.ToArray(allocator); + + public static ConnectionList Create() + { + return new ConnectionList(Allocator.Persistent); + } + + private ConnectionList(Allocator allocator) + { + var defaultConnectionData = new ConnectionData { State = NetworkConnection.State.Disconnected }; + m_Connections = new ConnectionDataMap(1, defaultConnectionData, allocator); + m_FinishedDisconnections = new NativeQueue(allocator); + m_FinishedConnections = new NativeQueue(allocator); + m_FreeList = new NativeQueue(allocator); + m_IncomingConnections = new NativeQueue(allocator); + } + + public void Dispose() + { + m_Connections.Dispose(); + m_FinishedDisconnections.Dispose(); + m_FinishedConnections.Dispose(); + m_IncomingConnections.Dispose(); + m_FreeList.Dispose(); + } + + private ConnectionId GetNewConnection() + { + if (m_FreeList.TryDequeue(out var connectionId)) + { + // There is one free connection slot that we can reuse + // its version has been already increased. + return connectionId; + } + else + { + return new ConnectionId + { + Id = m_Connections.Length, + Version = 1, + }; + } + } + + /// + /// Creates a new connection to the provided address and sets its state to Connecting. + /// + /// The endpoint to connect to. + /// Returns the ConnectionId identifier for the new created connection. + /// The connection is going to be fully connected only when FinishConnecting() is called. + internal ConnectionId StartConnecting(ref NetworkEndpoint address) + { + var connection = GetNewConnection(); + + m_Connections[connection] = new ConnectionData + { + Endpoint = address, + State = NetworkConnection.State.Connecting, + }; + + return connection; + } + + /// + /// Completes a connection started by the local endpoint in Connecting state by setting it to Connected. + /// + /// The connecting connection to be completed. + internal void FinishConnectingFromLocal(ref ConnectionId connectionId) + { + // TODO: we might want to restric the connection completion to the layer that + // owns the connection list. + + CompleteConnecting(ref connectionId); + m_FinishedConnections.Enqueue(connectionId); + } + + /// + /// Completes a connection started by the remote endpoint in Connecting state by setting it to Connected. + /// + /// The connecting connection to be completed. + internal void FinishConnectingFromRemote(ref ConnectionId connectionId) + { + // TODO: we might want to restric the connection completion to the layer that + // owns the connection list. + + CompleteConnecting(ref connectionId); + m_IncomingConnections.Enqueue(connectionId); + } + + private void CompleteConnecting(ref ConnectionId connectionId) + { + var connectionData = m_Connections[connectionId]; + + if (connectionData.State != NetworkConnection.State.Connecting) + { + UnityEngine.Debug.LogWarning(string.Format("Attempting to complete a connection with state '{0}'", connectionData.State)); + return; + } + + connectionData.State = NetworkConnection.State.Connected; + m_Connections[connectionId] = connectionData; + } + + internal ConnectionId AcceptConnection() + { + if (!m_IncomingConnections.TryDequeue(out var connectionId)) + return default; + + var connectionState = GetConnectionState(connectionId); + if (connectionState != NetworkConnection.State.Connected) + { + UnityEngine.Debug.LogWarning(string.Format("Attempting to accept a connection ({0}) with state '{1}'", connectionId, connectionState)); + return default; + } + + return connectionId; + } + + internal bool IsConnectionAccepted(ref ConnectionId connectionId) + { + if (m_IncomingConnections.Count == 0) + return true; + + var unacceptedConnections = QueryIncomingConnections(Allocator.Temp); + if (unacceptedConnections.Contains(connectionId)) + return false; + + return true; + } + + /// + /// Sets the state of the connection to Disconnected. + /// + /// The connection to disconnect. + /// The connection is going to be fully disconnected only when CompleteDisconnection is called. + internal void StartDisconnecting(ref ConnectionId connectionId) + { + var connectionData = m_Connections[connectionId]; + + if (connectionData.State == NetworkConnection.State.Disconnected || + connectionData.State == NetworkConnection.State.Disconnecting) + { + UnityEngine.Debug.LogWarning("Attempting to disconnect an already disconnected connection"); + return; + } + + connectionData.State = NetworkConnection.State.Disconnecting; + m_Connections[connectionId] = connectionData; + } + + /// + /// Completes a disconnection by setting the state of the connection to Disconneted. + /// + /// The disconnecting connection to be completed. + /// The disconnect reason + /// + /// A Disconnect event with the provided reason will be enqueued at the begining of the next ScheduleUpdate() call. + /// The resources associated to the connection will be released at the begining of the next ScheduleUpdate() call. + /// + internal void FinishDisconnecting(ref ConnectionId connectionId, Error.DisconnectReason reason = Error.DisconnectReason.Default) + { + // TODO: we might want to restric the disconnection completion to the layer that + // owns the connection list. + + var connectionData = m_Connections[connectionId]; + + if (connectionData.State != NetworkConnection.State.Disconnecting) + { + UnityEngine.Debug.LogWarning(string.Format("Attempting to complete a disconnection with state different to Disconnecting ({0})", connectionData.State)); + return; + } + + m_FinishedDisconnections.Enqueue(new CompletedDisconnection + { + Connection = connectionId, + Reason = reason, + }); + + connectionData.State = NetworkConnection.State.Disconnected; + m_Connections[connectionId] = connectionData; + } + + /// + /// Cleanup of queues for connections/disconnections that has been completed. + /// + internal void Cleanup() + { + while (m_FinishedDisconnections.TryDequeue(out var disconnectionRequest)) + { + var overridingConnection = disconnectionRequest.Connection; + overridingConnection.Version++; + + // This will "initialize" the new available connection left by the disconnected one. + m_Connections.ClearData(ref overridingConnection); + + m_FreeList.Enqueue(overridingConnection); + } + + m_FinishedConnections.Clear(); + } + + internal void UpdateConnectionAddress(ref ConnectionId connection, ref NetworkEndpoint address) + { + var connectionData = m_Connections[connection]; + if (connectionData.Endpoint != address) + { + connectionData.Endpoint = address; + m_Connections[connection] = connectionData; + } + } + + public override bool Equals(object obj) + { + return obj is ConnectionList list && + this == list; + } + + public override int GetHashCode() + { + return m_Connections.GetHashCode(); + } + + public static unsafe bool operator==(ConnectionList a, ConnectionList b) + { + return a.m_Connections == b.m_Connections; + } + + public static unsafe bool operator!=(ConnectionList a, ConnectionList b) + { + return !(a == b); + } + } +} diff --git a/Runtime/SecureProtocol/SecureNetworkProtocol.cs.meta b/Runtime/ConnectionList.cs.meta similarity index 83% rename from Runtime/SecureProtocol/SecureNetworkProtocol.cs.meta rename to Runtime/ConnectionList.cs.meta index fbc61f5..a24b2e5 100644 --- a/Runtime/SecureProtocol/SecureNetworkProtocol.cs.meta +++ b/Runtime/ConnectionList.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6173d30467dc3f14c9b401273eb55a25 +guid: a983c47b15bcf459393545d9f33d8458 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/SessionIdToken.cs b/Runtime/ConnectionToken.cs similarity index 64% rename from Runtime/SessionIdToken.cs rename to Runtime/ConnectionToken.cs index 92a7a6d..35d1050 100644 --- a/Runtime/SessionIdToken.cs +++ b/Runtime/ConnectionToken.cs @@ -5,34 +5,34 @@ namespace Unity.Networking.Transport { [StructLayout(LayoutKind.Explicit)] - internal unsafe struct SessionIdToken : IEquatable, IComparable + internal unsafe struct ConnectionToken : IEquatable, IComparable { public const int k_Length = 8; [FieldOffset(0)] public fixed byte Value[k_Length]; - public static bool operator==(SessionIdToken lhs, SessionIdToken rhs) + public static bool operator==(ConnectionToken lhs, ConnectionToken rhs) { return lhs.Compare(rhs) == 0; } - public static bool operator!=(SessionIdToken lhs, SessionIdToken rhs) + public static bool operator!=(ConnectionToken lhs, ConnectionToken rhs) { return lhs.Compare(rhs) != 0; } - public bool Equals(SessionIdToken other) + public bool Equals(ConnectionToken other) { return Compare(other) == 0; } - public int CompareTo(SessionIdToken other) + public int CompareTo(ConnectionToken other) { return Compare(other); } public override bool Equals(object other) { - return other != null && this == (SessionIdToken)other; + return other != null && this == (ConnectionToken)other; } public override int GetHashCode() @@ -51,7 +51,13 @@ public override int GetHashCode() } } - int Compare(SessionIdToken other) + public override string ToString() + { + fixed(byte* p = Value) + return $"0x{*(long*)p:x16}"; + } + + int Compare(ConnectionToken other) { fixed(void* p = Value) { diff --git a/Runtime/ConnectionToken.cs.meta b/Runtime/ConnectionToken.cs.meta new file mode 100644 index 0000000..1f63e68 --- /dev/null +++ b/Runtime/ConnectionToken.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd23bec225464a243a02419ddaab6080 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/DataStream.cs b/Runtime/DataStream.cs deleted file mode 100644 index 408f42f..0000000 --- a/Runtime/DataStream.cs +++ /dev/null @@ -1,1089 +0,0 @@ -using System.Runtime.InteropServices; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using System; -using System.Diagnostics; -using Unity.Burst; - -namespace Unity.Networking.Transport -{ - [StructLayout(LayoutKind.Explicit)] - internal struct UIntFloat - { - [FieldOffset(0)] public float floatValue; - - [FieldOffset(0)] public uint intValue; - - [FieldOffset(0)] public double doubleValue; - - [FieldOffset(0)] public ulong longValue; - } - - /// - /// Data streams can be used to serialize data over the network. The - /// DataStreamWriter and DataStreamReader classes work together - /// to serialize data for sending and then to deserialize when receiving. - /// - /// - /// The reader can be used to deserialize the data from a NativeArray, writing data - /// to a NativeArray and reading it back can be done like this: - /// - /// using (var data = new NativeArray(16, Allocator.Persistent)) - /// { - /// var dataWriter = new DataStreamWriter(data); - /// dataWriter.WriteInt(42); - /// dataWriter.WriteInt(1234); - /// // Length is the actual amount of data inside the writer, - /// // Capacity is the total amount. - /// var dataReader = new DataStreamReader(nativeArrayOfBytes.GetSubArray(0, dataWriter.Length)); - /// var myFirstInt = dataReader.ReadInt(); - /// var mySecondInt = dataReader.ReadInt(); - /// } - /// - /// - /// There are a number of functions for various data types. If a copy of the writer - /// is stored it can be used to overwrite the data later on, this is particularly useful when - /// the size of the data is written at the start and you want to write it at - /// the end when you know the value. - /// - /// - /// using (var data = new NativeArray(16, Allocator.Persistent)) - /// { - /// var dataWriter = new DataStreamWriter(data); - /// // My header data - /// var headerSizeMark = dataWriter; - /// dataWriter.WriteUShort((ushort)0); - /// var payloadSizeMark = dataWriter; - /// dataWriter.WriteUShort((ushort)0); - /// dataWriter.WriteInt(42); - /// dataWriter.WriteInt(1234); - /// var headerSize = data.Length; - /// // Update header size to correct value - /// headerSizeMark.WriteUShort((ushort)headerSize); - /// // My payload data - /// byte[] someBytes = Encoding.ASCII.GetBytes("some string"); - /// dataWriter.Write(someBytes, someBytes.Length); - /// // Update payload size to correct value - /// payloadSizeMark.WriteUShort((ushort)(dataWriter.Length - headerSize)); - /// } - /// - /// - [StructLayout(LayoutKind.Sequential)] - public unsafe struct DataStreamWriter - { - public static bool IsLittleEndian - { - get - { - uint test = 1; - byte* testPtr = (byte*)&test; - return testPtr[0] == 1; - } - } - - struct StreamData - { - public byte* buffer; - public int length; - public int capacity; - public ulong bitBuffer; - public int bitIndex; - public int failedWrites; - } - - [NativeDisableUnsafePtrRestriction] StreamData m_Data; - internal IntPtr m_SendHandleData; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle m_Safety; -#endif - - /// - /// Initializes a new instance of the DataStreamWriter struct. - /// - /// The length of the buffer. - /// The used to allocate the memory. - public DataStreamWriter(int length, Allocator allocator) - { - CheckAllocator(allocator); - Initialize(out this, new NativeArray(length, allocator)); - } - - /// - /// Initializes a new instance of the DataStreamWriter struct with a NativeArray{byte} - /// - /// The buffer we want to attach to our DataStreamWriter. - public DataStreamWriter(NativeArray data) - { - Initialize(out this, data); - } - - /// - /// Initializes a new instance of the DataStreamWriter struct with a memory we don't own - /// - /// Pointer to the data - /// Length of the data - public DataStreamWriter(byte* data, int length) - { - var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data, length, Allocator.Invalid); -#if ENABLE_UNITY_COLLECTIONS_CHECKS - NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle()); -#endif - Initialize(out this, na); - } - - public NativeArray AsNativeArray() - { - var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_Data.buffer, Length, Allocator.Invalid); -#if ENABLE_UNITY_COLLECTIONS_CHECKS - NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, m_Safety); -#endif - return na; - } - - private static void Initialize(out DataStreamWriter self, NativeArray data) - { - self.m_SendHandleData = IntPtr.Zero; - - self.m_Data.capacity = data.Length; - self.m_Data.length = 0; - self.m_Data.buffer = (byte*)data.GetUnsafePtr(); - self.m_Data.bitBuffer = 0; - self.m_Data.bitIndex = 0; - self.m_Data.failedWrites = 0; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(data); -#endif - } - - private static short ByteSwap(short val) - { - return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff)); - } - - private static int ByteSwap(int val) - { - return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff)); - } - - /// - /// True if there is a valid data buffer present. This would be false - /// if the writer was created with no arguments. - /// - public bool IsCreated - { - get { return m_Data.buffer != null; } - } - - public bool HasFailedWrites => m_Data.failedWrites > 0; - - /// - /// The total size of the data buffer, see for - /// the size of space used in the buffer. - /// - public int Capacity - { - get - { - CheckRead(); - return m_Data.capacity; - } - } - - /// - /// The size of the buffer used. See for the total size. - /// - public int Length - { - get - { - CheckRead(); - SyncBitData(); - return m_Data.length + ((m_Data.bitIndex + 7) >> 3); - } - } - /// - /// The size of the buffer used in bits. See for the length in bytes. - /// - public int LengthInBits - { - get - { - CheckRead(); - SyncBitData(); - return m_Data.length * 8 + m_Data.bitIndex; - } - } - - private void SyncBitData() - { - var bitIndex = m_Data.bitIndex; - if (bitIndex <= 0) - return; - CheckWrite(); - - var bitBuffer = m_Data.bitBuffer; - int offset = 0; - while (bitIndex > 0) - { - m_Data.buffer[m_Data.length + offset] = (byte)bitBuffer; - bitIndex -= 8; - bitBuffer >>= 8; - ++offset; - } - } - - public void Flush() - { - while (m_Data.bitIndex > 0) - { - m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer; - m_Data.bitIndex -= 8; - m_Data.bitBuffer >>= 8; - } - - m_Data.bitIndex = 0; - } - - public bool WriteBytes(byte* data, int bytes) - { - CheckWrite(); - - if (m_Data.length + ((m_Data.bitIndex + 7) >> 3) + bytes > m_Data.capacity) - { - ++m_Data.failedWrites; - return false; - } - Flush(); - UnsafeUtility.MemCpy(m_Data.buffer + m_Data.length, data, bytes); - m_Data.length += bytes; - return true; - } - - public bool WriteByte(byte value) - { - return WriteBytes((byte*)&value, sizeof(byte)); - } - - /// - /// Copy NativeArray of bytes into the writers data buffer. - /// - /// Source byte array - public bool WriteBytes(NativeArray value) - { - return WriteBytes((byte*)value.GetUnsafeReadOnlyPtr(), value.Length); - } - - public bool WriteShort(short value) - { - return WriteBytes((byte*)&value, sizeof(short)); - } - - public bool WriteUShort(ushort value) - { - return WriteBytes((byte*)&value, sizeof(ushort)); - } - - public bool WriteInt(int value) - { - return WriteBytes((byte*)&value, sizeof(int)); - } - - public bool WriteUInt(uint value) - { - return WriteBytes((byte*)&value, sizeof(uint)); - } - - public bool WriteLong(long value) - { - return WriteBytes((byte*)&value, sizeof(long)); - } - - public bool WriteULong(ulong value) - { - return WriteBytes((byte*)&value, sizeof(ulong)); - } - - public bool WriteShortNetworkByteOrder(short value) - { - short netValue = IsLittleEndian ? ByteSwap(value) : value; - return WriteBytes((byte*)&netValue, sizeof(short)); - } - - public bool WriteUShortNetworkByteOrder(ushort value) - { - return WriteShortNetworkByteOrder((short)value); - } - - public bool WriteIntNetworkByteOrder(int value) - { - int netValue = IsLittleEndian ? ByteSwap(value) : value; - return WriteBytes((byte*)&netValue, sizeof(int)); - } - - public bool WriteUIntNetworkByteOrder(uint value) - { - return WriteIntNetworkByteOrder((int)value); - } - - public bool WriteFloat(float value) - { - UIntFloat uf = new UIntFloat(); - uf.floatValue = value; - return WriteInt((int)uf.intValue); - } - - private void FlushBits() - { - while (m_Data.bitIndex >= 8) - { - m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer; - m_Data.bitIndex -= 8; - m_Data.bitBuffer >>= 8; - } - } - - void WriteRawBitsInternal(uint value, int numbits) - { - CheckBits(value, numbits); - - m_Data.bitBuffer |= ((ulong)value << m_Data.bitIndex); - m_Data.bitIndex += numbits; - } - - public bool WriteRawBits(uint value, int numbits) - { - CheckWrite(); - - if (m_Data.length + ((m_Data.bitIndex + numbits + 7) >> 3) > m_Data.capacity) - { - ++m_Data.failedWrites; - return false; - } - WriteRawBitsInternal(value, numbits); - FlushBits(); - return true; - } - - public bool WritePackedUInt(uint value, NetworkCompressionModel model) - { - CheckWrite(); - int bucket = model.CalculateBucket(value); - uint offset = model.bucketOffsets[bucket]; - int bits = model.bucketSizes[bucket]; - ushort encodeEntry = model.encodeTable[bucket]; - - if (m_Data.length + ((m_Data.bitIndex + (encodeEntry & 0xff) + bits + 7) >> 3) > m_Data.capacity) - { - ++m_Data.failedWrites; - return false; - } - WriteRawBitsInternal((uint)(encodeEntry >> 8), encodeEntry & 0xFF); - WriteRawBitsInternal(value - offset, bits); - FlushBits(); - return true; - } - - public bool WritePackedULong(ulong value, NetworkCompressionModel model) - { - return WritePackedUInt((uint)(value >> 32), model) & - WritePackedUInt((uint)(value & 0xFFFFFFFF), model); - } - - public bool WritePackedInt(int value, NetworkCompressionModel model) - { - uint interleaved = (uint)((value >> 31) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2 - return WritePackedUInt(interleaved, model); - } - - public bool WritePackedLong(long value, NetworkCompressionModel model) - { - ulong interleaved = (ulong)((value >> 63) ^ (value << 1)); // interleave negative values between positive values: 0, -1, 1, -2, 2 - return WritePackedULong(interleaved, model); - } - - public bool WritePackedFloat(float value, NetworkCompressionModel model) - { - return WritePackedFloatDelta(value, 0, model); - } - - public bool WritePackedUIntDelta(uint value, uint baseline, NetworkCompressionModel model) - { - int diff = (int)(baseline - value); - return WritePackedInt(diff, model); - } - - public bool WritePackedIntDelta(int value, int baseline, NetworkCompressionModel model) - { - int diff = (int)(baseline - value); - return WritePackedInt(diff, model); - } - - public bool WritePackedLongDelta(long value, long baseline, NetworkCompressionModel model) - { - long diff = (long)(baseline - value); - return WritePackedLong(diff, model); - } - - public bool WritePackedULongDelta(ulong value, ulong baseline, NetworkCompressionModel model) - { - long diff = (long)(baseline - value); - return WritePackedLong(diff, model); - } - - public bool WritePackedFloatDelta(float value, float baseline, NetworkCompressionModel model) - { - CheckWrite(); - var bits = 0; - if (value != baseline) - bits = 32; - if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity) - { - ++m_Data.failedWrites; - return false; - } - if (bits == 0) - WriteRawBitsInternal(0, 1); - else - { - WriteRawBitsInternal(1, 1); - UIntFloat uf = new UIntFloat(); - uf.floatValue = value; - WriteRawBitsInternal(uf.intValue, bits); - } - FlushBits(); - return true; - } - - public unsafe bool WriteFixedString32(FixedString32Bytes str) - { - int length = (int)*((ushort*)&str) + 2; - byte* data = ((byte*)&str); - return WriteBytes(data, length); - } - public unsafe bool WriteFixedString64(FixedString64Bytes str) - { - int length = (int)*((ushort*)&str) + 2; - byte* data = ((byte*)&str); - return WriteBytes(data, length); - } - public unsafe bool WriteFixedString128(FixedString128Bytes str) - { - int length = (int)*((ushort*)&str) + 2; - byte* data = ((byte*)&str); - return WriteBytes(data, length); - } - public unsafe bool WriteFixedString512(FixedString512Bytes str) - { - int length = (int)*((ushort*)&str) + 2; - byte* data = ((byte*)&str); - return WriteBytes(data, length); - } - public unsafe bool WriteFixedString4096(FixedString4096Bytes str) - { - int length = (int)*((ushort*)&str) + 2; - byte* data = ((byte*)&str); - return WriteBytes(data, length); - } - - public unsafe bool WritePackedFixedString32Delta(FixedString32Bytes str, FixedString32Bytes baseline, NetworkCompressionModel model) - { - ushort length = *((ushort*)&str); - byte* data = ((byte*)&str) + 2; - return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - } - public unsafe bool WritePackedFixedString64Delta(FixedString64Bytes str, FixedString64Bytes baseline, NetworkCompressionModel model) - { - ushort length = *((ushort*)&str); - byte* data = ((byte*)&str) + 2; - return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - } - public unsafe bool WritePackedFixedString128Delta(FixedString128Bytes str, FixedString128Bytes baseline, NetworkCompressionModel model) - { - ushort length = *((ushort*)&str); - byte* data = ((byte*)&str) + 2; - return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - } - public unsafe bool WritePackedFixedString512Delta(FixedString512Bytes str, FixedString512Bytes baseline, NetworkCompressionModel model) - { - ushort length = *((ushort*)&str); - byte* data = ((byte*)&str) + 2; - return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - } - public unsafe bool WritePackedFixedString4096Delta(FixedString4096Bytes str, FixedString4096Bytes baseline, NetworkCompressionModel model) - { - ushort length = *((ushort*)&str); - byte* data = ((byte*)&str) + 2; - return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - } - - private unsafe bool WritePackedFixedStringDelta(byte* data, uint length, byte* baseData, uint baseLength, NetworkCompressionModel model) - { - var oldData = m_Data; - if (!WritePackedUIntDelta(length, baseLength, model)) - return false; - bool didFailWrite = false; - if (length <= baseLength) - { - for (uint i = 0; i < length; ++i) - didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model); - } - else - { - for (uint i = 0; i < baseLength; ++i) - didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model); - for (uint i = baseLength; i < length; ++i) - didFailWrite |= !WritePackedUInt(data[i], model); - } - // If anything was not written, rewind to the previous position - if (didFailWrite) - { - m_Data = oldData; - ++m_Data.failedWrites; - } - return !didFailWrite; - } - - /// - /// Moves the write position to the start of the data buffer used. - /// - public void Clear() - { - m_Data.length = 0; - m_Data.bitIndex = 0; - m_Data.bitBuffer = 0; - m_Data.failedWrites = 0; - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - void CheckRead() - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.CheckReadAndThrow(m_Safety); -#endif - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - void CheckWrite() - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); -#endif - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - static void CheckAllocator(Allocator allocator) - { - if (allocator != Allocator.Temp) - throw new InvalidOperationException("DataStreamWriters can only be created with temp memory"); - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - static void CheckBits(uint value, int numbits) - { - if (numbits < 0 || numbits > 32) - throw new ArgumentOutOfRangeException("Invalid number of bits"); - if (value >= (1UL << numbits)) - throw new ArgumentOutOfRangeException("Value does not fit in the specified number of bits"); - } - } - - /// - /// The DataStreamReader class is the counterpart of the - /// DataStreamWriter class and can be be used to deserialize - /// data which was prepared with it. - /// - /// - /// Simple usage example: - /// - /// using (var dataWriter = new DataStreamWriter(16, Allocator.Persistent)) - /// { - /// dataWriter.Write(42); - /// dataWriter.Write(1234); - /// // Length is the actual amount of data inside the writer, - /// // Capacity is the total amount. - /// var dataReader = new DataStreamReader(dataWriter, 0, dataWriter.Length); - /// var context = default(DataStreamReader.Context); - /// var myFirstInt = dataReader.ReadInt(ref context); - /// var mySecondInt = dataReader.ReadInt(ref context); - /// } - /// - /// - /// The DataStreamReader carries the position of the read pointer inside the struct, - /// taking a copy of the reader will also copy the read position. This includes passing the - /// reader to a method by value instead of by ref. - /// - /// See the class for more information - /// and examples. - /// - public unsafe struct DataStreamReader - { - struct Context - { - public int m_ReadByteIndex; - public int m_BitIndex; - public ulong m_BitBuffer; - public int m_FailedReads; - } - - [NativeDisableUnsafePtrRestriction] byte* m_bufferPtr; - Context m_Context; - int m_Length; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle m_Safety; -#endif - - public DataStreamReader(NativeArray array) - { - Initialize(out this, array); - } - - public DataStreamReader(byte* data, int length) - { - var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data, length, Allocator.Invalid); -#if ENABLE_UNITY_COLLECTIONS_CHECKS - NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle()); -#endif - Initialize(out this, na); - } - - private static void Initialize(out DataStreamReader self, NativeArray array) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array); -#endif - self.m_bufferPtr = (byte*)array.GetUnsafeReadOnlyPtr(); - self.m_Length = array.Length; - self.m_Context = default; - } - - public bool IsLittleEndian => DataStreamWriter.IsLittleEndian; - - private static short ByteSwap(short val) - { - return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff)); - } - - private static int ByteSwap(int val) - { - return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff)); - } - - public bool HasFailedReads => m_Context.m_FailedReads > 0; - /// - /// The total size of the buffer space this reader is working with. - /// - public int Length - { - get - { - CheckRead(); - return m_Length; - } - } - - /// - /// True if the reader has been pointed to a valid buffer space. This - /// would be false if the reader was created with no arguments. - /// - public bool IsCreated - { - get { return m_bufferPtr != null; } - } - - /// - /// Read and copy data to the memory location pointed to, an exception will - /// be thrown if it does not fit. - /// - /// - /// - /// Thrown if the length - /// will put the reader out of bounds based on the current read pointer - /// position. - public void ReadBytes(byte* data, int length) - { - CheckRead(); - if (GetBytesRead() + length > m_Length) - { - ++m_Context.m_FailedReads; -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to read {length} bytes from a stream where only {m_Length - GetBytesRead()} are available"); -#endif - UnsafeUtility.MemClear(data, length); - return; - } - // Restore the full bytes moved to the bit buffer but no consumed - m_Context.m_ReadByteIndex -= (m_Context.m_BitIndex >> 3); - m_Context.m_BitIndex = 0; - m_Context.m_BitBuffer = 0; - UnsafeUtility.MemCpy(data, m_bufferPtr + m_Context.m_ReadByteIndex, length); - m_Context.m_ReadByteIndex += length; - } - - /// - /// Read and copy data into the given NativeArray of bytes, an exception will - /// be thrown if not enough bytes are available. - /// - /// - public void ReadBytes(NativeArray array) - { - ReadBytes((byte*)array.GetUnsafePtr(), array.Length); - } - - public int GetBytesRead() - { - return m_Context.m_ReadByteIndex - (m_Context.m_BitIndex >> 3); - } - - public int GetBitsRead() - { - return (m_Context.m_ReadByteIndex << 3) - m_Context.m_BitIndex; - } - - public void SeekSet(int pos) - { - if (pos > m_Length) - { - ++m_Context.m_FailedReads; -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to seek to {pos} in a stream of length {m_Length}"); -#endif - return; - } - m_Context.m_ReadByteIndex = pos; - m_Context.m_BitIndex = 0; - m_Context.m_BitBuffer = 0UL; - } - - public byte ReadByte() - { - byte data; - ReadBytes((byte*)&data, sizeof(byte)); - return data; - } - - public short ReadShort() - { - short data; - ReadBytes((byte*)&data, sizeof(short)); - return data; - } - - public ushort ReadUShort() - { - ushort data; - ReadBytes((byte*)&data, sizeof(ushort)); - return data; - } - - public int ReadInt() - { - int data; - ReadBytes((byte*)&data, sizeof(int)); - return data; - } - - public uint ReadUInt() - { - uint data; - ReadBytes((byte*)&data, sizeof(uint)); - return data; - } - - public long ReadLong() - { - long data; - ReadBytes((byte*)&data, sizeof(long)); - return data; - } - - public ulong ReadULong() - { - ulong data; - ReadBytes((byte*)&data, sizeof(ulong)); - return data; - } - - public short ReadShortNetworkByteOrder() - { - short data; - ReadBytes((byte*)&data, sizeof(short)); - return IsLittleEndian ? ByteSwap(data) : data; - } - - public ushort ReadUShortNetworkByteOrder() - { - return (ushort)ReadShortNetworkByteOrder(); - } - - public int ReadIntNetworkByteOrder() - { - int data; - ReadBytes((byte*)&data, sizeof(int)); - return IsLittleEndian ? ByteSwap(data) : data; - } - - public uint ReadUIntNetworkByteOrder() - { - return (uint)ReadIntNetworkByteOrder(); - } - - public float ReadFloat() - { - UIntFloat uf = new UIntFloat(); - uf.intValue = (uint)ReadInt(); - return uf.floatValue; - } - - public uint ReadPackedUInt(NetworkCompressionModel model) - { - CheckRead(); - FillBitBuffer(); - uint peekMask = (1u << NetworkCompressionModel.k_MaxHuffmanSymbolLength) - 1u; - uint peekBits = (uint)m_Context.m_BitBuffer & peekMask; - ushort huffmanEntry = model.decodeTable[(int)peekBits]; - int symbol = huffmanEntry >> 8; - int length = huffmanEntry & 0xFF; - - if (m_Context.m_BitIndex < length) - { - ++m_Context.m_FailedReads; -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to read {length} bits from a stream where only {m_Context.m_BitIndex} are available"); -#endif - return 0; - } - - // Skip Huffman bits - m_Context.m_BitBuffer >>= length; - m_Context.m_BitIndex -= length; - - uint offset = model.bucketOffsets[symbol]; - int bits = model.bucketSizes[symbol]; - return ReadRawBitsInternal(bits) + offset; - } - - void FillBitBuffer() - { - while (m_Context.m_BitIndex <= 56 && m_Context.m_ReadByteIndex < m_Length) - { - m_Context.m_BitBuffer |= (ulong)m_bufferPtr[m_Context.m_ReadByteIndex++] << m_Context.m_BitIndex; - m_Context.m_BitIndex += 8; - } - } - - uint ReadRawBitsInternal(int numbits) - { - CheckBits(numbits); - if (m_Context.m_BitIndex < numbits) - { - ++m_Context.m_FailedReads; -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to read {numbits} bits from a stream where only {m_Context.m_BitIndex} are available"); -#endif - return 0; - } - uint res = (uint)(m_Context.m_BitBuffer & ((1UL << numbits) - 1UL)); - m_Context.m_BitBuffer >>= numbits; - m_Context.m_BitIndex -= numbits; - return res; - } - - public uint ReadRawBits(int numbits) - { - CheckRead(); - FillBitBuffer(); - return ReadRawBitsInternal(numbits); - } - - public ulong ReadPackedULong(NetworkCompressionModel model) - { - //hi - ulong hi = ReadPackedUInt(model); - hi <<= 32; - hi |= ReadPackedUInt(model); - return hi; - } - - public int ReadPackedInt(NetworkCompressionModel model) - { - uint folded = ReadPackedUInt(model); - return (int)(folded >> 1) ^ -(int)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...] - } - - public long ReadPackedLong(NetworkCompressionModel model) - { - ulong folded = ReadPackedULong(model); - return (long)(folded >> 1) ^ -(long)(folded & 1); // Deinterleave values from [0, -1, 1, -2, 2...] to [..., -2, -1, -0, 1, 2, ...] - } - - public float ReadPackedFloat(NetworkCompressionModel model) - { - return ReadPackedFloatDelta(0, model); - } - - public int ReadPackedIntDelta(int baseline, NetworkCompressionModel model) - { - int delta = ReadPackedInt(model); - return baseline - delta; - } - - public uint ReadPackedUIntDelta(uint baseline, NetworkCompressionModel model) - { - uint delta = (uint)ReadPackedInt(model); - return baseline - delta; - } - - public long ReadPackedLongDelta(long baseline, NetworkCompressionModel model) - { - long delta = ReadPackedLong(model); - return baseline - delta; - } - - public ulong ReadPackedULongDelta(ulong baseline, NetworkCompressionModel model) - { - ulong delta = (ulong)ReadPackedLong(model); - return baseline - delta; - } - - public float ReadPackedFloatDelta(float baseline, NetworkCompressionModel model) - { - CheckRead(); - FillBitBuffer(); - if (ReadRawBitsInternal(1) == 0) - return baseline; - - var bits = 32; - UIntFloat uf = new UIntFloat(); - uf.intValue = ReadRawBitsInternal(bits); - return uf.floatValue; - } - - public unsafe FixedString32Bytes ReadFixedString32() - { - FixedString32Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadFixedString(data, str.Capacity); - return str; - } - public unsafe FixedString64Bytes ReadFixedString64() - { - FixedString64Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadFixedString(data, str.Capacity); - return str; - } - public unsafe FixedString128Bytes ReadFixedString128() - { - FixedString128Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadFixedString(data, str.Capacity); - return str; - } - public unsafe FixedString512Bytes ReadFixedString512() - { - FixedString512Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadFixedString(data, str.Capacity); - return str; - } - public unsafe FixedString4096Bytes ReadFixedString4096() - { - FixedString4096Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadFixedString(data, str.Capacity); - return str; - } - - public unsafe ushort ReadFixedString(byte* data, int maxLength) - { - ushort length = ReadUShort(); - if (length > maxLength) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}"); -#endif - return 0; - } - ReadBytes(data, length); - return length; - } - - public unsafe FixedString32Bytes ReadPackedFixedString32Delta(FixedString32Bytes baseline, NetworkCompressionModel model) - { - FixedString32Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - return str; - } - public unsafe FixedString64Bytes ReadPackedFixedString64Delta(FixedString64Bytes baseline, NetworkCompressionModel model) - { - FixedString64Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - return str; - } - public unsafe FixedString128Bytes ReadPackedFixedString128Delta(FixedString128Bytes baseline, NetworkCompressionModel model) - { - FixedString128Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - return str; - } - public unsafe FixedString512Bytes ReadPackedFixedString512Delta(FixedString512Bytes baseline, NetworkCompressionModel model) - { - FixedString512Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - return str; - } - public unsafe FixedString4096Bytes ReadPackedFixedString4096Delta(FixedString4096Bytes baseline, NetworkCompressionModel model) - { - FixedString4096Bytes str; - byte* data = ((byte*)&str) + 2; - *(ushort*)&str = ReadPackedFixedStringDelta(data, str.Capacity, ((byte*)&baseline) + 2, *((ushort*)&baseline), model); - return str; - } - - public unsafe ushort ReadPackedFixedStringDelta(byte* data, int maxLength, byte* baseData, ushort baseLength, NetworkCompressionModel model) - { - uint length = ReadPackedUIntDelta(baseLength, model); - if (length > (uint)maxLength) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME - UnityEngine.Debug.LogError($"Trying to read a string of length {length} but max length is {maxLength}"); -#endif - return 0; - } - if (length <= baseLength) - { - for (int i = 0; i < length; ++i) - data[i] = (byte)ReadPackedUIntDelta(baseData[i], model); - } - else - { - for (int i = 0; i < baseLength; ++i) - data[i] = (byte)ReadPackedUIntDelta(baseData[i], model); - for (int i = baseLength; i < length; ++i) - data[i] = (byte)ReadPackedUInt(model); - } - return (ushort)length; - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - void CheckRead() - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.CheckReadAndThrow(m_Safety); -#endif - } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - static void CheckBits(int numbits) - { - if (numbits < 0 || numbits > 32) - throw new ArgumentOutOfRangeException("Invalid number of bits"); - } - } -} diff --git a/Runtime/HMACSHA256.cs b/Runtime/HMACSHA256.cs index a13343b..e0e48f6 100644 --- a/Runtime/HMACSHA256.cs +++ b/Runtime/HMACSHA256.cs @@ -2,9 +2,6 @@ namespace Unity.Networking.Transport { - /// - /// Utility class used to compute HMACSHA256 hash - /// internal static class HMACSHA256 { /// diff --git a/Runtime/INetworkInterface.cs b/Runtime/INetworkInterface.cs index 4fcad57..16f2ed0 100644 --- a/Runtime/INetworkInterface.cs +++ b/Runtime/INetworkInterface.cs @@ -1,276 +1,88 @@ using System; -using System.Runtime.CompilerServices; -using Unity.Networking.Transport.Protocols; using Unity.Collections; using Unity.Jobs; -using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; -using System.Runtime.InteropServices; -using Unity.Networking.Transport.Utilities; namespace Unity.Networking.Transport { - /// - /// The NetworkPacketReceiver is an interface for handling received packets, needed by the - /// It either can be used in two main scenarios: - /// 1. Your API requires a pointer to memory that you own. Then you should use , write to the memory and then with . You don't need to deallocate the memory - /// 2. Your API gives you a pointer that you don't own. In this case you should use with (default) - /// + [Obsolete("Use ReceiveJobArguments.ReceiveQueue instead", true)] public struct NetworkPacketReceiver { - /// - /// Calls NetworkDriver's - /// - /// Size of memory to allocate in bytes. Must be > 0 - /// Pointer to allocated memory or IntPtr.Zero if there is no space left (this function doesn't set ! caller should decide if this is Out of memory or something else) - [MethodImpl(MethodImplOptions.AggressiveInlining)] public IntPtr AllocateMemory(ref int dataLen) - { - return m_Driver.AllocateMemory(ref dataLen); - } + => throw new NotImplementedException(); - /// - /// Permits choosing between two ways of appending packets: via a copy or not. - /// [Flags] public enum AppendPacketMode { - /// - /// Append Packet via a copy, which is the default mode - /// None = 0, - /// - /// No Copy required when appending a packet - /// NoCopyNeeded = 1 } - /// - /// When data is received this function should be called to pass it inside - /// - /// Pointer to the data. If it is pointer to data that was received with make sure mode is > - /// Address where data was received from - /// Length of in bytes - /// Extra flags, like that means - no copy is needed, data is already in 's data stream - /// True if no errors - public bool AppendPacket(IntPtr data, ref NetworkInterfaceEndPoint address, int dataLen, AppendPacketMode mode = AppendPacketMode.None) - { - if ((mode & AppendPacketMode.NoCopyNeeded) != 0) - { - m_Driver.AppendPacket(data, ref address, dataLen); - return true; - } - - unsafe // copy external data -> m_Driver's data stream - { - var allocatedLen = dataLen; - var ptr = m_Driver.AllocateMemory(ref allocatedLen); - - if (ptr == IntPtr.Zero || allocatedLen < dataLen) - { - OutOfMemoryError(); - return false; - } - - UnsafeUtility.MemCpy((byte*)ptr.ToPointer(), (byte*)data.ToPointer(), dataLen); - m_Driver.AppendPacket(ptr, ref address, dataLen); - } - - return true; - } - - /// - /// Check if an address is currently associated with a valid connection. - /// This is mostly useful to keep interface internal lists of connections in sync with the correct state. - /// - public bool IsAddressUsed(NetworkInterfaceEndPoint address) - { - return m_Driver.IsAddressUsed(address); - } + public bool AppendPacket(IntPtr data, ref NetworkEndpoint address, int dataLen, AppendPacketMode mode = AppendPacketMode.None) + => throw new NotImplementedException(); - /// - /// Gets the value of the last update time. - /// - public long LastUpdateTime => m_Driver.LastUpdateTime; + public bool IsAddressUsed(NetworkEndpoint address) + => throw new NotImplementedException(); - void OutOfMemoryError() - { - ReceiveErrorCode = 10040; //(int)ErrorCode.OutOfMemory; - } + public long LastUpdateTime + => throw new NotImplementedException(); - /// - /// Sets the value of the receive error code - /// public int ReceiveErrorCode - { - set => m_Driver.ReceiveErrorCode = value; - } - - internal NetworkDriver m_Driver; + => throw new NotImplementedException(); } - /// - /// The send handle flags enum - /// [Flags] - public enum SendHandleFlags + internal enum SendHandleFlags { - /// - /// This SendHandle has been allocated by - /// AllocatedByDriver = 1 << 0 } - /// - /// A handle to data that's going to be sent on an interface. - /// - public struct NetworkInterfaceSendHandle + internal struct NetworkInterfaceSendHandle { - /// Pointer to the data buffer. public IntPtr data; - /// Maximum capacity of the data buffer. public int capacity; - /// Actual size of the data in the buffer. public int size; - /// Internal ID for this handle. public int id; - /// Internal flags used by the driver (tracks who allocated the memory). public SendHandleFlags flags; } - /// - /// The network send queue handle - /// - public struct NetworkSendQueueHandle - { - private IntPtr handle; - internal static unsafe NetworkSendQueueHandle ToTempHandle(NativeQueue.ParallelWriter sendQueue) - { - void* ptr = UnsafeUtility.Malloc(UnsafeUtility.SizeOf.ParallelWriter>(), UnsafeUtility.AlignOf.ParallelWriter>(), Allocator.Temp); - UnsafeUtility.WriteArrayElement(ptr, 0, sendQueue); - return new NetworkSendQueueHandle { handle = (IntPtr)ptr }; - } - - /// - /// Create from the internal handle - /// - /// - public unsafe NativeQueue.ParallelWriter FromHandle() - { - void* ptr = (void*)handle; - return UnsafeUtility.ReadArrayElement.ParallelWriter>(ptr, 0); - } - } - /// - /// The network send interface used to pass around function pointers to the actual - /// - public struct NetworkSendInterface - { - /// - /// Invoked from the lower level library at the beginning of the message sending routine. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int BeginSendMessageDelegate(out NetworkInterfaceSendHandle handle, IntPtr userData, int requiredPayloadSize); - - /// - /// Invoked from the lower level library at the end of the message sending routine. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int EndSendMessageDelegate(ref NetworkInterfaceSendHandle handle, ref NetworkInterfaceEndPoint address, IntPtr userData, ref NetworkSendQueueHandle sendQueue); - - /// - /// Will be invoked from the lower level library if sending a message was aborted. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void AbortSendMessageDelegate(ref NetworkInterfaceSendHandle handle, IntPtr userData); - - /// - /// The begin send message function pointer - /// - public TransportFunctionPointer BeginSendMessage; - /// - /// The end send message function pointer - /// - public TransportFunctionPointer EndSendMessage; - /// - /// The abort send message function pointer - /// - public TransportFunctionPointer AbortSendMessage; - /// - /// The user data - /// - [NativeDisableUnsafePtrRestriction] public IntPtr UserData; - } - /// - /// Interface for implementing a low-level networking interface see as an example - /// - /// public interface INetworkInterface : IDisposable { - /// - /// Gets the value of the local end point - /// - NetworkInterfaceEndPoint LocalEndPoint { get; } + NetworkEndpoint LocalEndpoint { get; } - /// - /// Initializes the interfacing passing in optional - /// - /// The param - /// The int - int Initialize(NetworkSettings settings); + int Initialize(ref NetworkSettings settings, ref int packetPadding); /// /// Schedule a ReceiveJob. This is used to read data from your supported medium and pass it to the AppendData function /// supplied by /// - /// A used to parse the data received. + /// A set of that can be used in the receive jobs. /// A to any dependency we might have. /// A to our newly created ScheduleReceive Job. - JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep); + JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep); /// /// Schedule a SendJob. This is used to flush send queues to your supported medium /// - /// The send queue which can be used to emulate parallel send. + /// A set of that can be used in the send jobs. /// A to any dependency we might have. /// A to our newly created ScheduleSend Job. - JobHandle ScheduleSend(NativeQueue sendQueue, JobHandle dep); + JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep); /// /// Binds the medium to a specific endpoint. /// /// - /// A valid . + /// A valid . /// /// 0 on Success - int Bind(NetworkInterfaceEndPoint endpoint); + int Bind(NetworkEndpoint endpoint); /// /// Start listening for incoming connections. This is normally a no-op for real UDP sockets. /// /// 0 on Success int Listen(); - - /// - /// Creates the send interface - /// - /// The network send interface - NetworkSendInterface CreateSendInterface(); - - /// - /// Creates the interface end point using the specified address - /// - /// The address - /// The endpoint - /// The int - int CreateInterfaceEndPoint(NetworkEndPoint address, out NetworkInterfaceEndPoint endpoint); - - /// - /// Gets the generic end point using the specified endpoint - /// - /// The endpoint - /// The network end point - NetworkEndPoint GetGenericEndPoint(NetworkInterfaceEndPoint endpoint); } } diff --git a/Runtime/INetworkLayer.cs b/Runtime/INetworkLayer.cs new file mode 100644 index 0000000..b277936 --- /dev/null +++ b/Runtime/INetworkLayer.cs @@ -0,0 +1,78 @@ +using System; +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + internal interface INetworkLayer : IDisposable + { + /// + /// Initialize the layer. + /// + /// + /// The connection list and packet padding arguments can be modified if necessary. A layer + /// implementing its own connections should replace the connection list with a new one. A + /// layer that may add padding to start of a packet should increment the packet padding + /// value by the maximum padding it may add. + /// + /// Configuration settings provided by the user. + /// Connection list of the layer below. + /// Total padding of the layers below. + int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding); + + /// + /// Schedule a job processing all received messages (and other internal bookkeeping). + /// + /// Arguments that can be used by the receive job. + /// Job that the new job should depend on. + JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency); + + /// + /// Schedule a job sending all packets in the send queue. + /// + /// Arguments that can be used by the send job. + /// Job that the new job should depend on. + JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency); + } + + /// + /// Arguments required by the ScheduleSend jobs. + /// + public struct SendJobArguments + { + /// + /// A queue containing all the packets to be sent. + /// + public PacketsQueue SendQueue; + + /// + /// The current update time value. + /// + public long Time; + } + + /// + /// Arguments required by the ScheduleReceive jobs. + /// + public struct ReceiveJobArguments + { + /// + /// A queue containing all the packets to be received. + /// + public PacketsQueue ReceiveQueue; + + /// + /// The result of the receive operation. + /// + public OperationResult ReceiveResult; + + /// + /// The current update time value. + /// + public long Time; + + internal NetworkDriverReceiver DriverReceiver; + internal NetworkEventQueue EventQueue; + internal NetworkPipelineProcessor PipelineProcessor; + } +} diff --git a/Runtime/INetworkLayer.cs.meta b/Runtime/INetworkLayer.cs.meta new file mode 100644 index 0000000..f2c4d7f --- /dev/null +++ b/Runtime/INetworkLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cbcf8786802acec4297e2b42ff1805c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/IPCManager.cs b/Runtime/IPCManager.cs index 7be230b..8bd82d4 100644 --- a/Runtime/IPCManager.cs +++ b/Runtime/IPCManager.cs @@ -1,12 +1,11 @@ using System; -using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Networking.Transport.Utilities; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; -using Unity.Networking.Transport.Protocols; -using Random = Unity.Mathematics.Random; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Burst; namespace Unity.Networking.Transport { @@ -17,13 +16,13 @@ internal struct IPCManager [StructLayout(LayoutKind.Explicit)] internal unsafe struct IPCData { - [FieldOffset(0)] public int from; - [FieldOffset(4)] public int length; - [FieldOffset(8)] public fixed byte data[NetworkParameterConstants.MTU]; + [FieldOffset(0)] public ushort fromPort; + [FieldOffset(2)] public int length; + [FieldOffset(6)] public fixed byte data[NetworkParameterConstants.MTU]; } private NativeMultiQueue m_IPCQueue; - private NativeHashMap m_IPCChannels; + private NativeParallelHashMap m_IPCChannels; internal static JobHandle ManagerAccessHandle; @@ -36,7 +35,7 @@ public void AddRef() if (m_RefCount == 0) { m_IPCQueue = new NativeMultiQueue(128); - m_IPCChannels = new NativeHashMap(64, Allocator.Persistent); + m_IPCChannels = new NativeParallelHashMap(64, Allocator.Persistent); } ++m_RefCount; } @@ -46,28 +45,47 @@ public void Release() --m_RefCount; if (m_RefCount == 0) { - ManagerAccessHandle.Complete(); + CompleteManagerAccess(); m_IPCQueue.Dispose(); m_IPCChannels.Dispose(); } } - internal unsafe void Update(NetworkInterfaceEndPoint local, NativeQueue queue) + internal unsafe void Update(NetworkEndpoint local, ref PacketsQueue sendQueue) { - QueuedSendMessage val; - while (queue.TryDequeue(out val)) + for (int i = 0; i < sendQueue.Count; i++) { + var packetProcessor = sendQueue[i]; + + if (!GetChannelByEndpoint(ref packetProcessor.EndpointRef, out var toChannel)) + { + if (packetProcessor.EndpointRef.Port == 0) + continue; + + CreateEndpoint(packetProcessor.EndpointRef.Port); + } + var ipcData = new IPCData(); - UnsafeUtility.MemCpy(ipcData.data, val.Data, val.DataLength); - ipcData.length = val.DataLength; - ipcData.from = *(int*)local.data; - m_IPCQueue.Enqueue(*(int*)val.Dest.data, ipcData); + packetProcessor.CopyPayload(ipcData.data, NetworkParameterConstants.MTU); + ipcData.length = packetProcessor.Length; + ipcData.fromPort = local.Port; + + m_IPCQueue.Enqueue(toChannel, ipcData); } } - public unsafe NetworkInterfaceEndPoint CreateEndPoint(ushort port) + [BurstDiscard] + private void CompleteManagerAccess() { + if (JobsUtility.IsExecutingJob) + return; + ManagerAccessHandle.Complete(); + } + + public unsafe NetworkEndpoint CreateEndpoint(ushort port) + { + CompleteManagerAccess(); int id = 0; if (port == 0) { @@ -90,75 +108,65 @@ public unsafe NetworkInterfaceEndPoint CreateEndPoint(ushort port) } } - var endpoint = default(NetworkInterfaceEndPoint); - endpoint.dataLength = 4; - *(int*)endpoint.data = id; + var endpoint = NetworkEndpoint.LoopbackIpv4.WithPort(port); return endpoint; } - public unsafe bool GetEndPointPort(NetworkInterfaceEndPoint ep, out ushort port) + public unsafe bool GetChannelByEndpoint(ref NetworkEndpoint endpoint, out int channel) { - ManagerAccessHandle.Complete(); - int id = *(int*)ep.data; - var values = m_IPCChannels.GetValueArray(Allocator.Temp); - var keys = m_IPCChannels.GetKeyArray(Allocator.Temp); - port = 0; - for (var i = 0; i < m_IPCChannels.Count(); ++i) + if (!endpoint.IsLoopback) { - if (values[i] == id) - { - port = keys[i]; - return true; - } + channel = -1; + return false; } - return false; + return m_IPCChannels.TryGetValue(endpoint.Port, out channel); } - public unsafe int PeekNext(NetworkInterfaceEndPoint local, void* slice, out int length, out NetworkInterfaceEndPoint from) + public unsafe int PeekNext(NetworkEndpoint local, void* slice, out int length, out NetworkEndpoint from) { - ManagerAccessHandle.Complete(); + CompleteManagerAccess(); IPCData data; from = default; length = 0; - if (m_IPCQueue.Peek(*(int*)local.data, out data)) + if (!GetChannelByEndpoint(ref local, out var localChannel)) + return 0; + + if (m_IPCQueue.Peek(localChannel, out data)) { UnsafeUtility.MemCpy(slice, data.data, data.length); length = data.length; } - GetEndPointByHandle(data.from, out from); + from = NetworkEndpoint.LoopbackIpv4.WithPort(data.fromPort); return length; } - public unsafe int ReceiveMessageEx(NetworkInterfaceEndPoint local, void* payloadData, int payloadLen, ref NetworkInterfaceEndPoint remote) + public unsafe int ReceiveMessageEx(NetworkEndpoint local, void* payloadData, int payloadLen, ref NetworkEndpoint remote) { + if (!GetChannelByEndpoint(ref local, out var localChannel)) + return 0; + IPCData data; - if (!m_IPCQueue.Peek(*(int*)local.data, out data)) + + if (!m_IPCQueue.Dequeue(localChannel, out data)) + { return 0; - GetEndPointByHandle(data.from, out remote); + } + + remote = NetworkEndpoint.LoopbackIpv4.WithPort(data.fromPort); var totalLength = Math.Min(payloadLen, data.length); UnsafeUtility.MemCpy(payloadData, data.data, totalLength); if (totalLength < data.length) return -10040; // out of memory - m_IPCQueue.Dequeue(*(int*)local.data, out data); return totalLength; } - - private unsafe void GetEndPointByHandle(int handle, out NetworkInterfaceEndPoint endpoint) - { - var temp = default(NetworkInterfaceEndPoint); - temp.dataLength = 4; - *(int*)temp.data = handle; - - endpoint = temp; - } } } diff --git a/Runtime/IPCNetworkInterface.cs b/Runtime/IPCNetworkInterface.cs index e28ee4d..7dcaf96 100644 --- a/Runtime/IPCNetworkInterface.cs +++ b/Runtime/IPCNetworkInterface.cs @@ -9,82 +9,23 @@ namespace Unity.Networking.Transport { - /// - /// The ipc network interface - /// [BurstCompile] public struct IPCNetworkInterface : INetworkInterface { - /// - /// The localendpoint - /// - [ReadOnly] private NativeArray m_LocalEndPoint; + [ReadOnly] private NativeArray m_LocalEndpoint; - /// - /// Gets the value of the local end point - /// - public NetworkInterfaceEndPoint LocalEndPoint => m_LocalEndPoint[0]; + public NetworkEndpoint LocalEndpoint => m_LocalEndpoint[0]; - /// - /// Creates an interface end point. Only available for loopback addresses. - /// - /// Loopback address - /// The endpoint - /// The status code of the result, 0 being a success. - public int CreateInterfaceEndPoint(NetworkEndPoint address, out NetworkInterfaceEndPoint endpoint) - { - if (!address.IsLoopback && !address.IsAny) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - throw new ArgumentException("IPC network driver can only handle loopback addresses"); -#else - endpoint = default(NetworkInterfaceEndPoint); - return (int)Error.StatusCode.NetworkArgumentMismatch; -#endif - } - - endpoint = IPCManager.Instance.CreateEndPoint(address.Port); - return (int)Error.StatusCode.Success; - } - - /// - /// Retrieves an already created endpoint with port or creates one. - /// - /// The loopback endpoint - /// NetworkEndPoint - public NetworkEndPoint GetGenericEndPoint(NetworkInterfaceEndPoint endpoint) - { - if (!IPCManager.Instance.GetEndPointPort(endpoint, out var port)) - return default; - return NetworkEndPoint.LoopbackIpv4.WithPort(port); - } - - /// - /// Initializes the interface passing in optional - /// - /// The param - /// The status code of the result, 0 being a success. - public int Initialize(NetworkSettings settings) + public int Initialize(ref NetworkSettings settings, ref int packetPadding) { IPCManager.Instance.AddRef(); - m_LocalEndPoint = new NativeArray(1, Allocator.Persistent); - - var ep = default(NetworkInterfaceEndPoint); - var result = 0; - - if ((result = CreateInterfaceEndPoint(NetworkEndPoint.LoopbackIpv4, out ep)) != (int)Error.StatusCode.Success) - return result; - - m_LocalEndPoint[0] = ep; + m_LocalEndpoint = new NativeArray(1, Allocator.Persistent); return 0; } - /// - /// Cleans up both the local end point and the IPCManager instance. - /// public void Dispose() { - m_LocalEndPoint.Dispose(); + m_LocalEndpoint.Dispose(); IPCManager.Instance.Release(); } @@ -92,56 +33,54 @@ public void Dispose() struct SendUpdate : IJob { public IPCManager ipcManager; - public NativeQueue ipcQueue; - [ReadOnly] public NativeArray localEndPoint; + public PacketsQueue SendQueue; + public NetworkEndpoint localEndPoint; public void Execute() { - ipcManager.Update(localEndPoint[0], ipcQueue); + ipcManager.Update(localEndPoint, ref SendQueue); } } [BurstCompile] struct ReceiveJob : IJob { - public NetworkPacketReceiver receiver; + public PacketsQueue ReceiveQueue; + public OperationResult ReceiveResult; public IPCManager ipcManager; - public NetworkInterfaceEndPoint localEndPoint; + public NetworkEndpoint localEndPoint; public unsafe void Execute() { - receiver.ReceiveErrorCode = 0; - while (true) { - var size = NetworkParameterConstants.MTU; - var ptr = receiver.AllocateMemory(ref size); - if (ptr == IntPtr.Zero) + // This always acquires one extra packet because we don't know if a message is incoming until + // we actually read it. If after all there is no message, packet will be dropped. But we still + // need to have that extra packet available in the queue. + if (!ReceiveQueue.EnqueuePacket(out var packetProcessor)) + { + ReceiveResult.ErrorCode = (int)Error.StatusCode.NetworkReceiveQueueFull; return; + } + + var ptr = packetProcessor.GetUnsafePayloadPtr(); + var endpoint = default(NetworkEndpoint); + var result = NativeReceive(ptr, packetProcessor.Capacity, ref endpoint); + + packetProcessor.EndpointRef = endpoint; - var endpoint = default(NetworkInterfaceEndPoint); - var resultReceive = NativeReceive((byte*)ptr.ToPointer(), size, ref endpoint); - if (resultReceive <= 0) + if (result <= 0) { - if (resultReceive != 0) - receiver.ReceiveErrorCode = -resultReceive; + if (result != 0) + ReceiveResult.ErrorCode = -result; return; } - var resultAppend = receiver.AppendPacket(ptr, ref endpoint, resultReceive); - if (resultAppend == false) - return; + packetProcessor.SetUnsafeMetadata(result); } } - /// - /// Reads incoming data using native methods. - /// - /// The data - /// The length - /// The address - /// The int - unsafe int NativeReceive(void* data, int length, ref NetworkInterfaceEndPoint address) + unsafe int NativeReceive(void* data, int length, ref NetworkEndpoint address) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (length <= 0) @@ -151,143 +90,42 @@ unsafe int NativeReceive(void* data, int length, ref NetworkInterfaceEndPoint ad } } - /// - /// Schedule a ReceiveJob. This is used to read data from your supported medium and pass it to the AppendData function - /// supplied by - /// - /// A used to parse the data received. - /// A to any dependency we might have. - /// A to our newly created ScheduleReceive Job. - public JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep) + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) { var job = new ReceiveJob { - receiver = receiver, + ReceiveQueue = arguments.ReceiveQueue, ipcManager = IPCManager.Instance, - localEndPoint = LocalEndPoint + localEndPoint = m_LocalEndpoint[0], + ReceiveResult = arguments.ReceiveResult, }; dep = job.Schedule(JobHandle.CombineDependencies(dep, IPCManager.ManagerAccessHandle)); IPCManager.ManagerAccessHandle = dep; return dep; } - /// - /// Schedule a SendJob. This is used to flush send queues to your supported medium - /// - /// The send queue which can be used to emulate parallel send. - /// A to any dependency we might have. - /// A to our newly created ScheduleSend Job. - public JobHandle ScheduleSend(NativeQueue sendQueue, JobHandle dep) + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) { - var sendJob = new SendUpdate {ipcManager = IPCManager.Instance, ipcQueue = sendQueue, localEndPoint = m_LocalEndPoint}; + var sendJob = new SendUpdate {ipcManager = IPCManager.Instance, SendQueue = arguments.SendQueue, localEndPoint = m_LocalEndpoint[0]}; dep = sendJob.Schedule(JobHandle.CombineDependencies(dep, IPCManager.ManagerAccessHandle)); IPCManager.ManagerAccessHandle = dep; return dep; } - /// - /// Binds the medium to a specific endpoint. - /// - /// - /// A valid . - /// - /// 0 on Success - public unsafe int Bind(NetworkInterfaceEndPoint endpoint) + public unsafe int Bind(NetworkEndpoint endpoint) { #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (endpoint.dataLength != 4 || *(int*)endpoint.data == 0) - throw new InvalidOperationException(); + if (!endpoint.IsLoopback && !endpoint.IsAny) + throw new InvalidOperationException($"Trying to bind IPC interface to a non-loopback endpoint ({endpoint})"); #endif - m_LocalEndPoint[0] = endpoint; - return 0; - } - /// - /// Start listening for incoming connections. This is normally a no-op for real UDP sockets. - /// - /// 0 on Success - public int Listen() - { + m_LocalEndpoint[0] = IPCManager.Instance.CreateEndpoint(endpoint.Port); return 0; } - /// - /// Burst function pointer called at the beginning of the message sending routine. - /// - static TransportFunctionPointer BeginSendMessageFunctionPointer = new TransportFunctionPointer(BeginSendMessage); - /// - /// Burst function pointer called at the end of the message sending routine. - /// - static TransportFunctionPointer EndSendMessageFunctionPointer = new TransportFunctionPointer(EndSendMessage); - /// - /// Burst function pointer called if a scheduled message is aborted. - /// - static TransportFunctionPointer AbortSendMessageFunctionPointer = new TransportFunctionPointer(AbortSendMessage); - - /// - /// Creates the send interface - /// - /// The network send interface - public NetworkSendInterface CreateSendInterface() - { - return new NetworkSendInterface - { - BeginSendMessage = BeginSendMessageFunctionPointer, - EndSendMessage = EndSendMessageFunctionPointer, - AbortSendMessage = AbortSendMessageFunctionPointer, - }; - } - - /// - /// Begins the send message using the specified handle - /// - /// The handle - /// The user data - /// The required payload size - /// The int - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.BeginSendMessageDelegate))] - private static unsafe int BeginSendMessage(out NetworkInterfaceSendHandle handle, IntPtr userData, int requiredPayloadSize) + public int Listen() { - handle.id = 0; - handle.size = 0; - handle.capacity = requiredPayloadSize; - handle.data = (IntPtr)UnsafeUtility.Malloc(handle.capacity, 8, Allocator.Temp); - handle.flags = default; return 0; } - - /// - /// Ends the send message using the specified handle - /// - /// The handle - /// The address - /// The user data - /// The send queue handle - /// The int - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.EndSendMessageDelegate))] - private static unsafe int EndSendMessage(ref NetworkInterfaceSendHandle handle, ref NetworkInterfaceEndPoint address, IntPtr userData, ref NetworkSendQueueHandle sendQueueHandle) - { - var sendQueue = sendQueueHandle.FromHandle(); - var msg = default(QueuedSendMessage); - msg.Dest = address; - msg.DataLength = handle.size; - UnsafeUtility.MemCpy(msg.Data, (void*)handle.data, handle.size); - - sendQueue.Enqueue(msg); - return handle.size; - } - - /// - /// Aborts the send message using the specified handle - /// - /// The handle - /// The user data - [BurstCompile(DisableDirectCall = true)] - [AOT.MonoPInvokeCallback(typeof(NetworkSendInterface.AbortSendMessageDelegate))] - private static void AbortSendMessage(ref NetworkInterfaceSendHandle handle, IntPtr userData) - { - } } } diff --git a/Samples~/CustomNetworkInterface/Scenes.meta b/Runtime/Layers.meta similarity index 77% rename from Samples~/CustomNetworkInterface/Scenes.meta rename to Runtime/Layers.meta index d004c80..eaa3deb 100644 --- a/Samples~/CustomNetworkInterface/Scenes.meta +++ b/Runtime/Layers.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6b7ec5f1e28a9d148a233ee9c8ac6b09 +guid: da7c9acdfa0135142bfaac57ca669b49 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/Layers/BottomLayer.cs b/Runtime/Layers/BottomLayer.cs new file mode 100644 index 0000000..9cac964 --- /dev/null +++ b/Runtime/Layers/BottomLayer.cs @@ -0,0 +1,76 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + /// + /// The BottomLayer is the first one executed on receiving, + /// and the last one executed on sending. + /// + internal struct BottomLayer : INetworkLayer + { + private NativeList m_ConnectionLists; + + internal void AddConnectionList(ref ConnectionList connections) + { + m_ConnectionLists.Add(connections); + } + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + m_ConnectionLists = new NativeList(1, Allocator.Persistent); + return 0; + } + + public void Dispose() + { + m_ConnectionLists.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + var jobs = dependency; + + foreach (var connectionList in m_ConnectionLists) + { + jobs = JobHandle.CombineDependencies(jobs, new ConnectionListCleanup + { + Connections = connectionList, + }.Schedule(dependency)); + } + + return jobs; + } + + [BurstCompile] + private struct ConnectionListCleanup : IJob + { + public ConnectionList Connections; + + public void Execute() + { + Connections.Cleanup(); + } + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new ClearJob + { + SendQueue = arguments.SendQueue, + }.Schedule(dependency); + } + + [BurstCompile] + private struct ClearJob : IJob + { + public PacketsQueue SendQueue; + + public void Execute() + { + SendQueue.Clear(); + } + } + } +} diff --git a/Runtime/Layers/BottomLayer.cs.meta b/Runtime/Layers/BottomLayer.cs.meta new file mode 100644 index 0000000..cd51677 --- /dev/null +++ b/Runtime/Layers/BottomLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c62eda2f296cc4cc98e243af71ed7fe9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/DTLSLayer.cs b/Runtime/Layers/DTLSLayer.cs new file mode 100644 index 0000000..e0905de --- /dev/null +++ b/Runtime/Layers/DTLSLayer.cs @@ -0,0 +1,509 @@ +#if ENABLE_MANAGED_UNITYTLS + +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.TLS; +using Unity.TLS.LowLevel; +using UnityEngine; + +namespace Unity.Networking.Transport +{ + 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; + + private struct DTLSConnectionData + { + [NativeDisableUnsafePtrRestriction] + public Binding.unitytls_client* UnityTLSClientPtr; + + // Client being used for device reconnection. Will replace UnityTLSClientPtr if we + // actually get a response from the server on this new session. + [NativeDisableUnsafePtrRestriction] + public Binding.unitytls_client* ReconnectionClientPtr; + + // 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; + + // Tracks the last time a packet was received on this connection. Only used to determine + // when to initiate device reconnection. + public long LastReceive; + } + + internal ConnectionList m_ConnectionList; + private ConnectionDataMap m_ConnectionsData; + private NativeParallelHashMap m_EndpointToConnectionMap; + private UnityTLSConfiguration m_UnityTLSConfiguration; + private PacketsQueue m_DeferredSends; + private long m_HalfOpenDisconnectTimeout; + private long m_ReconnectionTimeout; + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (connectionList.IsCreated) + throw new InvalidOperationException("DTLS layer doesn't support underlying connection lists."); +#endif + var mtu = (ushort)(NetworkParameterConstants.MTU - packetPadding); + + connectionList = m_ConnectionList = ConnectionList.Create(); + m_ConnectionsData = new ConnectionDataMap(1, default(DTLSConnectionData), Allocator.Persistent); + m_EndpointToConnectionMap = new NativeParallelHashMap(1, Allocator.Persistent); + m_UnityTLSConfiguration = new UnityTLSConfiguration(ref settings, SecureTransportProtocol.DTLS, mtu); + m_DeferredSends = new PacketsQueue(k_DeferredSendsQueueSize); + + 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; + m_ReconnectionTimeout = netConfig.reconnectionTimeoutMS; + + packetPadding += k_DTLSPadding; + + return 0; + } + + public void Dispose() + { + // Destroy any remaining UnityTLS clients (their memory is managed by UnityTLS). + for (int i = 0; i < m_ConnectionsData.Length; i++) + { + var data = m_ConnectionsData.DataAt(i); + if (data.UnityTLSClientPtr != null) + Binding.unitytls_client_destroy(data.UnityTLSClientPtr); + if (data.ReconnectionClientPtr != null) + Binding.unitytls_client_destroy(data.ReconnectionClientPtr); + } + + m_ConnectionList.Dispose(); + m_ConnectionsData.Dispose(); + m_EndpointToConnectionMap.Dispose(); + m_UnityTLSConfiguration.Dispose(); + m_DeferredSends.Dispose(); + } + + [BurstCompile] + private struct ReceiveJob : IJob + { + public ConnectionList Connections; + public ConnectionDataMap ConnectionsData; + public NativeParallelHashMap EndpointToConnection; + public PacketsQueue DeferredSends; + public PacketsQueue ReceiveQueue; + public long Time; + public long HalfOpenDisconnectTimeout; + public long ReconnectionTimeout; + [NativeDisableUnsafePtrRestriction] + public Binding.unitytls_client_config* UnityTLSConfig; + [NativeDisableUnsafePtrRestriction] + public UnityTLSCallbacks.CallbackContext* UnityTLSCallbackContext; + + public void Execute() + { + UnityTLSCallbackContext->ReceivedPacket = default; + UnityTLSCallbackContext->SendQueue = DeferredSends; + UnityTLSCallbackContext->SendQueueIndex = -1; + UnityTLSCallbackContext->PacketPadding = 0; + + ProcessReceivedMessages(); + ProcessConnectionList(); + } + + private void ProcessReceivedMessages() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + if (packetProcessor.Length == 0) + continue; + + UnityTLSCallbackContext->ReceivedPacket = packetProcessor; + + if (DTLSUtilities.IsClientHello(ref packetProcessor)) + { + ProcessClientHello(packetProcessor.EndpointRef); + // Don't drop the packet, handshake check below will cover that. + } + + // Check if packet is from a known endpoint. Drop if not. + if (!EndpointToConnection.TryGetValue(packetProcessor.EndpointRef, out var connectionId)) + { + packetProcessor.Drop(); + continue; + } + + UpdateLastReceiveTime(connectionId); + + HandlePossibleReconnection(connectionId, ref packetProcessor); + + packetProcessor.ConnectionRef = connectionId; + + // If in initial or handshake state, process everything as a handshake message. + var clientPtr = ConnectionsData[connectionId].UnityTLSClientPtr; + var clientState = Binding.unitytls_client_get_state(clientPtr); + if (clientState == Binding.UnityTLSClientState_Init || clientState == Binding.UnityTLSClientState_Handshake) + { + ProcessHandshakeMessage(ref packetProcessor); + packetProcessor.Drop(); + continue; + } + + // If we get here then we have a data message (or some irrelevant garbage). + ProcessDataMessage(ref packetProcessor); + } + } + + private void ProcessClientHello(NetworkEndpoint fromEndpoint) + { + // Ignore Client Hellos if we already have a connection from that endpoint. + if (EndpointToConnection.ContainsKey(fromEndpoint)) + return; + + var connectionId = Connections.StartConnecting(ref fromEndpoint); + EndpointToConnection.Add(fromEndpoint, connectionId); + + var clientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Server, UnityTLSConfig); + Binding.unitytls_client_init(clientPtr); + + ConnectionsData[connectionId] = new DTLSConnectionData { UnityTLSClientPtr = clientPtr }; + } + + private void ProcessHandshakeMessage(ref PacketProcessor packetProcessor) + { + var connectionId = packetProcessor.ConnectionRef; + var data = ConnectionsData[connectionId]; + + UnityTLSCallbackContext->NewPacketsEndpoint = packetProcessor.EndpointRef; + + AdvanceHandshake(data.UnityTLSClientPtr); + + // Update the last handshake update time. + data.LastHandshakeUpdate = Time; + ConnectionsData[connectionId] = data; + + // Check if the handshake is over. + var clientState = Binding.unitytls_client_get_state(data.UnityTLSClientPtr); + if (clientState == Binding.UnityTLSClientState_Messaging) + { + var role = Binding.unitytls_client_get_role(data.UnityTLSClientPtr); + if (role == Binding.UnityTLSRole_Client) + Connections.FinishConnectingFromLocal(ref connectionId); + else + Connections.FinishConnectingFromRemote(ref connectionId); + } + } + + private void ProcessDataMessage(ref PacketProcessor packetProcessor) + { + var connectionId = packetProcessor.ConnectionRef; + var originalPacketOffset = packetProcessor.Offset; + + // 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 decryptedLength = new UIntPtr(); + var result = Binding.unitytls_client_read_data(ConnectionsData[connectionId].UnityTLSClientPtr, + (byte*)tempBuffer.GetUnsafePtr(), new UIntPtr((uint)tempBuffer.Length), &decryptedLength); + + if (result == Binding.UNITYTLS_SUCCESS) + { + packetProcessor.SetUnsafeMetadata(0, originalPacketOffset); + packetProcessor.AppendToPayload(tempBuffer.GetUnsafePtr(), (int)decryptedLength.ToUInt32()); + } + 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. + packetProcessor.Drop(); + } + } + + private void HandlePossibleReconnection(ConnectionId connection, ref PacketProcessor packetProcessor) + { + var data = ConnectionsData[connection]; + if (data.ReconnectionClientPtr == null) + return; + + // When it comes to reconnection, there are basically two scenarios: our IP address + // has changed, or it hasn't. If the our IP address has changed, then we need to + // create a new DTLS session with the new address. If it hasn't changed, then we + // need to maintain our existing one (we can't create a new one since the server + // will see any traffic from us as coming from the old session). + // + // This is why when we detect a need for reconnection, we create a whole new DTLS + // session on the side (ReconnectionClientPtr). If we get an answer from the server + // on that session (if we get a Server Hello), we know we changed IP address and + // start using the new session. Otherwise, it means we didn't change IP address and + // must keep using the new session. + + if (DTLSUtilities.IsServerHello(ref packetProcessor)) + { + Binding.unitytls_client_destroy(data.UnityTLSClientPtr); + data.UnityTLSClientPtr = data.ReconnectionClientPtr; + data.ReconnectionClientPtr = null; + } + else + { + Binding.unitytls_client_destroy(data.ReconnectionClientPtr); + data.ReconnectionClientPtr = null; + } + + ConnectionsData[connection] = data; + } + + private void ProcessConnectionList() + { + var count = Connections.Count; + for (int i = 0; i < count; i++) + { + var connectionId = Connections.ConnectionAt(i); + + HandleConnectionState(connectionId); + CheckForFailedClient(connectionId); + CheckForHalfOpenConnection(connectionId); + CheckForReconnection(connectionId); + } + } + + private void HandleConnectionState(ConnectionId connection) + { + var connectionEndpoint = Connections.GetConnectionEndpoint(connection); + var connectionState = Connections.GetConnectionState(connection); + + switch (connectionState) + { + case NetworkConnection.State.Connecting: + if (EndpointToConnection.TryAdd(connectionEndpoint, connection)) + { + var clientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Client, UnityTLSConfig); + Binding.unitytls_client_init(clientPtr); + + ConnectionsData[connection] = new DTLSConnectionData + { + UnityTLSClientPtr = clientPtr, + LastHandshakeUpdate = Time + }; + } + + // No matter if the connection is newly-connected or not, try to make + // progress on the handshake (e.g. with Client Hello resends). + UnityTLSCallbackContext->NewPacketsEndpoint = connectionEndpoint; + AdvanceHandshake(ConnectionsData[connection].UnityTLSClientPtr); + + break; + case NetworkConnection.State.Disconnecting: + Disconnect(connection); + break; + } + } + + private void CheckForFailedClient(ConnectionId connection) + { + var clientPtr = ConnectionsData[connection].UnityTLSClientPtr; + if (clientPtr == null) + return; + + if (Binding.unitytls_client_get_state(clientPtr) == Binding.UnityTLSClientState_Fail) + { + Connections.StartDisconnecting(ref connection); + // TODO Not the ideal disconnect reason. If we ever have a better one, use it. + Disconnect(connection, Error.DisconnectReason.ClosedByRemote); + } + } + + private void CheckForHalfOpenConnection(ConnectionId connection) + { + var clientPtr = ConnectionsData[connection].UnityTLSClientPtr; + if (clientPtr == null) + return; + + // Check client state; Init and Handshake state means a half-open connection. + var clientState = Binding.unitytls_client_get_state(clientPtr); + if (clientState != Binding.UnityTLSClientState_Init && clientState != Binding.UnityTLSClientState_Handshake) + return; + + // Check if connection has been half-open for too long. + var lastHandshakeUpdate = ConnectionsData[connection].LastHandshakeUpdate; + if (Time - lastHandshakeUpdate > HalfOpenDisconnectTimeout) + { + Connections.StartDisconnecting(ref connection); + Disconnect(connection, Error.DisconnectReason.Timeout); + } + } + + private void CheckForReconnection(ConnectionId connection) + { + var data = ConnectionsData[connection]; + if (data.UnityTLSClientPtr == null) + return; + + // Already reconnecting, nothing to do. + if (data.ReconnectionClientPtr != null) + return; + + // No reconnection for servers. + var role = Binding.unitytls_client_get_role(data.UnityTLSClientPtr); + if (role == Binding.UnityTLSRole_Server) + return; + + // Check if we have not received anything for too long and we have to reconnect. + if (data.LastReceive > 0 && Time - data.LastReceive > ReconnectionTimeout) + { + data.ReconnectionClientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Client, UnityTLSConfig); + Binding.unitytls_client_init(data.ReconnectionClientPtr); + + UnityTLSCallbackContext->NewPacketsEndpoint = Connections.GetConnectionEndpoint(connection); + AdvanceHandshake(data.ReconnectionClientPtr); + + ConnectionsData[connection] = data; + } + } + + private void AdvanceHandshake(Binding.unitytls_client* clientPtr) + { + while (Binding.unitytls_client_handshake(clientPtr) == Binding.UNITYTLS_HANDSHAKE_STEP); + } + + private void UpdateLastReceiveTime(ConnectionId connection) + { + var data = ConnectionsData[connection]; + data.LastReceive = Time; + ConnectionsData[connection] = data; + } + + private void Disconnect(ConnectionId connection, Error.DisconnectReason reason = Error.DisconnectReason.Default) + { + EndpointToConnection.Remove(Connections.GetConnectionEndpoint(connection)); + Connections.FinishDisconnecting(ref connection, reason); + + var data = ConnectionsData[connection]; + if (data.UnityTLSClientPtr != null) + Binding.unitytls_client_destroy(data.UnityTLSClientPtr); + if (data.ReconnectionClientPtr != null) + Binding.unitytls_client_destroy(data.ReconnectionClientPtr); + + ConnectionsData.ClearData(ref connection); + } + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + return new ReceiveJob + { + Connections = m_ConnectionList, + ConnectionsData = m_ConnectionsData, + EndpointToConnection = m_EndpointToConnectionMap, + DeferredSends = m_DeferredSends, + ReceiveQueue = arguments.ReceiveQueue, + Time = arguments.Time, + HalfOpenDisconnectTimeout = m_HalfOpenDisconnectTimeout, + ReconnectionTimeout = m_ReconnectionTimeout, + UnityTLSConfig = m_UnityTLSConfiguration.ConfigPtr, + UnityTLSCallbackContext = m_UnityTLSConfiguration.CallbackContextPtr, + }.Schedule(dependency); + } + + [BurstCompile] + private struct SendJob : IJob + { + public ConnectionDataMap ConnectionsData; + public NativeParallelHashMap EndpointToConnection; + public PacketsQueue SendQueue; + public PacketsQueue DeferredSends; + [NativeDisableUnsafePtrRestriction] + public UnityTLSCallbacks.CallbackContext* UnityTLSCallbackContext; + + public void Execute() + { + UnityTLSCallbackContext->SendQueue = SendQueue; + UnityTLSCallbackContext->PacketPadding = k_DTLSPadding; + + // Encrypt all the packets in the send queue. + var sendCount = SendQueue.Count; + for (int i = 0; i < sendCount; i++) + { + var packetProcessor = SendQueue[i]; + if (packetProcessor.Length == 0) + continue; + + UnityTLSCallbackContext->SendQueueIndex = i; + + if (!EndpointToConnection.TryGetValue(packetProcessor.EndpointRef, out var connectionId)) + { + packetProcessor.Drop(); + continue; + } + + 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. + var clientState = Binding.unitytls_client_get_state(clientPtr); + if (clientState != Binding.UnityTLSClientState_Messaging) + { + packetProcessor.Drop(); + continue; + } + + 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."); + packetProcessor.Drop(); + } + } + + // Add all the deferred sends to the send queue (they're already encrypted). + var deferredCount = DeferredSends.Count; + for (int i = 0; i < deferredCount; i++) + { + var deferredPacketProcessor = DeferredSends[i]; + if (deferredPacketProcessor.Length == 0) + continue; + + if (SendQueue.EnqueuePacket(out var packetProcessor)) + { + packetProcessor.EndpointRef = deferredPacketProcessor.EndpointRef; + packetProcessor.ConnectionRef = deferredPacketProcessor.ConnectionRef; + + // Remove the DTLS padding from the offset. + packetProcessor.SetUnsafeMetadata(0, packetProcessor.Offset - k_DTLSPadding); + + packetProcessor.AppendToPayload(deferredPacketProcessor); + } + } + + DeferredSends.Clear(); + } + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new SendJob + { + ConnectionsData = m_ConnectionsData, + EndpointToConnection = m_EndpointToConnectionMap, + SendQueue = arguments.SendQueue, + DeferredSends = m_DeferredSends, + UnityTLSCallbackContext = m_UnityTLSConfiguration.CallbackContextPtr, + }.Schedule(dependency); + } + } +} + +#endif \ No newline at end of file diff --git a/Runtime/Layers/DTLSLayer.cs.meta b/Runtime/Layers/DTLSLayer.cs.meta new file mode 100644 index 0000000..c3bf16e --- /dev/null +++ b/Runtime/Layers/DTLSLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 456b05a6bb3a07448a11cee5179f1e59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/LogLayer.cs b/Runtime/Layers/LogLayer.cs new file mode 100644 index 0000000..5c97c2a --- /dev/null +++ b/Runtime/Layers/LogLayer.cs @@ -0,0 +1,85 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + internal struct LogLayer : INetworkLayer + { + static private int s_InstancesCount; + + private int m_InstanceId; + + public void Dispose() {} + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + m_InstanceId = ++s_InstancesCount; + return 0; + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + return new LogJob + { + Label = $"[{m_InstanceId}] Received", + Queue = arguments.ReceiveQueue, + }.Schedule(dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new LogJob + { + Label = $"[{m_InstanceId}] Sent", + Queue = arguments.SendQueue, + }.Schedule(dependency); + } + + [BurstCompile] + private struct LogJob : IJob + { + public FixedString64Bytes Label; + public PacketsQueue Queue; + + public void Execute() + { + var count = Queue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = Queue[i]; + + if (packetProcessor.Length <= 0) + continue; + + var str = new FixedString4096Bytes(Label); + str.Append(FixedString.Format(" {0} bytes [Endpoint: {1}]: ", packetProcessor.Length, packetProcessor.EndpointRef.ToFixedString())); + if (AppendPayload(ref str, ref packetProcessor)) + { + UnityEngine.Debug.Log(str); + } + else + { + UnityEngine.Debug.Log(str); + UnityEngine.Debug.Log("Message truncated"); + } + } + } + + private bool AppendPayload(ref FixedString4096Bytes str, ref PacketProcessor packetProcessor) + { + var length = packetProcessor.Length; + for (int i = 0; i < length; i++) + { + var payloadStr = FixedString.Format("{0:X2} ", packetProcessor.GetPayloadDataRef(i)); + + if (str.Capacity - str.Length < payloadStr.Length) + return false; + + str.Append(payloadStr); + } + return true; + } + } + } +} diff --git a/Tests/Editor/NetworkConnectionUnitTests.cs.meta b/Runtime/Layers/LogLayer.cs.meta similarity index 100% rename from Tests/Editor/NetworkConnectionUnitTests.cs.meta rename to Runtime/Layers/LogLayer.cs.meta index a2a0d9e..4227776 100644 --- a/Tests/Editor/NetworkConnectionUnitTests.cs.meta +++ b/Runtime/Layers/LogLayer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: affed4d906927cb40a80f84fd8c662c3 +guid: c81327eb24722407882cf3a0fd61be09 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/Layers/NetworkInterfaceLayer.cs b/Runtime/Layers/NetworkInterfaceLayer.cs new file mode 100644 index 0000000..3a86952 --- /dev/null +++ b/Runtime/Layers/NetworkInterfaceLayer.cs @@ -0,0 +1,67 @@ +using Unity.Burst; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + internal struct NetworkInterfaceLayer : INetworkLayer where N : unmanaged, INetworkInterface + { + internal N m_NetworkInterface; + + public NetworkInterfaceLayer(N networkInterface) + { + m_NetworkInterface = networkInterface; + } + + public unsafe int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + var result = m_NetworkInterface.Initialize(ref settings, ref packetPadding); + if (result != 0) + return result; + + // This is here only to support current websocket implementation where it requires to keep a reference to the connection list. + if (BurstRuntime.GetHashCode64() == BurstRuntime.GetHashCode64()) + { + fixed(void* interfacePtr = &m_NetworkInterface) + { + ref var nif = ref *(WebSocketNetworkInterface*)interfacePtr; + connectionList = nif.CreateConnectionList(); + } + } +#if !UNITY_WEBGL || UNITY_EDITOR + else if (BurstRuntime.GetHashCode64() == BurstRuntime.GetHashCode64()) + { + fixed(void* interfacePtr = &m_NetworkInterface) + { + ref var nif = ref *(TCPNetworkInterface*)interfacePtr; + connectionList = nif.CreateConnectionList(); + } + } +#endif + return 0; + } + + public int Bind(ref NetworkEndpoint endpoint) + { + return m_NetworkInterface.Bind(endpoint); + } + + public int Listen() => m_NetworkInterface.Listen(); + + public NetworkEndpoint GetLocalEndpoint() => m_NetworkInterface.LocalEndpoint; + + public void Dispose() + { + m_NetworkInterface.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + return m_NetworkInterface.ScheduleReceive(ref arguments, dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return m_NetworkInterface.ScheduleSend(ref arguments, dependency); + } + } +} diff --git a/Runtime/Layers/NetworkInterfaceLayer.cs.meta b/Runtime/Layers/NetworkInterfaceLayer.cs.meta new file mode 100644 index 0000000..be50a57 --- /dev/null +++ b/Runtime/Layers/NetworkInterfaceLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 730a42312eea4054a94afe21681fe204 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/RelayLayer.cs b/Runtime/Layers/RelayLayer.cs new file mode 100644 index 0000000..f05115c --- /dev/null +++ b/Runtime/Layers/RelayLayer.cs @@ -0,0 +1,468 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Networking.Transport.Relay; + +namespace Unity.Networking.Transport +{ + internal struct RelayLayer : INetworkLayer + { + private const int k_DeferredSendQueueSize = 10; + + private struct ProtocolData + { + public RelayConnectionStatus ConnectionStatus; + public RelayServerData ServerData; + public ConnectionId UnderlyingConnection; + public long LastSentTime; + public int ConnectTimeout; + public int HeartbeatTime; + } + + private struct ConnectionData + { + public long LastConnectAttempt; + } + + /// + /// Connections at this layer level are connections to Relay Allocation Ids. + /// That is, other clients connected through the Relay server. Therefore, the + /// endpoints of all these connections are actually Allocation Ids. + /// + private ConnectionList m_Connections; + + /// + /// Underlying connections contains only one connection: the one between this + /// driver and the Relay server, using regular endpoints. + /// + private ConnectionList m_UnderlyingConnections; + + private PacketsQueue m_DeferredSendQueue; + private NativeReference m_ProtocolData; + private ConnectionDataMap m_ConnectionsData; + private NativeParallelHashMap m_EndpointsHashMap; + + public RelayConnectionStatus ConnectionStatus => m_ProtocolData.Value.ConnectionStatus; + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + var protocolData = new ProtocolData + { + ConnectionStatus = RelayConnectionStatus.NotEstablished, + ConnectTimeout = settings.GetNetworkConfigParameters().connectTimeoutMS, + HeartbeatTime = settings.GetRelayParameters().RelayConnectionTimeMS, + ServerData = settings.GetRelayParameters().ServerData, + }; + + m_ProtocolData = new NativeReference(protocolData, Allocator.Persistent); + m_DeferredSendQueue = new PacketsQueue(k_DeferredSendQueueSize); + m_ConnectionsData = new ConnectionDataMap(1, default, Allocator.Persistent); + m_EndpointsHashMap = new NativeParallelHashMap(1, Allocator.Persistent); + + m_DeferredSendQueue.SetDefaultDataOffset(packetPadding); + + if (connectionList.IsCreated) + m_UnderlyingConnections = connectionList; + + connectionList = m_Connections = ConnectionList.Create(); + + packetPadding += RelayMessageRelay.k_Length; + + return 0; + } + + public void Dispose() + { + m_Connections.Dispose(); + m_ProtocolData.Dispose(); + m_ConnectionsData.Dispose(); + m_EndpointsHashMap.Dispose(); + m_DeferredSendQueue.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + if (m_UnderlyingConnections.IsCreated) + return ScheduleReceive(new ReceiveJob(), new UnderlyingConnectionList(ref m_UnderlyingConnections), ref arguments, dependency); + else + return ScheduleReceive(new ReceiveJob(), default, ref arguments, dependency); + } + + private JobHandle ScheduleReceive(ReceiveJob job, T underlyingConnectionList, ref ReceiveJobArguments arguments, JobHandle dependency) + where T : unmanaged, IUnderlyingConnectionList + { + job.Connections = m_Connections; + job.ConnectionsData = m_ConnectionsData; + job.EndpointsHashmap = m_EndpointsHashMap; + job.ReceiveQueue = arguments.ReceiveQueue; + job.UnderlyingConnections = underlyingConnectionList; + job.DeferredSendQueue = m_DeferredSendQueue; + job.RelayProtocolData = m_ProtocolData; + job.Time = arguments.Time; + + return job.Schedule(dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new SendJob + { + Connections = m_Connections, + SendQueue = arguments.SendQueue, + DeferredSendQueue = m_DeferredSendQueue, + RelayProtocolData = m_ProtocolData, + Time = arguments.Time, + }.Schedule(dependency); + } + + [BurstCompile] + private struct SendJob : IJob + { + public ConnectionList Connections; + public PacketsQueue DeferredSendQueue; + public PacketsQueue SendQueue; + public NativeReference RelayProtocolData; + public long Time; + + public void Execute() + { + // Process all data messages. + var fromAllocationId = RelayProtocolData.Value.ServerData.AllocationId; + var underlyingConnectionId = RelayProtocolData.Value.UnderlyingConnection; + var underlyingEndpoint = RelayProtocolData.Value.ServerData.Endpoint; + var count = SendQueue.Count; + var actualSend = DeferredSendQueue.Count > 0; + + for (int i = 0; i < count; i++) + { + var packetProcessor = SendQueue[i]; + + if (packetProcessor.Length == 0) + continue; + + var connection = packetProcessor.ConnectionRef; + var endpoint = Connections.GetConnectionEndpoint(connection); + + RelayMessageRelay.Write(ref packetProcessor, ref fromAllocationId, ref endpoint.AsRelayAllocationId(), (ushort)packetProcessor.Length); + packetProcessor.ConnectionRef = underlyingConnectionId; + packetProcessor.EndpointRef = underlyingEndpoint; + + actualSend = true; + } + + // Send all deferred packets. + SendQueue.EnqueuePackets(ref DeferredSendQueue); + DeferredSendQueue.Clear(); + + if (actualSend) + { + var protocolData = RelayProtocolData.Value; + protocolData.LastSentTime = Time; + RelayProtocolData.Value = protocolData; + } + } + } + + [BurstCompile] + private struct ReceiveJob : IJob where T : unmanaged, IUnderlyingConnectionList + { + public ConnectionList Connections; + public ConnectionDataMap ConnectionsData; + public NativeParallelHashMap EndpointsHashmap; + public NativeReference RelayProtocolData; + public T UnderlyingConnections; + public PacketsQueue ReceiveQueue; + public PacketsQueue DeferredSendQueue; + public long Time; + + public void Execute() + { + ProcessReceivedMessages(); + ProcessRelayServerConnection(); + ProcessConnectionStates(); + } + + private void ProcessReceivedMessages() + { + var protocolData = RelayProtocolData.Value; + var count = ReceiveQueue.Count; + + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + if (packetProcessor.Length < RelayMessageHeader.k_Length) + { + packetProcessor.Drop(); + continue; + } + + var header = packetProcessor.GetPayloadDataRef(); + + if (!header.IsValid()) + { + packetProcessor.Drop(); + continue; + } + + switch (header.Type) + { + case RelayMessageType.BindReceived: + { + packetProcessor.Drop(); + protocolData.ConnectionStatus = RelayConnectionStatus.Established; + break; + } + case RelayMessageType.Accepted: + { + var acceptedMessage = packetProcessor.GetPayloadDataRef(); + + // An Accepted message can be received only when we requested the connection, + // and as we can only connect to the host, only one connection should be present in the list. + if (Connections.Count == 1) + { + var connectionId = Connections.ConnectionAt(0); + + if (Connections.GetConnectionState(connectionId) == NetworkConnection.State.Connecting) + { + var newEndpoint = acceptedMessage.FromAllocationId.ToNetworkEndpoint(); + Connections.FinishConnectingFromLocal(ref connectionId); + Connections.UpdateConnectionAddress(ref connectionId, ref newEndpoint); + EndpointsHashmap.Add(newEndpoint, connectionId); + } + } + else + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError("Received a Relay Accepted message but there is not one connection in the list." + + "One and only one connection is expected when initiating a connection using Relay"); +#endif + } + + packetProcessor.Drop(); + break; + } + case RelayMessageType.Disconnect: + { + var disconnectMessage = packetProcessor.GetPayloadDataRef(); + + // else, we received a disconnect request that was not for us, that should not happen. + if (disconnectMessage.ToAllocationId == RelayProtocolData.Value.ServerData.AllocationId) + { + var endpoint = disconnectMessage.FromAllocationId.ToNetworkEndpoint(); + if (EndpointsHashmap.TryGetValue(endpoint, out var connectionId)) + { + Connections.StartDisconnecting(ref connectionId); + Connections.FinishDisconnecting(ref connectionId, Error.DisconnectReason.ClosedByRemote); + EndpointsHashmap.Remove(endpoint); + } + } + + packetProcessor.Drop(); + break; + } + case RelayMessageType.Relay: + { + var relayHeader = packetProcessor.RemoveFromPayloadStart(); + if (relayHeader.DataLength != packetProcessor.Length) + { + packetProcessor.Drop(); + } + else + { + var destination = relayHeader.ToAllocationId; + if (destination != RelayProtocolData.Value.ServerData.AllocationId) + { + packetProcessor.Drop(); + } + else + { + var endpoint = relayHeader.FromAllocationId.ToNetworkEndpoint(); + if (!EndpointsHashmap.TryGetValue(endpoint, out var connectionId)) + { + // Relay does not notify of new connections, so we can only know about them + // when receiving a relay message from a new allocation id. + connectionId = Connections.StartConnecting(ref endpoint); + Connections.FinishConnectingFromRemote(ref connectionId); + EndpointsHashmap.TryAdd(endpoint, connectionId); + } + packetProcessor.EndpointRef = endpoint; + packetProcessor.ConnectionRef = connectionId; + } + } + break; + } + case RelayMessageType.Error: + { + var errorMessage = packetProcessor.GetPayloadDataRef(); + + errorMessage.LogError(); + + // ClientPlayerMismatch error means our IP has change and we need to rebind. + if (errorMessage.ErrorCode == 3) + { + protocolData.ServerData.Nonce++; + // Send a (re)Bind message + if (DeferredSendQueue.EnqueuePacket(out var bindPacket)) + { + RelayMessageBind.Write(ref bindPacket, ref protocolData.ServerData); + bindPacket.ConnectionRef = protocolData.UnderlyingConnection; + bindPacket.EndpointRef = protocolData.ServerData.Endpoint; + } + } + // Allocation time outs and failure to find the allocation indicate that the allocation + // is not valid anymore, and that users will need to recreate a new one. + else if (errorMessage.ErrorCode == 1 || errorMessage.ErrorCode == 4) + { + protocolData.ConnectionStatus = RelayConnectionStatus.AllocationInvalid; + } + + packetProcessor.Drop(); + break; + } + default: + packetProcessor.Drop(); + break; + } + } + + RelayProtocolData.Value = protocolData; + } + + private void ProcessRelayServerConnection() + { + var protocolData = RelayProtocolData.Value; + + switch (protocolData.ConnectionStatus) + { + case RelayConnectionStatus.NotEstablished: + { + if (UnderlyingConnections.TryConnect(ref protocolData.ServerData.Endpoint, ref protocolData.UnderlyingConnection)) + { + // When not connection is established LastSentTime is the last time we tried to bind. + if (Time - protocolData.LastSentTime > protocolData.ConnectTimeout || + protocolData.LastSentTime == 0) + { + protocolData.LastSentTime = Time; + + // Send a Bind message + if (DeferredSendQueue.EnqueuePacket(out var packetProcessor)) + { + RelayMessageBind.Write(ref packetProcessor, ref protocolData.ServerData); + packetProcessor.ConnectionRef = protocolData.UnderlyingConnection; + packetProcessor.EndpointRef = protocolData.ServerData.Endpoint; + } + } + } + break; + } + case RelayConnectionStatus.Established: + { + if (Time - protocolData.LastSentTime >= protocolData.HeartbeatTime) + { + // Send a Ping message + if (DeferredSendQueue.EnqueuePacket(out var packetProcessor)) + { + RelayMessagePing.Write(ref packetProcessor, ref protocolData.ServerData.AllocationId); + packetProcessor.ConnectionRef = protocolData.UnderlyingConnection; + packetProcessor.EndpointRef = protocolData.ServerData.Endpoint; + } + } + break; + } + } + + RelayProtocolData.Value = protocolData; + } + + private void ProcessConnectionStates() + { + var count = Connections.Count; + for (int i = 0; i < count; i++) + { + var connectionId = Connections.ConnectionAt(i); + var connectionState = Connections.GetConnectionState(connectionId); + + switch (connectionState) + { + case NetworkConnection.State.Disconnecting: + ProcessDisconnecting(ref connectionId); + break; + case NetworkConnection.State.Connecting: + ProcessConnecting(ref connectionId); + break; + } + } + } + + private void ProcessDisconnecting(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + var protocolData = RelayProtocolData.Value; + + if (protocolData.ConnectionStatus == RelayConnectionStatus.Established) + { + // Send a Disconnect message + if (DeferredSendQueue.EnqueuePacket(out var packetProcessor)) + { + var endpoint = Connections.GetConnectionEndpoint(connectionId); + RelayMessageDisconnect.Write(ref packetProcessor, + ref protocolData.ServerData.AllocationId, + ref endpoint.AsRelayAllocationId()); + + packetProcessor.ConnectionRef = protocolData.UnderlyingConnection; + packetProcessor.EndpointRef = protocolData.ServerData.Endpoint; + } + } + + Connections.FinishDisconnecting(ref connectionId); + + ConnectionsData.ClearData(ref connectionId); + EndpointsHashmap.Remove(Connections.GetConnectionEndpoint(connectionId)); + } + + private void ProcessConnecting(ref ConnectionId connectionId) + { + if (Connections.Count > 1) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError("Connect can only be called once when using Relay"); +#endif + Connections.StartDisconnecting(ref connectionId); + Connections.FinishDisconnecting(ref connectionId); + return; + } + + var connectionData = ConnectionsData[connectionId]; + + if (Time - connectionData.LastConnectAttempt >= RelayProtocolData.Value.ConnectTimeout) + { + var protocolData = RelayProtocolData.Value; + + 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)) + { + RelayMessageConnectRequest.Write(ref packetProcessor, + ref protocolData.ServerData.AllocationId, + ref protocolData.ServerData.HostConnectionData); + + packetProcessor.ConnectionRef = protocolData.UnderlyingConnection; + packetProcessor.EndpointRef = protocolData.ServerData.Endpoint; + } + } + } + } + } +} diff --git a/Runtime/Layers/RelayLayer.cs.meta b/Runtime/Layers/RelayLayer.cs.meta new file mode 100644 index 0000000..48f51c7 --- /dev/null +++ b/Runtime/Layers/RelayLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c348a99b49a4a4de8948c030dbd85a46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/SimpleConnectionLayer.cs b/Runtime/Layers/SimpleConnectionLayer.cs new file mode 100644 index 0000000..8845dbf --- /dev/null +++ b/Runtime/Layers/SimpleConnectionLayer.cs @@ -0,0 +1,974 @@ +using System.Runtime.InteropServices; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.Utilities; + +namespace Unity.Networking.Transport +{ + internal struct SimpleConnectionLayer : INetworkLayer + { + internal const byte k_ProtocolVersion = 1; + internal const int k_HeaderSize = 1 + ConnectionToken.k_Length; + internal const int k_HandshakeSize = 4 + k_HeaderSize; + internal const uint k_ProtocolSignatureAndVersion = 0x00505455 | (k_ProtocolVersion << 3 * 8); // Reversed for endianness + + private enum ConnectionState + { + Default = 0, + AwaitingAccept, + Established, + DisconnectionSent, + } + + internal enum HandshakeType : byte + { + ConnectionRequest = 1, + ConnectionAccept = 2, + } + + internal enum MessageType : byte + { + Data = 1, + Disconnect = 2, + Ping = 3, + Pong = 4, + } + + private struct SimpleConnectionData + { + public ConnectionId UnderlyingConnection; + public ConnectionToken Token; + public ConnectionState State; + public long LastReceiveTime; + public long LastNonDataSend; + public int ConnectionAttempts; + public Error.DisconnectReason DisconnectReason; + } + + private unsafe struct ControlPacketCommand + { + private const int k_Capacity = k_HandshakeSize; + public ConnectionId Connection; + private fixed byte Data[k_Capacity]; + private int Length; + + public void CopyTo(ref PacketProcessor packetProcessor) + { + fixed(void* dataPtr = Data) + packetProcessor.AppendToPayload(dataPtr, Length); + } + + public ControlPacketCommand(ConnectionId connection, HandshakeType type, ref ConnectionToken token) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (UnsafeUtility.SizeOf() + + UnsafeUtility.SizeOf() + + UnsafeUtility.SizeOf() > k_HandshakeSize || + k_HandshakeSize > k_Capacity) + throw new System.OverflowException(); +#endif + Connection = connection; + fixed(byte* dataPtr = Data) + { + *(uint*)dataPtr = k_ProtocolSignatureAndVersion; + UnsafeUtility.CopyStructureToPtr(ref type, dataPtr + UnsafeUtility.SizeOf()); + UnsafeUtility.CopyStructureToPtr(ref token, dataPtr + UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf()); + } + Length = k_HandshakeSize; + } + + public ControlPacketCommand(ConnectionId connection, MessageType type, ref ConnectionToken token) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (UnsafeUtility.SizeOf() + + UnsafeUtility.SizeOf() > k_HeaderSize || + k_HeaderSize > k_Capacity) + throw new System.OverflowException(); +#endif + Connection = connection; + fixed(byte* dataPtr = Data) + { + UnsafeUtility.CopyStructureToPtr(ref type, dataPtr); + UnsafeUtility.CopyStructureToPtr(ref token, dataPtr + UnsafeUtility.SizeOf()); + } + Length = k_HeaderSize; + } + } + + private ConnectionList m_ConnectionList; + private ConnectionList m_UnderlyingConnectionList; + private ConnectionDataMap m_ConnectionsData; + private NativeList m_ControlCommands; + private NativeParallelHashMap m_TokensHashMap; + private int m_ConnectTimeout; + private int m_DisconnectTimeout; + private int m_HeartbeatTimeout; + private int m_MaxConnectionAttempts; + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + packetPadding += k_HeaderSize; + + var networkConfigParameters = settings.GetNetworkConfigParameters(); + + if (connectionList.IsCreated) + m_UnderlyingConnectionList = connectionList; + + m_ConnectTimeout = networkConfigParameters.connectTimeoutMS; + m_DisconnectTimeout = networkConfigParameters.disconnectTimeoutMS; + m_HeartbeatTimeout = networkConfigParameters.heartbeatTimeoutMS; + m_MaxConnectionAttempts = networkConfigParameters.maxConnectAttempts; + + connectionList = m_ConnectionList = ConnectionList.Create(); + m_ConnectionsData = new ConnectionDataMap(1, default(SimpleConnectionData), Collections.Allocator.Persistent); + m_ControlCommands = new NativeList(Allocator.Persistent); + m_TokensHashMap = new NativeParallelHashMap(1, Allocator.Persistent); + + return 0; + } + + public void Dispose() + { + m_ConnectionList.Dispose(); + m_ConnectionsData.Dispose(); + m_ControlCommands.Dispose(); + m_TokensHashMap.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + if (m_UnderlyingConnectionList.IsCreated) + { + // TODO: revert when MTT-4557 is fixed + // return ScheduleReceive(new ReceiveJob(), new UnderlyingConnectionList(ref m_UnderlyingConnectionList), ref arguments, dependency); + var job = new ReceiveJob2 + { + Connections = m_ConnectionList, + ConnectionsData = m_ConnectionsData, + UnderlyingConnections = new UnderlyingConnectionList(ref m_UnderlyingConnectionList), + ReceiveQueue = arguments.ReceiveQueue, + ControlCommands = m_ControlCommands, + TokensHashMap = m_TokensHashMap, + Time = arguments.Time, + ConnectTimeout = m_ConnectTimeout, + MaxConnectionAttempts = m_MaxConnectionAttempts, + DisconnectTimeout = m_DisconnectTimeout, + HeartbeatTimeout = m_HeartbeatTimeout, + }; + + return job.Schedule(dependency); + } + else + return ScheduleReceive(new ReceiveJob(), default, ref arguments, dependency); + } + + private JobHandle ScheduleReceive(ReceiveJob job, T underlyingConnectionList, ref ReceiveJobArguments arguments, JobHandle dependency) + where T : unmanaged, IUnderlyingConnectionList + { + job.Connections = m_ConnectionList; + job.ConnectionsData = m_ConnectionsData; + job.UnderlyingConnections = underlyingConnectionList; + job.ReceiveQueue = arguments.ReceiveQueue; + job.ControlCommands = m_ControlCommands; + job.TokensHashMap = m_TokensHashMap; + job.Time = arguments.Time; + job.ConnectTimeout = m_ConnectTimeout; + job.MaxConnectionAttempts = m_MaxConnectionAttempts; + job.DisconnectTimeout = m_DisconnectTimeout; + job.HeartbeatTimeout = m_HeartbeatTimeout; + + return job.Schedule(dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new SendJob + { + Connections = m_ConnectionList, + ConnectionsData = m_ConnectionsData, + SendQueue = arguments.SendQueue, + ControlCommands = m_ControlCommands, + Time = arguments.Time, + }.Schedule(dependency); + } + + [BurstCompile] + private struct SendJob : IJob + { + public ConnectionList Connections; + public ConnectionDataMap ConnectionsData; + public PacketsQueue SendQueue; + public NativeList ControlCommands; + public long Time; + + public void Execute() + { + // Process all data messages + var count = SendQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = SendQueue[i]; + if (packetProcessor.Length == 0) + continue; + + var connection = packetProcessor.ConnectionRef; + var connectionData = ConnectionsData[connection]; + var connectionToken = connectionData.Token; + + packetProcessor.PrependToPayload(connectionToken); + packetProcessor.PrependToPayload((byte)MessageType.Data); + + packetProcessor.ConnectionRef = connectionData.UnderlyingConnection; + } + + // Send all control messages + var controlCommandsCount = ControlCommands.Length; + for (int i = 0; i < controlCommandsCount; i++) + SendControlCommand(ref ControlCommands.ElementAt(i)); + ControlCommands.Clear(); + } + + private void SendControlCommand(ref ControlPacketCommand controlCommand) + { + if (SendQueue.EnqueuePacket(out var packetProcessor)) + { + packetProcessor.EndpointRef = Connections.GetConnectionEndpoint(controlCommand.Connection); + + controlCommand.CopyTo(ref packetProcessor); + + var connectionData = ConnectionsData[controlCommand.Connection]; + connectionData.LastNonDataSend = Time; + ConnectionsData[controlCommand.Connection] = connectionData; + + packetProcessor.ConnectionRef = connectionData.UnderlyingConnection; + } + } + } + + [BurstCompile] + private struct ReceiveJob : IJob where T : unmanaged, IUnderlyingConnectionList + { + public ConnectionList Connections; + public ConnectionDataMap ConnectionsData; + public T UnderlyingConnections; + public PacketsQueue ReceiveQueue; + public NativeList ControlCommands; + public NativeParallelHashMap TokensHashMap; + public long Time; + public int ConnectTimeout; + public int DisconnectTimeout; + public int HeartbeatTimeout; + public int MaxConnectionAttempts; + + public void Execute() + { + ProcessReceivedMessages(); + ProcessConnectionStates(); + } + + private void ProcessConnectionStates() + { + // Disconnect if underlying connection is disconnected. + var underlyingDisconnections = UnderlyingConnections.QueryFinishedDisconnections(Allocator.Temp); + var count = underlyingDisconnections.Length; + for (int i = 0; i < count; i++) + { + var disconnection = underlyingDisconnections[i]; + var connectionId = FindConnectionByUnderlyingConnection(ref disconnection.Connection); + var connectionState = Connections.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected || + connectionState == NetworkConnection.State.Disconnecting) + { + continue; + } + + var connectionData = ConnectionsData[connectionId]; + + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = disconnection.Reason; + connectionData.State = ConnectionState.Default; + ConnectionsData[connectionId] = connectionData; + } + + count = Connections.Count; + for (int i = 0; i < count; i++) + { + var connectionId = Connections.ConnectionAt(i); + var connectionState = Connections.GetConnectionState(connectionId); + + switch (connectionState) + { + case NetworkConnection.State.Disconnecting: + SendDisconnectMessageIfRequired(ref connectionId); + ProcessDisconnecting(ref connectionId); + break; + case NetworkConnection.State.Connecting: + ProcessConnecting(ref connectionId); + break; + case NetworkConnection.State.Connected: + ProcessConnected(ref connectionId); + break; + } + } + } + + private void SendDisconnectMessageIfRequired(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + if (connectionData.State == ConnectionState.Established && connectionData.DisconnectReason != Error.DisconnectReason.ClosedByRemote) + { + // The disconnection was requested but no disconnection message has been sent yet. + ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Disconnect, ref connectionData.Token)); + + connectionData.State = ConnectionState.DisconnectionSent; + ConnectionsData[connectionId] = connectionData; + } + } + + private void ProcessDisconnecting(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + // If the underlying connection can be disconnected then we can clean the connection data + // at this protocol level so it can be then completed in the connection list. + if (UnderlyingConnections.TryDisconnect(ref connectionData.UnderlyingConnection)) + { + Connections.FinishDisconnecting(ref connectionId, connectionData.DisconnectReason); + + TokensHashMap.Remove(connectionData.Token); + } + } + + private void ProcessConnecting(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + var connectionState = connectionData.State; + + if (connectionState == ConnectionState.Default) + { + var endpoint = Connections.GetConnectionEndpoint(connectionId); + + if (UnderlyingConnections.TryConnect(ref endpoint, ref connectionData.UnderlyingConnection)) + { + // The connection was just created, we need to initialize it. + connectionData.State = ConnectionState.AwaitingAccept; + connectionData.Token = RandomHelpers.GetRandomConnectionToken(); + connectionData.LastNonDataSend = Time; + connectionData.ConnectionAttempts++; + + TokensHashMap.Add(connectionData.Token, connectionId); + + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionRequest, ref connectionData.Token)); + + ConnectionsData[connectionId] = connectionData; + return; + } + } + + // Check for connect timeout and connection attempts. + // Note that while connecting, LastNonDataSend can only track connection requests. + if (Time - connectionData.LastNonDataSend > ConnectTimeout) + { + if (connectionData.ConnectionAttempts >= MaxConnectionAttempts) + { + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.MaxConnectionAttempts; + + ConnectionsData[connectionId] = connectionData; + + ProcessDisconnecting(ref connectionId); + } + else + { + connectionData.ConnectionAttempts++; + connectionData.LastNonDataSend = Time; + + ConnectionsData[connectionId] = connectionData; + + // Send connect request only if underlying connection has been fully established. + if (connectionState == ConnectionState.AwaitingAccept) + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionRequest, ref connectionData.Token)); + } + } + } + + private void ProcessConnected(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + // Check for the disconnect timeout. + if (Time - connectionData.LastReceiveTime > DisconnectTimeout) + { + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.Timeout; + ConnectionsData[connectionId] = connectionData; + + SendDisconnectMessageIfRequired(ref connectionId); + ProcessDisconnecting(ref connectionId); + } + + // Check for the heartbeat timeout. + if (HeartbeatTimeout > 0 && + Time - connectionData.LastReceiveTime > HeartbeatTimeout && + Time - connectionData.LastNonDataSend > HeartbeatTimeout) + { + ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Ping, ref connectionData.Token)); + } + } + + private void ProcessReceivedMessages() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + if (packetProcessor.Length == 0) + continue; + + if (ProcessHandshakeReceive(ref packetProcessor)) + { + packetProcessor.Drop(); + continue; + } + + if (packetProcessor.Length < k_HeaderSize) + { + packetProcessor.Drop(); + continue; + } + + var messageType = (MessageType)packetProcessor.RemoveFromPayloadStart(); + var connectionToken = packetProcessor.RemoveFromPayloadStart(); + var connectionId = FindConnectionByToken(ref connectionToken); + + if (!connectionId.IsCreated || Connections.GetConnectionState(connectionId) != NetworkConnection.State.Connected) + { + packetProcessor.Drop(); + continue; + } + + switch (messageType) + { + case MessageType.Disconnect: + { + var connectionData = ConnectionsData[connectionId]; + + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.ClosedByRemote; + ConnectionsData[connectionId] = connectionData; + + ProcessDisconnecting(ref connectionId); + + packetProcessor.Drop(); + break; + } + case MessageType.Data: + { + PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef); + 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: + { + PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef); + packetProcessor.Drop(); + break; + } + default: + UnityEngine.Debug.LogWarning(string.Format("Received message with type {0} was not processed", (byte)messageType)); + packetProcessor.Drop(); + break; + } + } + } + + private void PreprocessMessage(ref ConnectionId connectionId, ref NetworkEndpoint endpoint) + { + var connectionData = ConnectionsData[connectionId]; + + // Update the endpoint for reconnection, but only if the connection was previously + // fully establilshed. + if (connectionData.State == ConnectionState.Established) + Connections.UpdateConnectionAddress(ref connectionId, ref endpoint); + + // Any valid message updates last receive time + connectionData.LastReceiveTime = Time; + + ConnectionsData[connectionId] = connectionData; + } + + private ConnectionId FindConnectionByToken(ref ConnectionToken token) + { + if (TokensHashMap.TryGetValue(token, out var connectionId)) + return connectionId; + + return default; + } + + private ConnectionId FindConnectionByUnderlyingConnection(ref ConnectionId underlyingConnection) + { + var count = ConnectionsData.Length; + for (int i = 0; i < count; i++) + { + var connectionData = ConnectionsData.DataAt(i); + if (connectionData.UnderlyingConnection == underlyingConnection) + return ConnectionsData.ConnectionAt(i); + } + + return default; + } + + private bool ProcessHandshakeReceive(ref PacketProcessor packetProcessor) + { + if (packetProcessor.Length != SimpleConnectionLayer.k_HandshakeSize) + return false; + + if ((packetProcessor.GetPayloadDataRef(0) & 0xFFFFFF00) == (k_ProtocolSignatureAndVersion & 0xFFFFFF00)) + { + var protocolVersion = packetProcessor.GetPayloadDataRef(3); + if (protocolVersion != k_ProtocolVersion) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogWarning( + string.Format("Simple Connection Protocol version mismatch. This could happen if remote connection UTP version is different. (local: {0}, remote: {1})", + k_ProtocolVersion, protocolVersion)); +#endif + return true; + } + + var handshakeSeq = (HandshakeType)packetProcessor.GetPayloadDataRef(4); + var connectionToken = packetProcessor.GetPayloadDataRef(5); + var connectionId = FindConnectionByToken(ref connectionToken); + var connectionData = ConnectionsData[connectionId]; + switch (handshakeSeq) + { + case HandshakeType.ConnectionRequest: + { + // Received a duplicated connection request. Which is OK as client could be retrying. + 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); + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken)); + break; + } + + case HandshakeType.ConnectionAccept: + { + if (connectionId.IsCreated && + connectionData.State == ConnectionState.AwaitingAccept) + { + connectionData.State = ConnectionState.Established; + Connections.FinishConnectingFromLocal(ref connectionId); + } + else + { + // Received a connection accept for an unknown connection + return true; + } + + break; + } + + // We got a malformed packet + default: + return true; + } + + connectionData.LastReceiveTime = Time; + ConnectionsData[connectionId] = connectionData; + return true; + } + + return false; + } + } + + // TODO: MTT-4557. There seems to be a bug in DOTS Runtime about where the NativeQueue in the UnderlyingConnection list + // does not get its safety handle properly set up if this job uses generics. Duplicating it seems to fix it for now + [BurstCompile] + private struct ReceiveJob2 : IJob + { + public ConnectionList Connections; + public ConnectionDataMap ConnectionsData; + public UnderlyingConnectionList UnderlyingConnections; + public PacketsQueue ReceiveQueue; + public NativeList ControlCommands; + public NativeParallelHashMap TokensHashMap; + public long Time; + public int ConnectTimeout; + public int DisconnectTimeout; + public int HeartbeatTimeout; + public int MaxConnectionAttempts; + + public void Execute() + { + ProcessReceivedMessages(); + ProcessConnectionStates(); + } + + private void ProcessConnectionStates() + { + // Disconnect if underlying connection is disconnected. + var underlyingDisconnections = UnderlyingConnections.QueryFinishedDisconnections(Allocator.Temp); + var count = underlyingDisconnections.Length; + for (int i = 0; i < count; i++) + { + var disconnection = underlyingDisconnections[i]; + var connectionId = FindConnectionByUnderlyingConnection(ref disconnection.Connection); + var connectionState = Connections.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected || + connectionState == NetworkConnection.State.Disconnecting) + { + continue; + } + + var connectionData = ConnectionsData[connectionId]; + + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = disconnection.Reason; + connectionData.State = ConnectionState.Default; + ConnectionsData[connectionId] = connectionData; + } + + count = Connections.Count; + for (int i = 0; i < count; i++) + { + var connectionId = Connections.ConnectionAt(i); + var connectionState = Connections.GetConnectionState(connectionId); + + switch (connectionState) + { + case NetworkConnection.State.Disconnecting: + SendDisconnectMessageIfRequired(ref connectionId); + ProcessDisconnecting(ref connectionId); + break; + case NetworkConnection.State.Connecting: + ProcessConnecting(ref connectionId); + break; + case NetworkConnection.State.Connected: + ProcessConnected(ref connectionId); + break; + } + } + } + + private void SendDisconnectMessageIfRequired(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + if (connectionData.State == ConnectionState.Established && connectionData.DisconnectReason != Error.DisconnectReason.ClosedByRemote) + { + // The disconnection was requested but no disconnection message has been sent yet. + ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Disconnect, ref connectionData.Token)); + + connectionData.State = ConnectionState.DisconnectionSent; + ConnectionsData[connectionId] = connectionData; + } + } + + private void ProcessDisconnecting(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + // If the underlying connection can be disconnected then we can clean the connection data + // at this protocol level so it can be then completed in the connection list. + if (UnderlyingConnections.TryDisconnect(ref connectionData.UnderlyingConnection)) + { + Connections.FinishDisconnecting(ref connectionId, connectionData.DisconnectReason); + + TokensHashMap.Remove(connectionData.Token); + } + } + + private void ProcessConnecting(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + var connectionState = connectionData.State; + + if (connectionState == ConnectionState.Default) + { + var endpoint = Connections.GetConnectionEndpoint(connectionId); + + if (UnderlyingConnections.TryConnect(ref endpoint, ref connectionData.UnderlyingConnection)) + { + // The connection was just created, we need to initialize it. + connectionData.State = ConnectionState.AwaitingAccept; + connectionData.Token = RandomHelpers.GetRandomConnectionToken(); + connectionData.LastNonDataSend = Time; + connectionData.ConnectionAttempts++; + + TokensHashMap.Add(connectionData.Token, connectionId); + + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionRequest, ref connectionData.Token)); + + ConnectionsData[connectionId] = connectionData; + return; + } + } + + // Check for connect timeout and connection attempts. + // Note that while connecting, LastNonDataSend can only track connection requests. + if (Time - connectionData.LastNonDataSend > ConnectTimeout) + { + if (connectionData.ConnectionAttempts >= MaxConnectionAttempts) + { + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.MaxConnectionAttempts; + + ConnectionsData[connectionId] = connectionData; + + ProcessDisconnecting(ref connectionId); + } + else + { + connectionData.ConnectionAttempts++; + connectionData.LastNonDataSend = Time; + + ConnectionsData[connectionId] = connectionData; + + // Send connect request only if underlying connection has been fully established. + if (connectionState == ConnectionState.AwaitingAccept) + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionRequest, ref connectionData.Token)); + } + } + } + + private void ProcessConnected(ref ConnectionId connectionId) + { + var connectionData = ConnectionsData[connectionId]; + + // Check for the disconnect timeout. + if (Time - connectionData.LastReceiveTime > DisconnectTimeout) + { + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.Timeout; + ConnectionsData[connectionId] = connectionData; + + SendDisconnectMessageIfRequired(ref connectionId); + ProcessDisconnecting(ref connectionId); + } + + // Check for the heartbeat timeout. + if (HeartbeatTimeout > 0 && + Time - connectionData.LastReceiveTime > HeartbeatTimeout && + Time - connectionData.LastNonDataSend > HeartbeatTimeout) + { + ControlCommands.Add(new ControlPacketCommand(connectionId, MessageType.Ping, ref connectionData.Token)); + } + } + + private void ProcessReceivedMessages() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + if (packetProcessor.Length == 0) + continue; + + if (ProcessHandshakeReceive(ref packetProcessor)) + { + packetProcessor.Drop(); + continue; + } + + if (packetProcessor.Length < k_HeaderSize) + { + packetProcessor.Drop(); + continue; + } + + var messageType = (MessageType)packetProcessor.RemoveFromPayloadStart(); + var connectionToken = packetProcessor.RemoveFromPayloadStart(); + var connectionId = FindConnectionByToken(ref connectionToken); + + if (!connectionId.IsCreated || Connections.GetConnectionState(connectionId) != NetworkConnection.State.Connected) + { + packetProcessor.Drop(); + continue; + } + + switch (messageType) + { + case MessageType.Disconnect: + { + var connectionData = ConnectionsData[connectionId]; + + Connections.StartDisconnecting(ref connectionId); + connectionData.DisconnectReason = Error.DisconnectReason.ClosedByRemote; + ConnectionsData[connectionId] = connectionData; + + ProcessDisconnecting(ref connectionId); + + packetProcessor.Drop(); + break; + } + case MessageType.Data: + { + PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef); + 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: + { + PreprocessMessage(ref connectionId, ref packetProcessor.EndpointRef); + packetProcessor.Drop(); + break; + } + default: + UnityEngine.Debug.LogWarning(string.Format("Received message with type {0} was not processed", (byte)messageType)); + packetProcessor.Drop(); + break; + } + } + } + + private void PreprocessMessage(ref ConnectionId connectionId, ref NetworkEndpoint endpoint) + { + var connectionData = ConnectionsData[connectionId]; + + // Update the endpoint for reconnection, but only if the connection was previously + // fully establilshed. + if (connectionData.State == ConnectionState.Established) + Connections.UpdateConnectionAddress(ref connectionId, ref endpoint); + + // Any valid message updates last receive time + connectionData.LastReceiveTime = Time; + + ConnectionsData[connectionId] = connectionData; + } + + private ConnectionId FindConnectionByToken(ref ConnectionToken token) + { + if (TokensHashMap.TryGetValue(token, out var connectionId)) + return connectionId; + + return default; + } + + private ConnectionId FindConnectionByUnderlyingConnection(ref ConnectionId underlyingConnection) + { + var count = ConnectionsData.Length; + for (int i = 0; i < count; i++) + { + var connectionData = ConnectionsData.DataAt(i); + if (connectionData.UnderlyingConnection == underlyingConnection) + return ConnectionsData.ConnectionAt(i); + } + + return default; + } + + private bool ProcessHandshakeReceive(ref PacketProcessor packetProcessor) + { + if (packetProcessor.Length != SimpleConnectionLayer.k_HandshakeSize) + return false; + + if ((packetProcessor.GetPayloadDataRef(0) & 0xFFFFFF00) == (k_ProtocolSignatureAndVersion & 0xFFFFFF00)) + { + var protocolVersion = packetProcessor.GetPayloadDataRef(3); + if (protocolVersion != k_ProtocolVersion) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogWarning( + string.Format("Simple Connection Protocol version mismatch. This could happen if remote connection UTP version is different. (local: {0}, remote: {1})", + k_ProtocolVersion, protocolVersion)); +#endif + return true; + } + + var handshakeSeq = (HandshakeType)packetProcessor.GetPayloadDataRef(4); + var connectionToken = packetProcessor.GetPayloadDataRef(5); + var connectionId = FindConnectionByToken(ref connectionToken); + var connectionData = ConnectionsData[connectionId]; + switch (handshakeSeq) + { + case HandshakeType.ConnectionRequest: + { + // Received a duplicated connection request. Which is OK as client could be retrying. + 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); + ControlCommands.Add(new ControlPacketCommand(connectionId, HandshakeType.ConnectionAccept, ref connectionToken)); + break; + } + + case HandshakeType.ConnectionAccept: + { + if (connectionId.IsCreated && + connectionData.State == ConnectionState.AwaitingAccept) + { + connectionData.State = ConnectionState.Established; + Connections.FinishConnectingFromLocal(ref connectionId); + } + else + { + // Received a connection accept for an unknown connection + return true; + } + + break; + } + + // We got a malformed packet + default: + return true; + } + + connectionData.LastReceiveTime = Time; + ConnectionsData[connectionId] = connectionData; + return true; + } + + return false; + } + } + } +} diff --git a/Runtime/Layers/SimpleConnectionLayer.cs.meta b/Runtime/Layers/SimpleConnectionLayer.cs.meta new file mode 100644 index 0000000..df0700d --- /dev/null +++ b/Runtime/Layers/SimpleConnectionLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44ade25446b99406a8cf0047215d4478 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/SimulatorLayer.cs b/Runtime/Layers/SimulatorLayer.cs new file mode 100644 index 0000000..79ce97b --- /dev/null +++ b/Runtime/Layers/SimulatorLayer.cs @@ -0,0 +1,166 @@ +using Unity.Collections; +using Unity.Burst; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Networking.Transport.Utilities; +using UnityEngine; + +namespace Unity.Networking.Transport +{ + using Random = Unity.Mathematics.Random; + + internal struct SimulatorLayer : INetworkLayer + { + // Need to store parameters in a native reference if they are to be modified since + // ModifyNetworkSimulatorParameters() only gets a copy of the layer structure. + private NativeReference m_Parameters; + + public NetworkSimulatorParameter Parameters + { + private get => m_Parameters.Value; + set => m_Parameters.Value = value; + } + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + // Can ignore the result of TryGet since simulator layer is only added if it's true. + settings.TryGet(out var parameters); + + m_Parameters = new NativeReference(parameters, Allocator.Persistent); + + return 0; + } + + public void Dispose() + { + m_Parameters.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + if (Parameters.ReceivePacketLossPercent == 0.0f) + return dependency; + + return new SimulatorJob + { + Packets = arguments.ReceiveQueue, + PacketLoss = Parameters.ReceivePacketLossPercent, + }.Schedule(dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + if (Parameters.SendPacketLossPercent == 0.0f) + return dependency; + + return new SimulatorJob + { + Packets = arguments.SendQueue, + PacketLoss = Parameters.SendPacketLossPercent, + }.Schedule(dependency); + } + + [BurstCompile] + private struct SimulatorJob : IJob + { + public PacketsQueue Packets; + public float PacketLoss; + + public void Execute() + { + var random = new Random((uint)TimerHelpers.GetTicks()); + + var count = Packets.Count; + for (int i = 0; i < count; i++) + { + if (random.NextFloat(100.0f) < PacketLoss) + Packets[i].Drop(); + } + } + } + } + + /// Parameters for the global network simulator. + /// + /// These parameters are for the global network simulator, which applies to all traffic going + /// through a (including control traffic). For the parameters of + /// , refer to . + /// + /// We recommend using to simulate network conditions as + /// it has more features than the global one (which is only intended for specialized use cases). + /// + public struct NetworkSimulatorParameter : INetworkParameter + { + /// Percentage of received packets to drop (0-100). + public float ReceivePacketLossPercent; + /// Percentage of sent packets to drop (0-100). + public float SendPacketLossPercent; + + /// Validate the network simulator parameters. + public bool Validate() + { + if (ReceivePacketLossPercent < 0.0f || ReceivePacketLossPercent > 100.0f) + { + Debug.LogError($"{nameof(ReceivePacketLossPercent)} value ({ReceivePacketLossPercent}) must be between 0 and 100."); + return false; + } + + if (SendPacketLossPercent < 0.0f || SendPacketLossPercent > 100.0f) + { + Debug.LogError($"{nameof(SendPacketLossPercent)} value ({SendPacketLossPercent}) must be between 0 and 100."); + return false; + } + + return true; + } + } + + public static class NetworkSimulatorParameterExtensions + { + /// Set the global network simulator parameters. + /// + /// This is not the recommended way of configuring simulated network conditions. See + /// for details. + /// + /// Percentage of received packets to drop. + /// Percentage of sent packets to drop. + public static ref NetworkSettings WithNetworkSimulatorParameters( + ref this NetworkSettings settings, + float receivePacketLossPercent = 0.0f, + float sendPacketLossPercent = 0.0f) + { + var parameters = new NetworkSimulatorParameter + { + ReceivePacketLossPercent = receivePacketLossPercent, + SendPacketLossPercent = sendPacketLossPercent, + }; + + settings.AddRawParameterStruct(ref parameters); + return ref settings; + } + + // TODO This ModifyNetworkSimulatorParameters() extension method is NOT a pattern we want + // repeated throughout the code. At some point we'll want to deprecate it and replace + // it with a proper general mechanism to modify settings at runtime (see MTT-4161). + + /// Modify the parameters of the global network simulator. + /// New parameters for the simulator. + public static void ModifyNetworkSimulatorParameters(this NetworkDriver driver, NetworkSimulatorParameter newParams) + { + if (!driver.m_NetworkStack.TryGetLayer(out var layer)) + { + Debug.LogError("Network simulator not available. Driver must have been configured with " + + "NetworkSettings.WithNetworkSimulatorParameters for network simulator to be available."); + } + else if (!newParams.Validate()) + { + Debug.LogError("Modified network simulator parameters are invalid and were not applied."); + } + else + { + layer.Parameters = newParams; + driver.m_NetworkSettings.AddRawParameterStruct(ref newParams); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Layers/SimulatorLayer.cs.meta b/Runtime/Layers/SimulatorLayer.cs.meta new file mode 100644 index 0000000..2991ddf --- /dev/null +++ b/Runtime/Layers/SimulatorLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9ff1d18118a4974ca794656aa6141f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/StreamToDatagramLayer.cs b/Runtime/Layers/StreamToDatagramLayer.cs new file mode 100644 index 0000000..4c934ab --- /dev/null +++ b/Runtime/Layers/StreamToDatagramLayer.cs @@ -0,0 +1,187 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + /// + /// A simple layer to provide message segmentation. + /// + /// + /// This layer takes care of segmenting messages over stream-oriented layers at the cost of a minimum overhead + /// (only 2 bytes per message). Stream oriented layers such as and the + /// cannot offer the guarantee that a packet in the receive queue corresponds to exactly one + /// message and thus require explicit segmentation. This layer should work correctly over datagram oriented layers + /// but its use then would be redundant and only waste packet space since datagram oriented layers provide message + /// segmentation for free. This layer is also unnecessary where there are already other layers in the stack that + /// can provide message segmentation such as the . + /// + internal struct StreamToDatagramLayer : INetworkLayer + { + const int k_HeaderSize = sizeof(ushort); + + // Maps a connection id from the connection list to its connection data. + private ConnectionDataMap m_ConnectionMap; + + public unsafe struct StreamToDatagramLayerPacketBuffer + { + public const int Capacity = 2 * NetworkParameterConstants.MTU; + + public fixed byte Data[Capacity]; + public int Length; + } + + unsafe struct ConnectionData + { + public StreamToDatagramLayerPacketBuffer RecvBuffer; + public int ReceiveIgnore; + } + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + packetPadding += k_HeaderSize; + m_ConnectionMap = new ConnectionDataMap(1, default, Allocator.Persistent); + + return 0; + } + + public void Dispose() + { + m_ConnectionMap.Dispose(); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + { + return new SendJob + { + SendQueue = arguments.SendQueue, + }.Schedule(dep); + } + + [BurstCompile] + struct SendJob : IJob + { + public PacketsQueue SendQueue; + + static ushort HostToNetwork(ushort value) + => (ushort)(BitConverter.IsLittleEndian ? ((value & 0xFF) << 8) | ((value >> 8) & 0xFF) : value); + + public void Execute() + { + // Process all data messages + var count = SendQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = SendQueue[i]; + // Don't send empty packets or packets larger than we can receive on the other side. + if (packetProcessor.Length == 0 || (ushort)packetProcessor.Length > (NetworkParameterConstants.MTU - k_HeaderSize)) + { + packetProcessor.Drop(); + continue; + } + + var msglen = HostToNetwork((ushort)packetProcessor.Length); + packetProcessor.PrependToPayload(msglen); + } + } + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + { + return new ReceiveJob + { + ReceiveQueue = arguments.ReceiveQueue, + ConnectionMap = m_ConnectionMap, + }.Schedule(dep); + } + + [BurstCompile] + unsafe struct ReceiveJob : IJob + { + public PacketsQueue ReceiveQueue; + public ConnectionDataMap ConnectionMap; + + public void Execute() + { + // Process all data messages + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + if (packetProcessor.Length == 0 || packetProcessor.Length > NetworkParameterConstants.MTU) + { + packetProcessor.Drop(); + continue; + } + + var connectionId = packetProcessor.ConnectionRef; + var connectionData = ConnectionMap[connectionId]; + + // Determine if we're ignoring incoming data from an excessively large message + var ignored = Math.Max(0, Math.Min(connectionData.ReceiveIgnore, packetProcessor.Length)); + // There is data beyond the ignored point copy in the packet buffer + if (ignored < packetProcessor.Length) + { + var nbytes = packetProcessor.Length - ignored; + UnsafeUtility.MemCpy(connectionData.RecvBuffer.Data + connectionData.RecvBuffer.Length, (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset + ignored, nbytes); + connectionData.RecvBuffer.Length += nbytes; + } + + connectionData.ReceiveIgnore -= ignored; + + // At this point connectionData.ReceivePacketBuffer.Length is > 0 only if + // connectionData.ReceiveIgnore == 0, in other words, if we're not ignoring anything anymore. + var total = connectionData.RecvBuffer.Length; + var start = 0; + while (total >= k_HeaderSize) + { + // Try to pack a message + var msglen = (ushort)(((connectionData.RecvBuffer.Data[start] & 0xFF) << 8) + (connectionData.RecvBuffer.Data[start + 1] & 0xFF)); + total -= k_HeaderSize; + + // If incoming message is too large, just ignore + if (msglen > NetworkParameterConstants.MTU - k_HeaderSize) + { + connectionData.ReceiveIgnore = Math.Max(0, msglen - total); + total = Math.Max(0, total - msglen); + start += k_HeaderSize + msglen; + } + else if (msglen == 0) // if message is empty there is nothing to do but advance in the buffer + { + // Skip the msg size + start += k_HeaderSize; + } + else if (msglen <= total) + { + // Skip the msg size + start += k_HeaderSize; + + if (ReceiveQueue.EnqueuePacket(out var newPacketProcessor)) + { + newPacketProcessor.ConnectionRef = packetProcessor.ConnectionRef; + newPacketProcessor.EndpointRef = packetProcessor.EndpointRef; + newPacketProcessor.AppendToPayload(connectionData.RecvBuffer.Data + start, msglen); + } + + total -= msglen; + start += msglen; + } + } + + // Move data to the beginning of the buffer if needed + if (start > 0 && start < connectionData.RecvBuffer.Length) + UnsafeUtility.MemMove(connectionData.RecvBuffer.Data, connectionData.RecvBuffer.Data + start, connectionData.RecvBuffer.Length - start); + + // Update the buffer length. It could have been partially consumed (start < Length), totally + // consumed (start == Length) or not consumed at all (start == 0). + connectionData.RecvBuffer.Length -= start; + + ConnectionMap[connectionId] = connectionData; + packetProcessor.Drop(); + } + } + } + } +} diff --git a/Runtime/Layers/StreamToDatagramLayer.cs.meta b/Runtime/Layers/StreamToDatagramLayer.cs.meta new file mode 100644 index 0000000..623efa6 --- /dev/null +++ b/Runtime/Layers/StreamToDatagramLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d39494ec926e2564d8c107949d812e7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/TLSLayer.cs b/Runtime/Layers/TLSLayer.cs new file mode 100644 index 0000000..a159e44 --- /dev/null +++ b/Runtime/Layers/TLSLayer.cs @@ -0,0 +1,500 @@ +#if ENABLE_MANAGED_UNITYTLS + +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.TLS; +using Unity.TLS.LowLevel; +using UnityEngine; + +namespace Unity.Networking.Transport +{ + internal unsafe struct TLSLayer : 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. Although + // unlike with DTLS, we likely don't want to decrease it too much since with TLS the + // certificate chains could be much larger and require more deferred sends. + private const int k_DeferredSendsQueueSize = 64; + + // Padding used by TLS records. Since UnityTLS creates a record everytime we call + // unitytls_client_send_data, each packet in the send queue must be ready to accomodate the + // overhead of a TLS record. The overhead is 5 bytes for the record header, 32 bytes for the + // MAC (typically 20 bytes, but can be 32 in TLS 1.2), and up to 31 bytes to pad to the + // block size (for block ciphers, which are commonly 16 bytes but can be up to 32 for the + // ciphers supported by UnityTLS). + // + // TODO See if we can limit what UnityTLS is willing to support in terms of ciphers so that + // we can reduce this value (we should normally be fine with a value of 40). + private const int k_TLSPadding = 5 + 32 + 31; + + private struct TLSConnectionData + { + [NativeDisableUnsafePtrRestriction] + public Binding.unitytls_client* UnityTLSClientPtr; + + public ConnectionId UnderlyingConnection; + public Error.DisconnectReason DisconnectReason; + + // Used to delete old half-open connections. + public long LastHandshakeUpdate; + } + + internal ConnectionList m_ConnectionList; + private ConnectionList m_UnderlyingConnectionList; + private ConnectionDataMap m_ConnectionsData; + private NativeParallelHashMap m_UnderlyingIdToCurrentIdMap; + private UnityTLSConfiguration m_UnityTLSConfiguration; + private PacketsQueue m_DeferredSends; + private long m_HalfOpenDisconnectTimeout; + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!connectionList.IsCreated) + throw new InvalidOperationException("TLS layer expects to have an underlying connection list."); +#endif + m_UnderlyingConnectionList = connectionList; + connectionList = m_ConnectionList = ConnectionList.Create(); + m_ConnectionsData = new ConnectionDataMap(1, default(TLSConnectionData), Allocator.Persistent); + m_UnderlyingIdToCurrentIdMap = new NativeParallelHashMap(1, Allocator.Persistent); + m_UnityTLSConfiguration = new UnityTLSConfiguration(ref settings, SecureTransportProtocol.TLS); + m_DeferredSends = new PacketsQueue(k_DeferredSendsQueueSize); + + 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; + + packetPadding += k_TLSPadding; + + return 0; + } + + public void Dispose() + { + // Destroy any remaining UnityTLS clients (their memory is managed by UnityTLS). + for (int i = 0; i < m_ConnectionsData.Length; i++) + { + var data = m_ConnectionsData.DataAt(i); + if (data.UnityTLSClientPtr != null) + Binding.unitytls_client_destroy(data.UnityTLSClientPtr); + } + + m_ConnectionList.Dispose(); + m_ConnectionsData.Dispose(); + m_UnderlyingIdToCurrentIdMap.Dispose(); + m_UnityTLSConfiguration.Dispose(); + m_DeferredSends.Dispose(); + } + + [BurstCompile] + private struct ReceiveJob : IJob + { + public ConnectionList Connections; + public ConnectionList UnderlyingConnections; + public ConnectionDataMap ConnectionsData; + public NativeParallelHashMap UnderlyingIdToCurrentId; + public PacketsQueue DeferredSends; + public PacketsQueue ReceiveQueue; + public long Time; + public long HalfOpenDisconnectTimeout; + [NativeDisableUnsafePtrRestriction] + public Binding.unitytls_client_config* UnityTLSConfig; + [NativeDisableUnsafePtrRestriction] + public UnityTLSCallbacks.CallbackContext* UnityTLSCallbackContext; + + public void Execute() + { + UnityTLSCallbackContext->ReceivedPacket = default; + UnityTLSCallbackContext->SendQueue = DeferredSends; + UnityTLSCallbackContext->SendQueueIndex = -1; + UnityTLSCallbackContext->PacketPadding = 0; + + ProcessReceivedMessages(); + ProcessUnderlyingConnectionList(); + ProcessConnectionList(); + } + + private void ProcessReceivedMessages() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + if (packetProcessor.Length == 0) + continue; + + UnityTLSCallbackContext->ReceivedPacket = packetProcessor; + + // If we don't know about the underlying connection, then assume this is the + // first message from a client (a client hello). If it turns out it isn't, then + // the UnityTLS client will just fail, eventually causing a disconnection. + if (!UnderlyingIdToCurrentId.TryGetValue(packetProcessor.ConnectionRef, out var connectionId)) + { + connectionId = ProcessClientHello(ref packetProcessor); + // Don't drop the packet, handshake check below will cover that. + } + + packetProcessor.ConnectionRef = connectionId; + + // If in initial or handshake state, process everything as a handshake message. + var clientPtr = ConnectionsData[connectionId].UnityTLSClientPtr; + var clientState = Binding.unitytls_client_get_state(clientPtr); + if (clientState == Binding.UnityTLSClientState_Init || clientState == Binding.UnityTLSClientState_Handshake) + { + ProcessHandshakeMessage(ref packetProcessor); + + // TODO Can this even happen? If so, need to handle this better. + if (packetProcessor.Length > 0) + Debug.LogError("TLS handshake message was not entirely processed. Dropping leftovers."); + + continue; + } + + // If we get here then we must be dealing with an actual data message. + ProcessDataMessage(ref packetProcessor); + } + } + + private ConnectionId ProcessClientHello(ref PacketProcessor packetProcessor) + { + var connectionId = Connections.StartConnecting(ref packetProcessor.EndpointRef); + UnderlyingIdToCurrentId.Add(packetProcessor.ConnectionRef, connectionId); + + var clientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Server, UnityTLSConfig); + Binding.unitytls_client_init(clientPtr); + + ConnectionsData[connectionId] = new TLSConnectionData + { + UnityTLSClientPtr = clientPtr, + UnderlyingConnection = packetProcessor.ConnectionRef, + }; + + return connectionId; + } + + private void ProcessHandshakeMessage(ref PacketProcessor packetProcessor) + { + var connectionId = packetProcessor.ConnectionRef; + var data = ConnectionsData[connectionId]; + + UnityTLSCallbackContext->NewPacketsEndpoint = packetProcessor.EndpointRef; + UnityTLSCallbackContext->NewPacketsConnection = data.UnderlyingConnection; + + AdvanceHandshake(data.UnityTLSClientPtr); + + // Update the last handshake update time. + data.LastHandshakeUpdate = Time; + ConnectionsData[connectionId] = data; + + // Check if the handshake is over. + var clientState = Binding.unitytls_client_get_state(data.UnityTLSClientPtr); + if (clientState == Binding.UnityTLSClientState_Messaging) + { + var role = Binding.unitytls_client_get_role(data.UnityTLSClientPtr); + if (role == Binding.UnityTLSRole_Client) + Connections.FinishConnectingFromLocal(ref connectionId); + else + Connections.FinishConnectingFromRemote(ref connectionId); + } + } + + private void ProcessDataMessage(ref PacketProcessor packetProcessor) + { + var connectionId = packetProcessor.ConnectionRef; + var originalPacketOffset = packetProcessor.Offset; + + // 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. + 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; + + var decryptedLength = new UIntPtr(); + var result = Binding.unitytls_client_read_data(ConnectionsData[connectionId].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) + { + // 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. + break; + } + + tempBufferLength += (int)decryptedLength.ToUInt32(); + } + + packetProcessor.SetUnsafeMetadata(0, originalPacketOffset); + packetProcessor.AppendToPayload(tempBuffer.GetUnsafePtr(), tempBufferLength); + } + + private void ProcessUnderlyingConnectionList() + { + // The only thing we care about in the underlying connection list is disconnections. + var disconnects = UnderlyingConnections.QueryFinishedDisconnections(Allocator.Temp); + + var count = disconnects.Length; + for (int i = 0; i < count; i++) + { + var underlyingConnectionId = disconnects[i].Connection; + + // Shouldn't really happen. If it does, it means we didn't even know about the + // underlying connection, so the best thing to do is just ignore it. + if (!UnderlyingIdToCurrentId.TryGetValue(underlyingConnectionId, out var connectionId)) + continue; + + // If our connection is not disconnecting, then it means the layer below + // triggered the disconnection on its own, so start disconnecting. + if (Connections.GetConnectionState(connectionId) != NetworkConnection.State.Disconnecting) + Connections.StartDisconnecting(ref connectionId); + + Disconnect(connectionId, disconnects[i].Reason); + } + } + + private void ProcessConnectionList() + { + var count = Connections.Count; + for (int i = 0; i < count; i++) + { + var connectionId = Connections.ConnectionAt(i); + var connectionState = Connections.GetConnectionState(connectionId); + + switch (connectionState) + { + case NetworkConnection.State.Connecting: + HandleConnectingState(connectionId); + break; + case NetworkConnection.State.Disconnecting: + HandleDisconnectingState(connectionId); + break; + } + + CheckForFailedClient(connectionId); + CheckForHalfOpenConnection(connectionId); + } + } + + private void HandleConnectingState(ConnectionId connection) + { + var endpoint = Connections.GetConnectionEndpoint(connection); + var underlyingId = ConnectionsData[connection].UnderlyingConnection; + + // First we hear of this connection, start connecting on the underlying layer and + // create the UnityTLS context (we won't use it now, but it'll be there already). + if (underlyingId == default) + { + underlyingId = UnderlyingConnections.StartConnecting(ref endpoint); + UnderlyingIdToCurrentId.Add(underlyingId, connection); + + var clientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Client, UnityTLSConfig); + Binding.unitytls_client_init(clientPtr); + + ConnectionsData[connection] = new TLSConnectionData + { + UnityTLSClientPtr = clientPtr, + UnderlyingConnection = underlyingId, + LastHandshakeUpdate = Time, + }; + } + + // Make progress on the handshake if underlying connection is completed. + if (UnderlyingConnections.GetConnectionState(underlyingId) == NetworkConnection.State.Connected) + { + UnityTLSCallbackContext->NewPacketsEndpoint = endpoint; + UnityTLSCallbackContext->NewPacketsConnection = underlyingId; + AdvanceHandshake(ConnectionsData[connection].UnityTLSClientPtr); + } + } + + private void HandleDisconnectingState(ConnectionId connection) + { + var underlyingId = ConnectionsData[connection].UnderlyingConnection; + var underlyingState = UnderlyingConnections.GetConnectionState(underlyingId); + + if (underlyingState == NetworkConnection.State.Disconnected) + { + Disconnect(connection, ConnectionsData[connection].DisconnectReason); + } + else if (underlyingState != NetworkConnection.State.Disconnecting) + { + UnderlyingConnections.StartDisconnecting(ref underlyingId); + } + } + + private void CheckForFailedClient(ConnectionId connection) + { + var data = ConnectionsData[connection]; + + if (data.UnityTLSClientPtr == null) + return; + + if (Binding.unitytls_client_get_state(data.UnityTLSClientPtr) == Binding.UnityTLSClientState_Fail) + { + UnderlyingConnections.StartDisconnecting(ref data.UnderlyingConnection); + Connections.StartDisconnecting(ref connection); + + // TODO Not the ideal disconnect reason. If we ever have a better one, use it. + data.DisconnectReason = Error.DisconnectReason.ClosedByRemote; + ConnectionsData[connection] = data; + } + } + + private void CheckForHalfOpenConnection(ConnectionId connection) + { + var data = ConnectionsData[connection]; + + if (data.UnityTLSClientPtr == null) + return; + + var clientState = Binding.unitytls_client_get_state(data.UnityTLSClientPtr); + if (clientState != Binding.UnityTLSClientState_Init && clientState != Binding.UnityTLSClientState_Handshake) + return; + + if (Time - data.LastHandshakeUpdate > HalfOpenDisconnectTimeout) + { + UnderlyingConnections.StartDisconnecting(ref data.UnderlyingConnection); + Connections.StartDisconnecting(ref connection); + + data.DisconnectReason = Error.DisconnectReason.Timeout; + ConnectionsData[connection] = data; + } + } + + private void AdvanceHandshake(Binding.unitytls_client* clientPtr) + { + while (Binding.unitytls_client_handshake(clientPtr) == Binding.UNITYTLS_HANDSHAKE_STEP); + } + + private void Disconnect(ConnectionId connection, Error.DisconnectReason reason) + { + var data = ConnectionsData[connection]; + if (data.UnityTLSClientPtr != null) + Binding.unitytls_client_destroy(data.UnityTLSClientPtr); + + UnderlyingIdToCurrentId.Remove(data.UnderlyingConnection); + ConnectionsData.ClearData(ref connection); + Connections.FinishDisconnecting(ref connection, reason); + } + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + return new ReceiveJob + { + Connections = m_ConnectionList, + UnderlyingConnections = m_UnderlyingConnectionList, + ConnectionsData = m_ConnectionsData, + UnderlyingIdToCurrentId = m_UnderlyingIdToCurrentIdMap, + DeferredSends = m_DeferredSends, + ReceiveQueue = arguments.ReceiveQueue, + Time = arguments.Time, + HalfOpenDisconnectTimeout = m_HalfOpenDisconnectTimeout, + UnityTLSConfig = m_UnityTLSConfiguration.ConfigPtr, + UnityTLSCallbackContext = m_UnityTLSConfiguration.CallbackContextPtr, + }.Schedule(dependency); + } + + [BurstCompile] + private struct SendJob : IJob + { + public ConnectionDataMap ConnectionsData; + public PacketsQueue SendQueue; + public PacketsQueue DeferredSends; + [NativeDisableUnsafePtrRestriction] + public UnityTLSCallbacks.CallbackContext* UnityTLSCallbackContext; + + public void Execute() + { + UnityTLSCallbackContext->SendQueue = SendQueue; + UnityTLSCallbackContext->PacketPadding = k_TLSPadding; + + // Encrypt all the packets in the send queue. + var count = SendQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = SendQueue[i]; + if (packetProcessor.Length == 0) + continue; + + 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 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."); + packetProcessor.Drop(); + } + } + + // Add all the deferred sends to the send queue (they're already encrypted). + var deferredCount = DeferredSends.Count; + for (int i = 0; i < deferredCount; i++) + { + var deferredPacketProcessor = DeferredSends[i]; + if (deferredPacketProcessor.Length == 0) + continue; + + if (SendQueue.EnqueuePacket(out var packetProcessor)) + { + packetProcessor.EndpointRef = deferredPacketProcessor.EndpointRef; + packetProcessor.ConnectionRef = deferredPacketProcessor.ConnectionRef; + + // Remove the TLS padding from the offset. + packetProcessor.SetUnsafeMetadata(0, packetProcessor.Offset - k_TLSPadding); + + packetProcessor.AppendToPayload(deferredPacketProcessor); + } + } + + DeferredSends.Clear(); + } + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) + { + return new SendJob + { + ConnectionsData = m_ConnectionsData, + SendQueue = arguments.SendQueue, + DeferredSends = m_DeferredSends, + UnityTLSCallbackContext = m_UnityTLSConfiguration.CallbackContextPtr, + }.Schedule(dependency); + } + } +} + +#endif \ No newline at end of file diff --git a/Runtime/Layers/TLSLayer.cs.meta b/Runtime/Layers/TLSLayer.cs.meta new file mode 100644 index 0000000..07609fb --- /dev/null +++ b/Runtime/Layers/TLSLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cfa57defa195d6144873febfc119b11d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/TopLayer.cs b/Runtime/Layers/TopLayer.cs new file mode 100644 index 0000000..b965e4c --- /dev/null +++ b/Runtime/Layers/TopLayer.cs @@ -0,0 +1,145 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + // TODO: Leaving a TopLayer here for completeness, but it's doing nothing for now. + // This will allow us to add jobs to the last stage of receiving packets or the first + // stage of sending packets. + internal struct TopLayer : INetworkLayer + { + private ConnectionList connections; + + public void Dispose() {} + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { + connections = connectionList; + return 0; + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dependency) + { + return new CompleteReceiveJob + { + Connections = connections, + PipelineProcessor = arguments.PipelineProcessor, + Receiver = arguments.DriverReceiver, + EventQueue = arguments.EventQueue, + }.Schedule(dependency); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency) => dependency; + + [BurstCompile] + private struct CompleteReceiveJob : IJob + { + public ConnectionList Connections; + public NetworkPipelineProcessor PipelineProcessor; + public NetworkDriverReceiver Receiver; + public NetworkEventQueue EventQueue; + + public void Execute() + { + GenerateConnectionEvents(); + + // TODO: Connection initialization for pipelines, move this to the pipelines layer when ready + var incomingConnections = Connections.QueryIncomingConnections(Allocator.Temp); + for (int i = 0; i < incomingConnections.Length; i++) + { + var connection = incomingConnections[i]; + PipelineProcessor.InitializeConnection(new NetworkConnection(connection)); + } + + var count = Receiver.ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = Receiver.ReceiveQueue[i]; + + if (packetProcessor.Length <= 0) + continue; + + AppendToStream(ref packetProcessor); + } + + if (Receiver.ReceiveQueue.Count == Receiver.ReceiveQueue.Capacity) + UnityEngine.Debug.LogWarning($"Receive queue is full, some packets could be dropped, consider increase its size ({Receiver.ReceiveQueue.Capacity})."); + + Receiver.ReceiveQueue.Clear(); + + GenerateDisconnectionEvents(); + } + + private unsafe void AppendToStream(ref PacketProcessor packetProcessor) + { + if (packetProcessor.ConnectionRef == default) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError("Received a data event for a null connection. Ignoring."); +#endif + return; + } + + var pipelineId = packetProcessor.RemoveFromPayloadStart(); + + if (pipelineId > 0) + { + var connection = new NetworkConnection { m_ConnectionId = packetProcessor.ConnectionRef }; + var packetPtr = (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset; + PipelineProcessor.Receive(pipelineId, ref Receiver, ref EventQueue, ref connection, packetPtr, packetProcessor.Length); + } + else + { + var offset = Receiver.AppendToStream(ref packetProcessor); + + EventQueue.PushEvent(new NetworkEvent + { + pipelineId = (short)pipelineId, + connectionId = packetProcessor.ConnectionRef.Id, + type = NetworkEvent.Type.Data, + offset = offset, + size = packetProcessor.Length + }); + } + } + + private void GenerateConnectionEvents() + { + var newConnections = Connections.QueryFinishedConnections(Allocator.Temp); + var count = newConnections.Length; + for (int i = 0; i < count; i++) + { + EventQueue.PushEvent(new NetworkEvent + { + connectionId = newConnections[i].Id, + type = NetworkEvent.Type.Connect + }); + } + } + + private void GenerateDisconnectionEvents() + { + var newDisconnections = Connections.QueryFinishedDisconnections(Allocator.Temp); + var count = newDisconnections.Length; + for (int i = 0; i < count; i++) + { + var disconnectionCommand = newDisconnections[i]; + + // We don't trigger disconnection events if the disconnection + // was requested by the local endpoint. + if (disconnectionCommand.Reason == Error.DisconnectReason.Default) + continue; + + EventQueue.PushEvent(new NetworkEvent + { + connectionId = disconnectionCommand.Connection.Id, + type = NetworkEvent.Type.Disconnect, + status = (int)disconnectionCommand.Reason + }); + } + } + } + } +} diff --git a/Runtime/Layers/TopLayer.cs.meta b/Runtime/Layers/TopLayer.cs.meta new file mode 100644 index 0000000..87a8f50 --- /dev/null +++ b/Runtime/Layers/TopLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8de202a940b74467b0aaf93e345f598 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Layers/WebSocketLayer.cs b/Runtime/Layers/WebSocketLayer.cs new file mode 100644 index 0000000..0fcbb64 --- /dev/null +++ b/Runtime/Layers/WebSocketLayer.cs @@ -0,0 +1,817 @@ +#if !UNITY_WEBGL || UNITY_EDITOR +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.Utilities; + +namespace Unity.Networking.Transport +{ + internal struct WebSocketLayer : INetworkLayer + { + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void Warn(string msg) => UnityEngine.Debug.LogWarning(msg); + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void WarnIf(bool condition, string msg) { if (condition) UnityEngine.Debug.LogWarning(msg); } + + // Maps a connection id from the connection list to its connection data. + private ConnectionDataMap m_ConnectionMap; + + private ConnectionList m_ConnectionList; + private ConnectionList m_UnderlyingConnectionList; + private ConnectionDataMap m_UnderlyingConnectionMap; + + private WebSocket.Settings m_Settings; + + unsafe struct ConnectionData + { + public ConnectionId UnderlyingConnectionId; + + public WebSocket.State WebSocketState; + public WebSocket.Role Role; + + public WebSocket.Buffer SendBuffer; + public WebSocket.Buffer RecvBuffer; + + public WebSocket.Payload RecvPayload; + + private byte isReceivingPayload; + public bool IsReceivingPayload + { + get => isReceivingPayload > 0; + set => isReceivingPayload = (byte)(value ? 1 : 0); + } + + private byte isWaitingForPong; + public bool IsWaitingForPong + { + get => isWaitingForPong > 0; + set => isWaitingForPong = (byte)(value ? 1 : 0); + } + + public WebSocket.Keys Keys; + + public long CreateTimeStamp; + public long CloseTimeStamp; + public long ReceiveTimeStamp; + + public Error.DisconnectReason DisconnectReason; + + public bool IsClient => Role == WebSocket.Role.Client; + } + + public int Initialize(ref NetworkSettings settings, ref ConnectionList connectionList, ref int packetPadding) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!connectionList.IsCreated) + throw new InvalidOperationException($"{GetType().Name} requires an underlying connection list to track packets."); +#endif + + packetPadding += WebSocket.MaxHeaderSize; + + var networkConfigParameters = settings.GetNetworkConfigParameters(); + + m_Settings = new WebSocket.Settings + { + ConnectTimeoutMS = Math.Max(0, networkConfigParameters.connectTimeoutMS), + DisconnectTimeoutMS = Math.Max(0, networkConfigParameters.disconnectTimeoutMS), + HeartbeatTimeoutMS = Math.Max(0, networkConfigParameters.heartbeatTimeoutMS) + }; + + if (connectionList.IsCreated) + m_UnderlyingConnectionList = connectionList; + + m_ConnectionList = connectionList = ConnectionList.Create(); + m_ConnectionMap = new ConnectionDataMap(1, default, Allocator.Persistent); + m_UnderlyingConnectionMap = new ConnectionDataMap(1, default, Allocator.Persistent); + + return 0; + } + + public void Dispose() + { + m_ConnectionList.Dispose(); + m_ConnectionMap.Dispose(); + m_UnderlyingConnectionMap.Dispose(); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + { + return new SendJob + { + SendQueue = arguments.SendQueue, + UnderlyingConnectionList = m_UnderlyingConnectionList, + ConnectionList = m_ConnectionList, + ConnectionMap = m_ConnectionMap, + Settings = m_Settings, + Rand = new Mathematics.Random((uint)TimerHelpers.GetTicks()), + }.Schedule(dep); + } + + [BurstCompile] + unsafe struct SendJob : IJob + { + public PacketsQueue SendQueue; + public ConnectionList UnderlyingConnectionList; + public ConnectionList ConnectionList; + public ConnectionDataMap ConnectionMap; + public WebSocket.Settings Settings; + public Mathematics.Random Rand; + + public void Execute() + { + if (!UnderlyingConnectionList.IsCreated) + return; + + // Process all data messages + var count = SendQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = SendQueue[i]; + // Don't send empty packets or packets larger than we can receive on the other side. + if (packetProcessor.Length == 0 || (ushort)packetProcessor.Length > (NetworkParameterConstants.MTU - WebSocket.MaxHeaderSize)) + continue; + + var connectionId = packetProcessor.ConnectionRef; + var connectionState = ConnectionList.GetConnectionState(connectionId); + + // RFC6455 says we can only send data in the OPEN state but the layers assume they are allowed to + // send packets even if starting a disconnection right in the next line so let packets go through + // while a CLOSE hasn't been sent. + var connectionData = ConnectionMap[connectionId]; + packetProcessor.ConnectionRef = connectionData.UnderlyingConnectionId; + if ((connectionState == NetworkConnection.State.Connected && connectionData.WebSocketState == WebSocket.State.Open) + || (connectionState == NetworkConnection.State.Disconnecting && connectionData.WebSocketState != WebSocket.State.Closing)) + { + if (!WebSocket.Binary(ref packetProcessor, connectionData.IsClient, Rand.NextUInt())) + { + Warn("Failed to encode send packet"); + packetProcessor.Drop(); + } + } + else + { + packetProcessor.Drop(); + } + } + + // Send buffered handshake packets. User data is packed directly into WebSocket Binary packets above + // because packets from the upper layer are guaranteed to be no greater than + // NetworkParameterConstants.MTU - WebSocket.MaxHeaderSize bytes. + count = ConnectionList.Count; + for (int i = 0; i < count; i++) + { + var connectionId = ConnectionList.ConnectionAt(i); + var connectionState = ConnectionList.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected) + continue; + + var connectionData = ConnectionMap[connectionId]; + + // Send buffered WebSocket packets in possibly multiple MTU-sized packets. In theory, the loop + // might have to stall if the send queue becomes full but this is extremely unlikely. The send + // buffer should always have one (or more) complete packets to be transmitted. + var total = connectionData.SendBuffer.Length; + var pending = total; + var endpoint = ConnectionList.GetConnectionEndpoint(connectionId); + while (pending > 0 && SendQueue.EnqueuePacket(out var packetProcessor)) + { + packetProcessor.ConnectionRef = connectionData.UnderlyingConnectionId; + packetProcessor.EndpointRef = endpoint; + + // Pack the maximum possible amount of bytes from the send buffer into a packet. Must deduct + // the size of the header saved in the packet for this layer because the send buffer already + // contains both header and payload data. + var offset = packetProcessor.Offset - WebSocket.MaxHeaderSize; + var nbytes = Math.Min(pending, packetProcessor.BytesAvailableAtEnd + WebSocket.MaxHeaderSize); + packetProcessor.SetUnsafeMetadata(0, offset); + packetProcessor.AppendToPayload(connectionData.SendBuffer.Data + total - pending, nbytes); + pending -= nbytes; + } + + // Move any data that remains pending to the beginning of the buffer for the next iteration + if (pending > 0) + UnsafeUtility.MemMove(connectionData.SendBuffer.Data, connectionData.SendBuffer.Data + total - pending, pending); + + connectionData.SendBuffer.Length = pending; + ConnectionMap[connectionId] = connectionData; + } + } + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + { + return new ReceiveJob + { + ReceiveQueue = arguments.ReceiveQueue, + UnderlyingConnectionList = new UnderlyingConnectionList(ref m_UnderlyingConnectionList), + ConnectionList = m_ConnectionList, + ConnectionMap = m_ConnectionMap, + UnderlyingConnectionMap = m_UnderlyingConnectionMap, + Settings = m_Settings, + Rand = new Mathematics.Random((uint)TimerHelpers.GetTicks()), + Time = arguments.Time, + }.Schedule(dep); + } + + [BurstCompile] + unsafe struct ReceiveJob : IJob + { + public PacketsQueue ReceiveQueue; + + public ConnectionList ConnectionList; + public ConnectionDataMap ConnectionMap; + public UnderlyingConnectionList UnderlyingConnectionList; + public ConnectionDataMap UnderlyingConnectionMap; + public WebSocket.Settings Settings; + public Mathematics.Random Rand; + public long Time; + + // Close and clear data from disconnected underlying connections. + void ProcessUnderlyingDisconnections() + { + var disconnectionList = UnderlyingConnectionList.QueryFinishedDisconnections(Allocator.Temp); + var count = disconnectionList.Length; + for (int i = 0; i < count; i++) + { + var underlyingConnectionId = disconnectionList[i].Connection; + var connectionId = UnderlyingConnectionMap[underlyingConnectionId]; + if (connectionId.IsCreated) + { + var connectionState = ConnectionList.GetConnectionState(connectionId); + var connectionData = ConnectionMap[connectionId]; + + // At this point if the web socket was closed-and-flushed the disconnection reason applies + // (we just got ahead of ourselves); otherwise it must have been a connection reset by peer. + var reason = connectionData.WebSocketState == WebSocket.State.ClosedAndFlushed || disconnectionList[i].Reason == Error.DisconnectReason.MaxConnectionAttempts + ? disconnectionList[i].Reason + : Error.DisconnectReason.ClosedByRemote; + + if (connectionState != NetworkConnection.State.Disconnecting) + ConnectionList.StartDisconnecting(ref connectionId); + ConnectionList.FinishDisconnecting(ref connectionId, reason); + ConnectionMap.ClearData(ref connectionId); + UnderlyingConnectionMap.ClearData(ref underlyingConnectionId); + } + } + } + + // Process WebSocket State changes for each connection. WebSocket State transitions happen in + // combination with the current connection state (from the main connection list). + void ProcessConnectionStates() + { + var count = ConnectionList.Count; + for (int i = 0; i < count; i++) + { + var connectionId = ConnectionList.ConnectionAt(i); + var connectionState = ConnectionList.GetConnectionState(connectionId); + switch (connectionState) + { + case NetworkConnection.State.Disconnecting: + { + var connectionData = ConnectionMap[connectionId]; + switch (connectionData.WebSocketState) + { + case WebSocket.State.ClosedAndFlushed: + break; + // If the WebSocket is closed, wait until the send buffer is flushed to the + // underlying layer. + case WebSocket.State.Closed: + if (connectionData.SendBuffer.Length == 0) + connectionData.WebSocketState = WebSocket.State.ClosedAndFlushed; + break; + // If a WebSocket CLOSE has been sent, check for the close timeout waiting for a + // reply. A WebSocket CLOSE reply is expected in the receive queue and no other + // packet will be sent after the send buffer is flushed. If the remote peer does + // not close the connection within the time limit we can force it. + case WebSocket.State.Closing: + if (Time - connectionData.CloseTimeStamp > Settings.DisconnectTimeoutMS) + { + // Nothing in the send buffer is relevant anymore because a timeout is final. + connectionData.WebSocketState = WebSocket.State.ClosedAndFlushed; + connectionData.DisconnectReason = Error.DisconnectReason.Default; + } + break; + // If we were still in the middle of the handshake, the upper layer must want to + // abort the connection, so we can just force a disconnection. + case WebSocket.State.Opening: + // Nothing in the send buffer is relevant anymore because an abort is final. + connectionData.WebSocketState = WebSocket.State.ClosedAndFlushed; + connectionData.DisconnectReason = Error.DisconnectReason.Default; + break; + // If the upper layer is trying to disconnect normally, send a WebSocket Close and + // wait for a CLOSE reply. If the send buffer is full and we cannot send a CLOSE + // now ignore. In the next scheduled received we can try again and the send queue + // will eventually free up for the send buffer to flush and make room for the close. + // This is safe as no user data can be sent by this connection since it's not + // NetworkConnection.State.Connected anymore. + case WebSocket.State.Open: + if (WebSocket.Close(ref connectionData.SendBuffer, WebSocket.StatusCode.Normal, connectionData.IsClient, Rand.NextUInt())) + { + connectionData.WebSocketState = WebSocket.State.Closing; + connectionData.CloseTimeStamp = Time; + } + break; + } + + // If the WebSocket connection is closed-and-flushed we can close the underlying + // connection and discard all the connection data. Note that this may take multiple + // schedules as the layer below is not required to disconnect immediately. + if (connectionData.WebSocketState == WebSocket.State.ClosedAndFlushed) + { + if (UnderlyingConnectionList.TryDisconnect(ref connectionData.UnderlyingConnectionId)) + { + ConnectionList.FinishDisconnecting(ref connectionId, connectionData.DisconnectReason); + UnderlyingConnectionMap.ClearData(ref connectionData.UnderlyingConnectionId); + ConnectionMap.ClearData(ref connectionId); + } + } + + ConnectionMap[connectionId] = connectionData; + } + break; + case NetworkConnection.State.Connecting: + { + var connectionData = ConnectionMap[connectionId]; + + switch (connectionData.WebSocketState) + { + // If this is a brand new CLIENT connection from which we have to send an + // HTTP UPGARDE try to complete an underlying connection and initialize the + // connection data. + case WebSocket.State.None: + { + var remoteEndpoint = ConnectionList.GetConnectionEndpoint(connectionId); + if (!UnderlyingConnectionList.TryConnect(ref remoteEndpoint, ref connectionData.UnderlyingConnectionId)) + { + ConnectionMap[connectionId] = connectionData; + if (connectionData.UnderlyingConnectionId.IsCreated) + UnderlyingConnectionMap[connectionData.UnderlyingConnectionId] = connectionId; + + continue; + } + + connectionData.WebSocketState = WebSocket.State.Opening; + connectionData.Role = WebSocket.Role.Client; + connectionData.Keys.Key[0] = Rand.NextUInt(); + connectionData.Keys.Key[1] = Rand.NextUInt(); + connectionData.Keys.Key[2] = Rand.NextUInt(); + connectionData.Keys.Key[3] = Rand.NextUInt(); + connectionData.CreateTimeStamp = Time; + + WebSocket.Connect(ref connectionData.SendBuffer, ref remoteEndpoint, ref connectionData.Keys); + } + break; + // If this is a active (client) connection for which an HTTP UPGARDE has already + // been sent and we're waiting for an HTTP SWITCHING PROTOCOL; or this is a + // passive (server) connection from which we're waiting for a complete (and valid) + // HTTP UPGRADE, check the connection timeout. + case WebSocket.State.Opening: + if (Time - connectionData.CreateTimeStamp > Settings.ConnectTimeoutMS) + { + // Nothing in the send buffer is releavent anymore because a timeout is final. + ConnectionList.StartDisconnecting(ref connectionId); + connectionData.WebSocketState = WebSocket.State.ClosedAndFlushed; + connectionData.DisconnectReason = Error.DisconnectReason.Timeout; + } + break; + default: + Warn($"Invalid connection state: {connectionState}, {connectionData.WebSocketState}"); + break; + } + ConnectionMap[connectionId] = connectionData; + } + break; + case NetworkConnection.State.Connected: + { + var connectionData = ConnectionMap[connectionId]; + if (connectionData.WebSocketState == WebSocket.State.Open) + { + if (!connectionData.IsWaitingForPong && Time - connectionData.ReceiveTimeStamp > Settings.HeartbeatTimeoutMS) + { + // If there is not enough space in the send buffer we can try again in the + // next schedule, this is not a critial error. + if (WebSocket.Ping(ref connectionData.SendBuffer, connectionData.IsClient, Rand.NextUInt())) + { + connectionData.IsWaitingForPong = true; + } + } + } + else + { + Warn($"Invalid connection state: {connectionState}, {connectionData.WebSocketState}"); + } + ConnectionMap[connectionId] = connectionData; + } + break; + case NetworkConnection.State.Disconnected: + // Do nothing. This is not a default fallback clause so the compiler can generate a warning + // if someone one day defines a new connection state and forgets to handle it here. + break; + } + } + } + + // Process all data messages. All packets pass through the RecvBuffer to be segmented into + // individual HTTP handshake messages or WebSocket frames. + void ProcessReceivedMessages() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < count; i++) + { + var packetProcessor = ReceiveQueue[i]; + + // Must check packets up to NetworkParameterConstants.MTU here (including WebSocket.MaxHeaderSize) + // because in theory, a handshake packet larger than MTU would be split by the layer below (or + // network interface) into potentially multiple MTU-sized segments with no header. + if (packetProcessor.Length == 0 || packetProcessor.Length > NetworkParameterConstants.MTU) + { + packetProcessor.Drop(); + continue; + } + + var underlyingConnectionId = packetProcessor.ConnectionRef; + var connectionId = UnderlyingConnectionMap[underlyingConnectionId]; + + // If this is a packet from an untracked connection it might be a new HTTP UPGRADE (complete or + // incomplete), so start a new connection to decode whatever it is. This is assuming the underlying + // layer would never let a packet pass for a connection that is not fully connected in that level. + if (!connectionId.IsCreated) + { + connectionId = ConnectionList.StartConnecting(ref packetProcessor.EndpointRef); + ConnectionMap[connectionId] = new ConnectionData + { + UnderlyingConnectionId = underlyingConnectionId, + + // Different than active (client) connections, passive (server) connections do not require + // WebSocket.Keys to be initialized. + WebSocketState = WebSocket.State.Opening, + Role = WebSocket.Role.Server, + CreateTimeStamp = Time + }; + + UnderlyingConnectionMap[underlyingConnectionId] = connectionId; + } + + // If the connection is not disconnected and the WebSocket is not closed yet, process the received + // packet. + var connectionState = ConnectionList.GetConnectionState(connectionId); + if (connectionState != NetworkConnection.State.Disconnected) + { + var connectionData = ConnectionMap[connectionId]; + if (connectionData.WebSocketState != WebSocket.State.Closed + && connectionData.WebSocketState != WebSocket.State.ClosedAndFlushed) + { + var available = connectionData.RecvBuffer.Available; + if (packetProcessor.Length <= available) + { + // Copy packet data into the receive buffer. + packetProcessor.CopyPayload(connectionData.RecvBuffer.Data + connectionData.RecvBuffer.Length, packetProcessor.Length); + connectionData.RecvBuffer.Length += packetProcessor.Length; + + // If handshake is incomplete, try to complete. + if (connectionData.WebSocketState == WebSocket.State.Opening) + { + // WebSocket.Handshake returns WebSocket.State.Opening (incomplete), + // WebSocket.State.Open (complete) or WebSocket.State.Closed (error). + // If an error occurs, an error message may get written into the send buffer to + // be transmitted by the next send job scheduled. + connectionData.WebSocketState = WebSocket.Handshake(ref connectionData.RecvBuffer, + ref connectionData.SendBuffer, connectionData.IsClient, ref connectionData.Keys); + + if (connectionData.WebSocketState == WebSocket.State.Closed) + { + ConnectionList.StartDisconnecting(ref connectionId); + // TODO: We should probably use a proper error reason here (Protocol Error, Buffer Overflow, ...) + connectionData.DisconnectReason = Error.DisconnectReason.Timeout; + } + else if (connectionData.WebSocketState == WebSocket.State.Open) + { + connectionData.ReceiveTimeStamp = Time; + if (connectionData.IsClient) + ConnectionList.FinishConnectingFromLocal(ref connectionId); + else + ConnectionList.FinishConnectingFromRemote(ref connectionId); + } + } + + // If handshake is complete, we can receive PING/PONG/DATA/CLOSE or a CLOSE reply. + // It's naturally possible to receive data before an expected CLOSE reply either + // because the data was already in flight or because the remote peer wants to + // complete its transmission. We are not required to reply to PINGs after a CLOSE is + // received but we are [required] while waiting for a CLOSE reply. + if (connectionData.WebSocketState == WebSocket.State.Open || connectionData.WebSocketState == WebSocket.State.Closing) + ProcessWebSocketFrames(ref connectionId, ref connectionData); + } + else + { + // Insufficient buffer space at this level means an unrecoverable data loss. + // The WebSocket Protocol is an independent TCP-based protocol so there is no reason to + // presume the remote peer will be able to retransmit the lost packets in which case + // we're left with no other option but to disconnect. + Warn("Insufficient receive buffer."); + ConnectionList.StartDisconnecting(ref connectionId); + connectionData.WebSocketState = WebSocket.State.Closed; + // TODO: We should probably use a proper error reason here (Protocol Error, Buffer Overflow, ...) + connectionData.DisconnectReason = Error.DisconnectReason.Timeout; + } + } + ConnectionMap[connectionId] = connectionData; + } + + // Packet is always dropped because websocket messages must be processed by ProcessWebSocketFrames (eg. can be fragmented) + // and the upper layer should only see the content of connectionData.RecvBuffer when the message is valid and complete. + packetProcessor.Drop(); + } + } + + // Process all websocket frames in the recv buffer. Only happens after the hadnshake is complete. + // Unfragmented WebSocket messages are handled immediately. Fragmented WebSocket messages are assembled in + // RecvPayload and handled once complete. + void ProcessWebSocketFrames(ref ConnectionId connectionId, ref ConnectionData connectionData) + { + var total = connectionData.RecvBuffer.Length; + if (total <= 0) + return; + + var pending = total; + fixed(byte* start = connectionData.RecvBuffer.Data) + { + byte* end = start + total; + byte* data = start; + + while (data < end) + { + // Yield if minimum header has not beend received yet + if (end - data < 2) + break; + + // Calculate the header size + var headerSize = 2; + var payloadByte = data[1] & 0x7f; + if ((data[1] & 0x80) != 0) + headerSize += 4; + if (payloadByte == 126) + headerSize += 2; + else if (payloadByte == 127) + headerSize += 8; + + // Yield if complete header has not beend received yet + if (end - data < headerSize) + break; + + var isClient = connectionData.IsClient; + var isFragment = (data[0] & 0x80) == 0; + var opcode = data[0] & 0x0F; + var masked = (data[1] & 0x80) != 0; + + // Receiving a message with invalid header bits or a masked message on the client or an + // unmasked message on the server are all protocol errors. + // Currently only support fragmentation of binary frames. RFC6455 states control frames may be + // injected in the middle of a fragmented message but cannot be fragmented themselves. The + // fragments of one message cannot be interleaved between the fragments of another message + // either. + var invalidHeaderBits = (data[0] & 0x70) != 0; + var invalidMasking = masked == isClient; + var invalidFragmentation = isFragment && opcode != (int)WebSocket.Opcode.Continuation && opcode != (int)WebSocket.Opcode.BinaryData; + + if (invalidHeaderBits || invalidMasking || invalidFragmentation) + { + WarnIf(invalidHeaderBits, "Received message with invalid reserved header bits"); + WarnIf(invalidMasking, "Received message with unexpected masking"); + WarnIf(invalidFragmentation, "Received a fragmented control frame."); + + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.ProtocolError, isClient); + return; + } + + // A complete header is available so figure out how big the payload is. + var wsPayloadSize = 0UL; + if (wsPayloadSize == 127) + { + wsPayloadSize = ((ulong)data[6] << 56) + ((ulong)data[7] << 48) + + ((ulong)data[6] << 40) + ((ulong)data[7] << 32) + + ((ulong)data[6] << 24) + ((ulong)data[7] << 16) + + ((ulong)data[8] << 8) + (ulong)data[9]; + } + else if (payloadByte == 126) + { + wsPayloadSize = ((ulong)data[2] << 8) + data[3]; + } + else + { + wsPayloadSize = (ulong)payloadByte; + } + + // We don't support payloads larger than WebSocket.MaxPayloadSize. + if (wsPayloadSize > WebSocket.MaxPayloadSize) + { + Warn($"Received a message with a payload size of {wsPayloadSize} which exceeds maximum of {WebSocket.MaxPayloadSize} supported"); + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.MessageTooBig, isClient); + return; + } + + var payloadSize = (int)wsPayloadSize; + + // Update pointer to start of payload + data += headerSize; + + // Yield if complete payload has not been received yet + if (end - data < payloadSize) + break; + + // Unmask the payload + if (masked) + { + var maskBytes = data - 4; + for (int i = 0; i < payloadSize; ++i) + data[i] = (byte)(data[i] ^ maskBytes[i & 3]); + } + + if (opcode == (int)WebSocket.Opcode.Continuation) + { + // There must have been a previous binary frame to continue. Although allowed by the spec + // we don't support empty binrary frames. The RFC apparently allows for a data frame to be + // flagged as a fragment and yet carry 0 payload data. This would be just a waste of + // bandwidth and a possible indication of a malicious peer as further continuations could + // remain empty indefinitely. This check ensures we just accept continuations if we were + // already rebuilding a fragmented message (IsReceivingPayload is true). And this will only + // be the case if the first fragment was not empty because we ignore ignore empty data + // frames when handling WebSocket.Opcode.BinaryData. + var invalidFragment = !connectionData.IsReceivingPayload; + var invalidTotalSize = (connectionData.RecvPayload.Length + payloadSize) > WebSocket.MaxPayloadSize; + if (invalidFragment || invalidTotalSize) + { + WarnIf(invalidFragment, "Received a continuation that doesn't belong to a message"); + WarnIf(invalidTotalSize, $"Total message size is larger than maximum of {WebSocket.MaxPayloadSize} supported"); + + var status = invalidFragment ? WebSocket.StatusCode.ProtocolError : WebSocket.StatusCode.MessageTooBig; + Abort(ref connectionId, ref connectionData, status, isClient); + return; + } + + // A sender MAY create fragments of any size for non-control messages which includes + // empty fragments (no payload). + if (payloadSize > 0) + { + fixed(byte* recvPayload = connectionData.RecvPayload.Data) + { + UnsafeUtility.MemCpy(recvPayload + connectionData.RecvPayload.Length, data, payloadSize); + connectionData.RecvPayload.Length += payloadSize; + + // If this is the last fragment we can push the message + if (!isFragment) + { + if (!ReceiveQueue.EnqueuePacket(out var packetProcessor)) + { + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.InternalError, isClient); + return; + } + + packetProcessor.ConnectionRef = connectionId; + packetProcessor.EndpointRef = ConnectionList.GetConnectionEndpoint(connectionId); + packetProcessor.AppendToPayload(recvPayload, connectionData.RecvPayload.Length); + + connectionData.RecvPayload.Length = 0; + connectionData.IsReceivingPayload = false; + } + } + } + } + else if (opcode == (int)WebSocket.Opcode.TextData) + { + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.UnsupportedDataType, isClient); + return; + } + else if (opcode == (int)WebSocket.Opcode.BinaryData) + { + // A sender MAY create fragments of any size for non-control messages which includes + // empty fragments (no payload). + if (payloadSize > 0) + { + // If this is a fragment store in the RecvPayload + if (isFragment) + { + fixed(byte* payload = connectionData.RecvPayload.Data) + { + UnsafeUtility.MemCpy(payload + connectionData.RecvPayload.Length, data, payloadSize); + connectionData.RecvPayload.Length = payloadSize; + connectionData.IsReceivingPayload = true; + } + } + // If this is a complete message, push into the receive queue + else + { + // A new binary message can only arrive if we're not expecting any more fragments + // from a previous message. + if (connectionData.IsReceivingPayload) + { + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.ProtocolError, isClient); + return; + } + + if (!ReceiveQueue.EnqueuePacket(out var packetProcessor)) + { + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.InternalError, isClient); + return; + } + + packetProcessor.ConnectionRef = connectionId; + packetProcessor.EndpointRef = ConnectionList.GetConnectionEndpoint(connectionId); + packetProcessor.AppendToPayload(data, payloadSize); + } + } + } + else if (opcode == (int)WebSocket.Opcode.Close) + { + // If this is a close reply (a CLOSE had previously been sent) we can close immediately; + // otherwise this must be a CLOSE request in which case we can close after sending a CLOSE + // reply. + if (connectionData.WebSocketState == WebSocket.State.Closing) + { + connectionData.WebSocketState = WebSocket.State.ClosedAndFlushed; + if (UnderlyingConnectionList.TryDisconnect(ref connectionData.UnderlyingConnectionId)) + { + ConnectionList.FinishDisconnecting(ref connectionId, connectionData.DisconnectReason); + UnderlyingConnectionMap.ClearData(ref connectionData.UnderlyingConnectionId); + ConnectionMap.ClearData(ref connectionId); + } + } + else + { + // Spec recommends echoing the status code of the CLOSE received which comes encoded + // in big endian. + var status = payloadSize > 1 ? (WebSocket.StatusCode)((data[0] << 8) + data[1]) : WebSocket.StatusCode.Normal; + Abort(ref connectionId, ref connectionData, status, isClient, Error.DisconnectReason.ClosedByRemote); + } + return; + } + else if (opcode == (int)WebSocket.Opcode.Ping) + { + // There is no point in sending anything else even PONGs after we have sent a CLOSE request. + if (connectionData.WebSocketState != WebSocket.State.Closing) + { + if (!WebSocket.Pong(ref connectionData.SendBuffer, data, payloadSize, isClient, Rand.NextUInt())) + { + Warn("Insufficient send buffer."); + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.InternalError, isClient); + return; + } + } + } + else if (opcode == (int)WebSocket.Opcode.Pong) + { + connectionData.IsWaitingForPong = false; + } + else + { + // Unsupported opcode + Warn($"Received message with an unsupported opcode: 0x{opcode:X2}"); + Abort(ref connectionId, ref connectionData, WebSocket.StatusCode.ProtocolError, isClient); + return; + } + + data += payloadSize; + pending -= headerSize; + pending -= payloadSize; + + // Save the time of the latest frame received + connectionData.ReceiveTimeStamp = Time; + } + + if (pending > 0 && pending != total) + UnsafeUtility.MemMove(start, start + total - pending, pending); + } + connectionData.RecvBuffer.Length = pending; + } + + void Abort(ref ConnectionId connectionId, ref ConnectionData connectionData, WebSocket.StatusCode status, bool isClient, Error.DisconnectReason disconnectReason = default) + { + if (connectionData.WebSocketState != WebSocket.State.Closing) + { + // There is no point in checking if a CLOSE message could be written to the send buffer here since + // we're bound to close the socket without waiting for a reply anyway. + WebSocket.Close(ref connectionData.SendBuffer, status, isClient, Rand.NextUInt()); + } + + var state = ConnectionList.GetConnectionState(connectionId); + if (state != NetworkConnection.State.Disconnected && state != NetworkConnection.State.Disconnecting) + ConnectionList.StartDisconnecting(ref connectionId); + connectionData.WebSocketState = WebSocket.State.Closed; + connectionData.DisconnectReason = disconnectReason; + } + + public void Execute() + { + ProcessReceivedMessages(); + ProcessUnderlyingDisconnections(); + ProcessConnectionStates(); + } + } + } +} +#endif diff --git a/Runtime/Layers/WebSocketLayer.cs.meta b/Runtime/Layers/WebSocketLayer.cs.meta new file mode 100644 index 0000000..aa25037 --- /dev/null +++ b/Runtime/Layers/WebSocketLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 392f89cb499d36a4d8d5eea335e98478 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NetworkCompressionModel.cs b/Runtime/NetworkCompressionModel.cs deleted file mode 100644 index 4eeea63..0000000 --- a/Runtime/NetworkCompressionModel.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System; -using Unity.Collections; -using Unity.Mathematics; - -namespace Unity.Networking.Transport -{ - /// - /// Used to provide Huffman compression when using packed DataStream functions - /// - public unsafe struct NetworkCompressionModel : IDisposable - { - internal static readonly byte[] k_BucketSizes = - { - 0, 0, 1, 2, 3, 4, 6, 8, 10, 12, 15, 18, 21, 24, 27, 32 - }; - - internal static readonly uint[] k_BucketOffsets = - { - 0, 1, 2, 4, 8, 16, 32, 96, 352, 1376, 5472, 38240, 300384, 2397536, 19174752, 153392480 - }; - internal static readonly int[] k_FirstBucketCandidate = - { - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 - 15, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 1, 0 - }; - internal static readonly byte[] k_DefaultModelData = { 16, // 16 symbols - 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 6, 6, - 0, 0 }; // no contexts - internal const int k_AlphabetSize = 16; - internal const int k_MaxHuffmanSymbolLength = 6; - internal const int k_MaxContexts = 1; - - /// - /// Disposes this instance - /// - public void Dispose() - { - } - - /// - /// Initializes a new instance of the class - /// - /// The allocator - public NetworkCompressionModel(Allocator allocator) - { - for (int i = 0; i < k_AlphabetSize; ++i) - { - bucketSizes[i] = k_BucketSizes[i]; - bucketOffsets[i] = k_BucketOffsets[i]; - } - byte[] modelData = k_DefaultModelData; - - //int numContexts = NetworkConfig.maxContexts; - int numContexts = 1; - byte[,] symbolLengths = new byte[numContexts, k_AlphabetSize]; - - int readOffset = 0; - { - // default model - int defaultModelAlphabetSize = modelData[readOffset++]; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (defaultModelAlphabetSize != k_AlphabetSize) - throw new InvalidOperationException("The alphabet size of compression models must be " + k_AlphabetSize); -#endif - - for (int i = 0; i < k_AlphabetSize; i++) - { - byte length = modelData[readOffset++]; - for (int context = 0; context < numContexts; context++) - { - symbolLengths[context, i] = length; - } - } - - // other models - int numModels = modelData[readOffset] | (modelData[readOffset + 1] << 8); - readOffset += 2; - for (int model = 0; model < numModels; model++) - { - int context = modelData[readOffset] | (modelData[readOffset + 1] << 8); - readOffset += 2; - - int modelAlphabetSize = modelData[readOffset++]; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (modelAlphabetSize != k_AlphabetSize) - throw new InvalidOperationException("The alphabet size of compression models must be " + k_AlphabetSize); -#endif - for (int i = 0; i < k_AlphabetSize; i++) - { - byte length = modelData[readOffset++]; - symbolLengths[context, i] = length; - } - } - } - - // generate tables - var tmpSymbolLengths = new byte[k_AlphabetSize]; - var tmpSymbolDecodeTable = new ushort[1 << k_MaxHuffmanSymbolLength]; - var symbolCodes = new byte[k_AlphabetSize]; - - for (int context = 0; context < numContexts; context++) - { - for (int i = 0; i < k_AlphabetSize; i++) - tmpSymbolLengths[i] = symbolLengths[context, i]; - - GenerateHuffmanCodes(symbolCodes, 0, tmpSymbolLengths, 0, k_AlphabetSize, k_MaxHuffmanSymbolLength); - GenerateHuffmanDecodeTable(tmpSymbolDecodeTable, 0, tmpSymbolLengths, symbolCodes, k_AlphabetSize, k_MaxHuffmanSymbolLength); - for (int i = 0; i < k_AlphabetSize; i++) - { - encodeTable[context * k_AlphabetSize + i] = (ushort)((symbolCodes[i] << 8) | symbolLengths[context, i]); - } - for (int i = 0; i < (1 << k_MaxHuffmanSymbolLength); i++) - { - decodeTable[context * (1 << k_MaxHuffmanSymbolLength) + i] = tmpSymbolDecodeTable[i]; - } - } - } - - private static void GenerateHuffmanCodes(byte[] symboLCodes, int symbolCodesOffset, byte[] symbolLengths, int symbolLengthsOffset, int alphabetSize, int maxCodeLength) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (alphabetSize > 256 || maxCodeLength > 8) - throw new InvalidOperationException("Can only generate huffman codes up to alphabet size 256 and maximum code length 8"); -#endif - - var lengthCounts = new byte[maxCodeLength + 1]; - var symbolList = new byte[maxCodeLength + 1, alphabetSize]; - - //byte[] symbol_list[(MAX_HUFFMAN_CODE_LENGTH + 1u) * MAX_NUM_HUFFMAN_SYMBOLS]; - for (int symbol = 0; symbol < alphabetSize; symbol++) - { - int length = symbolLengths[symbol + symbolLengthsOffset]; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (length > maxCodeLength) - throw new InvalidOperationException("Maximum code length exceeded"); -#endif - symbolList[length, lengthCounts[length]++] = (byte)symbol; - } - - uint nextCodeWord = 0; - for (int length = 1; length <= maxCodeLength; length++) - { - int length_count = lengthCounts[length]; - for (int i = 0; i < length_count; i++) - { - int symbol = symbolList[length, i]; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (symbolLengths[symbol + symbolLengthsOffset] != length) - throw new InvalidOperationException("Incorrect symbol length"); -#endif - symboLCodes[symbol + symbolCodesOffset] = (byte)ReverseBits(nextCodeWord++, length); - } - nextCodeWord <<= 1; - } - } - - private static uint ReverseBits(uint value, int num_bits) - { - value = ((value & 0x55555555u) << 1) | ((value & 0xAAAAAAAAu) >> 1); - value = ((value & 0x33333333u) << 2) | ((value & 0xCCCCCCCCu) >> 2); - value = ((value & 0x0F0F0F0Fu) << 4) | ((value & 0xF0F0F0F0u) >> 4); - value = ((value & 0x00FF00FFu) << 8) | ((value & 0xFF00FF00u) >> 8); - value = (value << 16) | (value >> 16); - return value >> (32 - num_bits); - } - - // decode table entries: (symbol << 8) | length - private static void GenerateHuffmanDecodeTable(ushort[] decodeTable, int decodeTableOffset, byte[] symbolLengths, byte[] symbolCodes, int alphabetSize, int maxCodeLength) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (alphabetSize > 256 || maxCodeLength > 8) - throw new InvalidOperationException("Can only generate huffman codes up to alphabet size 256 and maximum code length 8"); -#endif - - uint maxCode = 1u << maxCodeLength; - for (int symbol = 0; symbol < alphabetSize; symbol++) - { - int length = symbolLengths[symbol]; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (length > maxCodeLength) - throw new InvalidOperationException("Maximum code length exceeded"); -#endif - if (length > 0) - { - uint code = symbolCodes[symbol]; - uint step = 1u << length; - do - { - decodeTable[decodeTableOffset + code] = (ushort)(symbol << 8 | length); - code += step; - } - while (code < maxCode); - } - } - } - - internal fixed ushort encodeTable[k_MaxContexts * k_AlphabetSize]; - internal fixed ushort decodeTable[k_MaxContexts * (1 << k_MaxHuffmanSymbolLength)]; - internal fixed byte bucketSizes[k_AlphabetSize]; - internal fixed uint bucketOffsets[k_AlphabetSize]; - - /// - /// Calculates the bucket using the specified value - /// - /// The value - /// The bucket index - public int CalculateBucket(uint value) - { - int bucketIndex = k_FirstBucketCandidate[math.lzcnt(value)]; - if (bucketIndex + 1 < k_AlphabetSize && value >= bucketOffsets[bucketIndex + 1]) - bucketIndex++; - - return bucketIndex; - } - } -} diff --git a/Runtime/NetworkConnection.cs b/Runtime/NetworkConnection.cs index 331f882..579eff4 100644 --- a/Runtime/NetworkConnection.cs +++ b/Runtime/NetworkConnection.cs @@ -1,182 +1,130 @@ using System; +using System.ComponentModel; using Unity.Collections; namespace Unity.Networking.Transport { namespace Error { - /// Reason for a disconnection event. - /// - /// One of these values may be present as a single byte in the - /// obtained with if the event type is - /// . - /// + /// + /// DisconnectReason enumerates all disconnect reasons. + /// public enum DisconnectReason : byte { - // Don't assign explicit values. - - /// Used internally when no other reason fits. - /// - /// This shouldn't normally appear as a result of calling . - /// - Default, + // This enum is matched by NetworkStreamDisconnectReason in Netcode for Entities, so any + // change to it should be discussed and properly synchronized with them first. - /// - /// Indicates the connection timed out (see ). - /// + /// Indicates a normal disconnection as a result of calling Disconnect on the connection. + Default, // don't assign explicit values + /// Indicates the connection timed out. Timeout, - - /// - /// Indicates the connection failed to establish after too many failed attempts - /// (see ). - /// + /// Indicates the connection failed to establish a connection after . MaxConnectionAttempts, - - /// - /// Indicates the connection was closed normally by the remote peer after calling - /// or . - /// + /// Indicates the connection was closed remotely. ClosedByRemote, - - /// Used internally to track the number of reasons. Keep last. + /// Used only for count. Keep last and don't assign explicit values Count } - /// Status code returned by many functions in the API. - /// Any value less than 0 denotes a failure. public enum StatusCode { - /// Success; no error encountered. Success = 0, - - /// Unknown connection (internal ID doesn't exist). NetworkIdMismatch = -1, - - /// Unknown connection (internal ID in use by newer connection). NetworkVersionMismatch = -2, - - /// Invalid operation given connection's state. NetworkStateMismatch = -3, - - /// Packet data is too large to handle. NetworkPacketOverflow = -4, - - /// Network send queue is full. NetworkSendQueueFull = -5, - - /// A packet's header is invalid. - // TODO Not in use anymore, should we delete? NetworkHeaderInvalid = -6, - - /// Tried to process the same connection multiple times in a parallel job. NetworkDriverParallelForErr = -7, - - /// Internal send handle is invalid. NetworkSendHandleInvalid = -8, - - /// Tried to create an on a non-loopback address. NetworkArgumentMismatch = -9, - - /// The underlying network socket has failed. - NetworkSocketError = -10, + NetworkReceiveQueueFull = -10, + NetworkSocketError = -11, } } /// - /// Public representation of a connection. Holds all information needed by the - /// to link it to an internal virtual connection. + /// The NetworkConnection is a struct that hold all information needed by the driver to link it with a virtual + /// connection. The NetworkConnection is a public representation of a connection. /// public struct NetworkConnection : IEquatable { - /// Index of the connection in the internal connection list. - internal int m_NetworkId; - - /// Version of the connection at the above index (indices can be reused). - internal int m_NetworkVersion; + internal ConnectionId m_ConnectionId; - /// Connection States + /// + /// ConnectionState enumerates available connection states a connection can have. + /// public enum State { - /// Indicates the connection is disconnected. + /// Indicates the connection is disconnected Disconnected, - /// Indicates the connection is being established. + + /// + /// Indicates the connection is in the process of being disconnected. + /// This is an internal state and it is mapped to Disconnected at the + /// NetworkDriver level. + /// + [EditorBrowsable(EditorBrowsableState.Never)] Disconnecting, + + /// Indicates the connection is trying to connect. Connecting, - /// Indicates the connection is connected. - Connected + + /// Indicates the connection is connected.. + Connected, + } + + internal NetworkConnection(ConnectionId connectionId) + { + m_ConnectionId = connectionId; } /// - /// Disconnects a connection and marks it for deletion. The connection will be removed on - /// the next frame. Same as . + /// Disconnects a virtual connection and marks it for deletion. This connection will be removed on next the next frame. /// - /// Driver to which the connection belongs. - /// An Error.StatusCode value (0 on success, -1 otherwise). + /// The driver that owns the virtual connection. public int Disconnect(NetworkDriver driver) { return driver.Disconnect(this); } /// - /// Receive an event for this specific connection. Should be called until it returns - /// , even if the connection is disconnected. + /// Receive an event for this specific connection. Should be called until it returns , even if the socket is disconnected. /// - /// Driver to which the connection belongs. - /// - /// A , that will only be populated if a - /// (or possibly ) event was received. + /// The driver that owns the virtual connection. + /// A DataStreamReader, that will only be populated if a + /// event was received. /// - /// The of the event. public NetworkEvent.Type PopEvent(NetworkDriver driver, out DataStreamReader stream) { return driver.PopEventForConnection(this, out stream); } - /// - /// Receive an event for this specific connection. Should be called until it returns - /// , even if the connection is disconnected. - /// - /// Driver to which the connection belongs. - /// - /// A , that will only be populated if a - /// (or possibly ) event was received. - /// - /// - /// The on which the event was received. Will only be populated - /// for events. - /// - /// The of the event. public NetworkEvent.Type PopEvent(NetworkDriver driver, out DataStreamReader stream, out NetworkPipeline pipeline) { return driver.PopEventForConnection(this, out stream, out pipeline); } /// - /// Disconnects a connection and marks it for deletion. The connection will be removed on - /// the next frame. Same as . + /// Close an active NetworkConnection, similar to . /// - /// Driver to which the connection belongs. - /// An Error.StatusCode value (0 on success, -1 otherwise). + /// The driver that owns the virtual connection. public int Close(NetworkDriver driver) { - if (m_NetworkId >= 0) + if (m_ConnectionId.Id >= 0) return driver.Disconnect(this); - return (int)Error.StatusCode.NetworkIdMismatch; + return -1; } - /// Checks to see if a connection is created. - /// - /// Connections are considered as created only if they've been obtained by a call to - /// or . - /// - /// Whether the connection is created or not. + /// + /// Check to see if a NetworkConnection is Created. + /// + /// `true` if the NetworkConnection has been correctly created by a call to + /// or public bool IsCreated { - get { return m_NetworkVersion != 0; } + get { return m_ConnectionId.Version != 0; } } - /// Gets the state of the connection. - /// Driver to which the connection belongs. - /// The value for the connection. public State GetState(NetworkDriver driver) { return driver.GetConnectionState(this); @@ -184,30 +132,35 @@ public State GetState(NetworkDriver driver) public static bool operator==(NetworkConnection lhs, NetworkConnection rhs) { - return lhs.m_NetworkId == rhs.m_NetworkId && lhs.m_NetworkVersion == rhs.m_NetworkVersion; + return lhs.m_ConnectionId == rhs.m_ConnectionId; } public static bool operator!=(NetworkConnection lhs, NetworkConnection rhs) { - return lhs.m_NetworkId != rhs.m_NetworkId || lhs.m_NetworkVersion != rhs.m_NetworkVersion; + return lhs.m_ConnectionId != rhs.m_ConnectionId; } - + public override bool Equals(object o) { return this == (NetworkConnection)o; } - + public bool Equals(NetworkConnection o) { return this == o; } - + public override int GetHashCode() { - return (m_NetworkId << 8) ^ m_NetworkVersion; + return m_ConnectionId.GetHashCode(); + } + + public override string ToString() + { + return $"NetworkConnection[id{InternalId},v{Version}]"; } - /// Gets the value of the connection's internal ID. - public int InternalId => m_NetworkId; + public int InternalId => m_ConnectionId.Id; + public int Version => m_ConnectionId.Version; } } diff --git a/Runtime/NetworkDriver.cs b/Runtime/NetworkDriver.cs index 29ccb8a..f23a0dc 100644 --- a/Runtime/NetworkDriver.cs +++ b/Runtime/NetworkDriver.cs @@ -1,7 +1,5 @@ using System; -using System.Diagnostics; -using System.Collections.Generic; -using System.Runtime.CompilerServices; +using System.Threading; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -17,7 +15,7 @@ namespace Unity.Networking.Transport public unsafe struct QueuedSendMessage { public fixed byte Data[NetworkParameterConstants.MTU]; - public NetworkInterfaceEndPoint Dest; + public NetworkEndpoint Dest; public int DataLength; } @@ -26,7 +24,7 @@ public unsafe struct QueuedSendMessage /// /// Basic usage: /// - /// var driver = NetworkDriver.Create(); + /// var driver = new NetworkDriver.Create(); /// /// public struct NetworkDriver : IDisposable @@ -38,15 +36,14 @@ public Concurrent ToConcurrent() { return new Concurrent { - m_NetworkSendInterface = m_NetworkSendInterface, - m_NetworkProtocolInterface = m_NetworkProtocolInterface, m_EventQueue = m_EventQueue.ToConcurrent(), - m_ConnectionList = m_ConnectionList, - m_DataStream = m_DataStream, + m_ConnectionList = m_NetworkStack.Connections, m_DisconnectReasons = m_DisconnectReasons, m_PipelineProcessor = m_PipelineProcessor.ToConcurrent(), m_DefaultHeaderFlags = m_DefaultHeaderFlags, - m_ConcurrentParallelSendQueue = m_ParallelSendQueue.AsParallelWriter(), + m_DriverSender = m_DriverSender.ToConcurrent(), + m_DriverReceiver = m_DriverReceiver, + m_PacketPadding = m_NetworkStack.PacketPadding, #if ENABLE_UNITY_COLLECTIONS_CHECKS m_ThreadIndex = 0, m_PendingBeginSend = m_PendingBeginSend @@ -58,15 +55,14 @@ private Concurrent ToConcurrentSendOnly() { return new Concurrent { - m_NetworkSendInterface = m_NetworkSendInterface, - m_NetworkProtocolInterface = m_NetworkProtocolInterface, m_EventQueue = default, - m_ConnectionList = m_ConnectionList, - m_DataStream = m_DataStream, + m_ConnectionList = m_NetworkStack.Connections, m_DisconnectReasons = m_DisconnectReasons, m_PipelineProcessor = m_PipelineProcessor.ToConcurrent(), m_DefaultHeaderFlags = m_DefaultHeaderFlags, - m_ConcurrentParallelSendQueue = m_ParallelSendQueue.AsParallelWriter(), + m_DriverSender = m_DriverSender.ToConcurrent(), + m_DriverReceiver = m_DriverReceiver, + m_PacketPadding = m_NetworkStack.PacketPadding, #if ENABLE_UNITY_COLLECTIONS_CHECKS m_ThreadIndex = 0, m_PendingBeginSend = m_PendingBeginSend @@ -75,56 +71,37 @@ private Concurrent ToConcurrentSendOnly() } /// - /// The Concurrent struct is used to create an Concurrent instance of the NetworkDriver. + /// The Concurrent struct is used to create an Concurrent instance of the GenericNetworkDriver. /// public struct Concurrent { - /// - /// Pops events for a connection using the specified connection id - /// - /// The connection id - /// Stream reader for the event's data. - /// The network event type public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader) { return PopEventForConnection(connectionId, out reader, out var _); } - /// - /// Pops events for a connection using the specified connection id - /// - /// The connection id - /// Stream reader for the event's data. - /// Pipeline on which the data event was received. - /// The type - public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader, out NetworkPipeline pipeline) + public NetworkEvent.Type PopEventForConnection(NetworkConnection connection, out DataStreamReader reader, out NetworkPipeline pipeline) { pipeline = default; reader = default; - if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length || - m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion) + if (m_ConnectionList.ConnectionAt(connection.InternalId) != connection.m_ConnectionId) return (int)NetworkEvent.Type.Empty; - var type = m_EventQueue.PopEventForConnection(connectionId.m_NetworkId, out var offset, out var size, out var pipelineId); + var type = m_EventQueue.PopEventForConnection(connection.InternalId, out var offset, out var size, out var pipelineId); pipeline = new NetworkPipeline { Id = pipelineId }; if (type == NetworkEvent.Type.Disconnect && offset < 0) reader = new DataStreamReader(m_DisconnectReasons.GetSubArray(math.abs(offset), 1)); else if (size > 0) - reader = new DataStreamReader(((NativeArray)m_DataStream).GetSubArray(offset, size)); + reader = new DataStreamReader(m_DriverReceiver.GetDataStreamSubArray(offset, size)); return type; } - /// - /// Max headersize including a - /// - /// The pipeline with which to get the maximum header size. - /// The header size public int MaxHeaderSize(NetworkPipeline pipe) { - var headerSize = m_NetworkProtocolInterface.PaddingSize; + var headerSize = m_PipelineProcessor.m_MaxPacketHeaderSize; if (pipe.Id > 0) { // All headers plus one byte for pipeline id @@ -134,11 +111,6 @@ public int MaxHeaderSize(NetworkPipeline pipe) return headerSize; } - internal int MaxProtocolHeaderSize() - { - return m_NetworkProtocolInterface.PaddingSize; - } - struct PendingSend { public NetworkPipeline Pipeline; @@ -174,28 +146,26 @@ public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id, { writer = default; - if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length) + if (id.InternalId < 0 || id.InternalId >= m_ConnectionList.Count) return (int)Error.StatusCode.NetworkIdMismatch; - var connection = m_ConnectionList[id.m_NetworkId]; - if (connection.Version != id.m_NetworkVersion) + var connection = m_ConnectionList.ConnectionAt(id.InternalId); + if (connection.Version != id.Version) return (int)Error.StatusCode.NetworkVersionMismatch; - if (connection.State != NetworkConnection.State.Connected) + if (m_ConnectionList.GetConnectionState(connection) != NetworkConnection.State.Connected) return (int)Error.StatusCode.NetworkStateMismatch; var pipelineHeader = (pipe.Id > 0) ? m_PipelineProcessor.SendHeaderCapacity(pipe) + 1 : 0; var pipelinePayloadCapacity = m_PipelineProcessor.PayloadCapacity(pipe); - var protocolOverhead = m_NetworkProtocolInterface.ComputePacketOverhead.Ptr.Invoke(ref connection, out var payloadOffset); - // If the pipeline doesn't have an explicity payload capacity, then use whatever // will fit inside the MTU (considering protocol and pipeline overhead). If there is // an explicity pipeline payload capacity we use that directly. Right now only // fragmented pipelines have an explicity capacity, and we want users to be able to // rely on this configured value. var payloadCapacity = pipelinePayloadCapacity == 0 - ? NetworkParameterConstants.MTU - protocolOverhead - pipelineHeader + ? NetworkParameterConstants.MTU - m_PacketPadding - pipelineHeader : pipelinePayloadCapacity; // Total capacity is the full size of the buffer we'll allocate. Without an explicit @@ -203,7 +173,7 @@ public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id, // capacity plus whatever overhead we need to transmit the packet. var totalCapacity = pipelinePayloadCapacity == 0 ? NetworkParameterConstants.MTU - : pipelinePayloadCapacity + protocolOverhead + pipelineHeader; + : pipelinePayloadCapacity + m_PacketPadding + pipelineHeader; // Check if we can accomodate the user's required payload size. if (payloadCapacity < requiredPayloadSize) @@ -219,8 +189,8 @@ public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id, totalCapacity -= extraCapacity; } - var result = 0; - if ((result = m_NetworkSendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, m_NetworkSendInterface.UserData, totalCapacity)) != 0) + var sendHandle = default(NetworkInterfaceSendHandle); + if (totalCapacity > NetworkParameterConstants.MTU) { sendHandle.data = (IntPtr)UnsafeUtility.Malloc(totalCapacity, 8, Allocator.Temp); sendHandle.capacity = totalCapacity; @@ -228,11 +198,19 @@ public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id, sendHandle.size = 0; sendHandle.flags = SendHandleFlags.AllocatedByDriver; } + else + { + var result = 0; + if ((result = m_DriverSender.BeginSend(out sendHandle, (uint)totalCapacity)) != 0) + { + return result; + } + } if (sendHandle.capacity < totalCapacity) return (int)Error.StatusCode.NetworkPacketOverflow; - var slice = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray((byte*)sendHandle.data + payloadOffset + pipelineHeader, payloadCapacity, Allocator.Invalid); + var slice = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray((byte*)sendHandle.data + m_PacketPadding + pipelineHeader, payloadCapacity, Allocator.Invalid); #if ENABLE_UNITY_COLLECTIONS_CHECKS var safety = AtomicSafetyHandle.GetTempMemoryHandle(); @@ -245,7 +223,7 @@ public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id, Pipeline = pipe, Connection = id, SendHandle = sendHandle, - headerSize = payloadOffset, + headerSize = m_PacketPadding, }; #if ENABLE_UNITY_COLLECTIONS_CHECKS m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize / 4] = m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize / 4] + 1; @@ -277,7 +255,7 @@ public unsafe int EndSend(DataStreamWriter writer) #endif } - if (m_ConnectionList[pendingSendPtr->Connection.m_NetworkId].Version != pendingSendPtr->Connection.m_NetworkVersion) + if (m_ConnectionList.ConnectionAt(pendingSendPtr->Connection.InternalId).Version != pendingSendPtr->Connection.Version) { #if ENABLE_UNITY_COLLECTIONS_CHECKS throw new InvalidOperationException("Connection closed between begin and end send"); @@ -357,7 +335,7 @@ internal unsafe int CompleteSend(NetworkConnection sendConnection, NetworkInterf { var ret = 0; NetworkInterfaceSendHandle originalHandle = sendHandle; - if ((ret = m_NetworkSendInterface.BeginSendMessage.Ptr.Invoke(out sendHandle, m_NetworkSendInterface.UserData, originalHandle.size)) != 0) + if ((ret = m_DriverSender.BeginSend(out sendHandle, (uint)math.max(NetworkParameterConstants.MTU, originalHandle.size))) != 0) { return ret; } @@ -365,44 +343,48 @@ internal unsafe int CompleteSend(NetworkConnection sendConnection, NetworkInterf sendHandle.size = originalHandle.size; } - var connection = m_ConnectionList[sendConnection.m_NetworkId]; - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ConcurrentParallelSendQueue); - return m_NetworkProtocolInterface.ProcessSend.Ptr.Invoke(ref connection, hasPipeline, ref m_NetworkSendInterface, ref sendHandle, ref queueHandle, m_NetworkProtocolInterface.UserData); + var endpoint = m_ConnectionList.GetConnectionEndpoint(sendConnection.m_ConnectionId); + sendHandle.size -= m_PacketPadding; + var result = m_DriverSender.EndSend(ref endpoint, ref sendHandle, m_PacketPadding, sendConnection.m_ConnectionId); + + // TODO: We temporarily add always a pipeline id (even if it's 0) when using new Layers + if (!hasPipeline) + { + var packetProcessor = m_DriverSender.m_SendQueue.GetPacketProcessor(sendHandle.id); + packetProcessor.PrependToPayload((byte)0); + } + return result; } internal void AbortSend(NetworkInterfaceSendHandle sendHandle) { if (0 == (sendHandle.flags & SendHandleFlags.AllocatedByDriver)) { - m_NetworkSendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, m_NetworkSendInterface.UserData); + m_DriverSender.AbortSend(ref sendHandle); } } - /// - /// Gets the connection state using the specified id - /// - /// The connection id - /// The network connection state public NetworkConnection.State GetConnectionState(NetworkConnection id) { - if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length) + if (id.InternalId < 0 || id.InternalId >= m_ConnectionList.Count) return NetworkConnection.State.Disconnected; - var connection = m_ConnectionList[id.m_NetworkId]; - if (connection.Version != id.m_NetworkVersion) + var connection = m_ConnectionList.ConnectionAt(id.InternalId); + if (connection.Version != id.Version) return NetworkConnection.State.Disconnected; - return connection.State; + + var state = m_ConnectionList.GetConnectionState(connection); + return state == NetworkConnection.State.Disconnecting ? NetworkConnection.State.Disconnected : state; } - internal NetworkSendInterface m_NetworkSendInterface; - internal NetworkProtocol m_NetworkProtocolInterface; internal NetworkEventQueue.Concurrent m_EventQueue; internal NativeArray m_DisconnectReasons; - [ReadOnly] internal NativeList m_ConnectionList; - [ReadOnly] internal NativeList m_DataStream; + [ReadOnly] internal ConnectionList m_ConnectionList; internal NetworkPipelineProcessor.Concurrent m_PipelineProcessor; internal UdpCHeader.HeaderFlags m_DefaultHeaderFlags; - internal NativeQueue.ParallelWriter m_ConcurrentParallelSendQueue; + internal NetworkDriverSender.Concurrent m_DriverSender; + [ReadOnly] internal NetworkDriverReceiver m_DriverReceiver; + internal int m_PacketPadding; #if ENABLE_UNITY_COLLECTIONS_CHECKS [NativeSetThreadIndex] internal int m_ThreadIndex; @@ -410,66 +392,16 @@ public NetworkConnection.State GetConnectionState(NetworkConnection id) #endif } - internal struct Connection - { - public NetworkInterfaceEndPoint Address; - public long LastNonDataSend; - public long LastReceive; - public int Id; - public int Version; - public int ConnectAttempts; - public NetworkConnection.State State; - public SessionIdToken ReceiveToken; - public SessionIdToken SendToken; - public byte DidReceiveData; - public byte IsAccepted; - - public static bool operator==(Connection lhs, Connection rhs) - { - return lhs.Id == rhs.Id && lhs.Version == rhs.Version && lhs.Address == rhs.Address; - } - - public static bool operator!=(Connection lhs, Connection rhs) - { - return lhs.Id != rhs.Id || lhs.Version != rhs.Version || lhs.Address != rhs.Address; - } - - public override bool Equals(object compare) - { - return this == (Connection)compare; - } - - /// - /// Null Connection - /// - public static Connection Null => new Connection() {Id = 0, Version = 0}; - - public override int GetHashCode() - { - return Id; - } - - public bool Equals(Connection connection) - { - return connection.Id == Id && connection.Version == Version && connection.Address == Address; - } - } - // internal variables ::::::::::::::::::::::::::::::::::::::::::::::::: - static List s_NetworkInterfaces = new List(); - static List s_NetworkProtocols = new List(); - int m_NetworkInterfaceIndex; - NetworkSendInterface m_NetworkSendInterface; + internal NetworkStack m_NetworkStack; - internal INetworkInterface NetworkInterface => s_NetworkInterfaces[m_NetworkInterfaceIndex]; + NetworkDriverSender m_DriverSender; + NetworkDriverReceiver m_DriverReceiver; - int m_NetworkProtocolIndex; - NetworkProtocol m_NetworkProtocolInterface; + internal NetworkDriverReceiver Receiver => m_DriverReceiver; + internal NetworkEventQueue EventQueue => m_EventQueue; - internal INetworkProtocol NetworkProtocol => s_NetworkProtocols[m_NetworkProtocolIndex]; - - NativeQueue m_ParallelSendQueue; #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArray m_PendingBeginSend; #endif @@ -477,237 +409,126 @@ public bool Equals(Connection connection) NetworkEventQueue m_EventQueue; private NativeArray m_DisconnectReasons; - NativeQueue m_FreeList; - NativeQueue m_NetworkAcceptQueue; - NativeList m_ConnectionList; + [ReadOnly] NativeArray m_InternalState; [NativeDisableContainerSafetyRestriction] - NativeArray m_InternalState; - - private NativeReference m_ProtocolStatus; - internal int ProtocolStatus => m_ProtocolStatus.Value; - - NativeQueue m_PendingFree; - NativeArray m_ErrorCodes; - - enum ErrorCodeType - { - ReceiveError = 0, - SendError = 1, - NumErrorCodes - } - -#pragma warning disable 649 - struct Parameters - { - public NetworkDataStreamParameter dataStream; - public NetworkConfigParameter config; - - public Parameters(NetworkSettings settings) - { - dataStream = settings.GetDataStreamParameters(); - config = settings.GetNetworkConfigParameters(); - } - } -#pragma warning restore 649 + NativeArray m_UpdateTime; - private Parameters m_NetworkParams; - private NativeList m_DataStream; - private NativeArray m_DataStreamHead; + private NetworkConfigParameter m_NetworkParams; private NetworkPipelineProcessor m_PipelineProcessor; private UdpCHeader.HeaderFlags m_DefaultHeaderFlags; - private long m_UpdateTime; - private long m_UpdateTimeAdjustment; - - private Unity.Mathematics.Random m_Rand; + internal NetworkSettings m_NetworkSettings; - /// - /// Gets the value of the last update time - /// - public long LastUpdateTime => m_UpdateTime; + /// Current settings used by the driver. + /// + /// Current settings are read-only and can't be modified except through methods like + /// . + /// + public NetworkSettings CurrentSettings => m_NetworkSettings.AsReadOnly(); - // properties ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - private const int InternalStateListening = 0; - private const int InternalStateBound = 1; + private const int k_InternalStateListening = 0; + private const int k_InternalStateBound = 1; - /// - /// Gets or sets if the driver is Listening - /// public bool Listening { - get { return (m_InternalState[InternalStateListening] != 0); } - private set { m_InternalState[InternalStateListening] = value ? 1 : 0; } + get => m_InternalState[k_InternalStateListening] != 0; + private set => m_InternalState[k_InternalStateListening] = value ? 1 : 0; } - public bool Bound => m_InternalState[InternalStateBound] == 1; + public bool Bound => m_InternalState[k_InternalStateBound] == 1; + + private const int k_LastUpdateTime = 0; + private const int k_UpdateTimeAdjustment = 1; + + internal long LastUpdateTime => m_UpdateTime[k_LastUpdateTime]; /// /// Helper function for creating a NetworkDriver. /// - /// - /// The for the new NetworkDriver. - /// + /// Configuration for the driver. /// public static NetworkDriver Create(NetworkSettings settings) { -#if UNITY_WEBGL - return new NetworkDriver(new IPCNetworkInterface(), settings); +#if UNITY_WEBGL && !UNITY_EDITOR + return Create(new IPCNetworkInterface(), settings); #else - return new NetworkDriver(new BaselibNetworkInterface(), settings); + return Create(new UDPNetworkInterface(), settings); #endif } - /// - /// Helper function for creating a NetworkDriver. - /// public static NetworkDriver Create() => Create(new NetworkSettings(Allocator.Temp)); - /// - /// Helper function for creating a NetworkDriver. - /// - /// - /// The custom interface to use. - public static NetworkDriver Create(N networkInterface) where N : INetworkInterface - => Create(networkInterface, new NetworkSettings(Allocator.Temp)); - - /// - /// Helper function for creating a NetworkDriver. - /// - /// - /// The custom interface to use. - /// The for the new NetworkDriver. - public static NetworkDriver Create(N networkInterface, NetworkSettings settings) where N : INetworkInterface - => new NetworkDriver(networkInterface, settings); + public static NetworkDriver Create(N networkInterface) where N : unmanaged, INetworkInterface + => Create(ref networkInterface); - public NetworkDriver(INetworkInterface netIf) - : this(netIf, new NetworkSettings()) {} + public static NetworkDriver Create(ref N networkInterface) where N : unmanaged, INetworkInterface + => Create(ref networkInterface, new NetworkSettings(Allocator.Temp)); -#if !UNITY_DOTSRUNTIME - [Obsolete("Use Create(NetworkSettings) instead", false)] - public static NetworkDriver Create(params INetworkParameter[] param) - { - return Create(NetworkSettings.FromArray(param)); - } - - [Obsolete("Use NetworkDriver(INetworkInterface, NetworkSettings) instead", false)] - public NetworkDriver(INetworkInterface netIf, params INetworkParameter[] param) - : this(netIf, NetworkSettings.FromArray(param)) {} - - [Obsolete("Use NetworkDriver(INetworkInterface, NetworkSettings) instead", false)] - internal NetworkDriver(INetworkInterface netIf, INetworkProtocol netProtocol, params INetworkParameter[] param) - : this(netIf, netProtocol, NetworkSettings.FromArray(param)) {} -#endif + public static NetworkDriver Create(N networkInterface, NetworkSettings settings) where N : unmanaged, INetworkInterface + => Create(ref networkInterface, settings); - private static int InsertInAvailableIndex(List list, T element) + public static NetworkDriver Create(ref N networkInterface, NetworkSettings settings) where N : unmanaged, INetworkInterface { - var n = list.Count; - for (var i = 0; i < n; ++i) + var driver = default(NetworkDriver); + + driver.m_NetworkSettings = new NetworkSettings(settings, Allocator.Persistent); + driver.m_NetworkParams = settings.GetNetworkConfigParameters(); +#if !UNITY_WEBGL || UNITY_EDITOR + // Legacy support for baselib queue capacity parameters + #pragma warning disable 618 + if (settings.TryGet(out var baselibParameter)) { - if (list[i] == null) + if (driver.m_NetworkParams.sendQueueCapacity == NetworkParameterConstants.SendQueueCapacity && + driver.m_NetworkParams.receiveQueueCapacity == NetworkParameterConstants.ReceiveQueueCapacity) { - list[i] = element; - return i; + driver.m_NetworkParams.sendQueueCapacity = baselibParameter.sendQueueCapacity; + driver.m_NetworkParams.receiveQueueCapacity = baselibParameter.receiveQueueCapacity; } } - - list.Add(element); - return n; - } - - private static INetworkProtocol GetProtocolForParameters(NetworkSettings settings) - { - if (settings.TryGet(out _)) - return new Relay.RelayNetworkProtocol(); -#if ENABLE_MANAGED_UNITYTLS - if (settings.TryGet(out _)) - return new TLS.SecureNetworkProtocol(); + #pragma warning restore 618 #endif - return new UnityTransportProtocol(); - } - public NetworkDriver(INetworkInterface netIf, NetworkSettings settings) - : this(netIf, GetProtocolForParameters(settings), settings) {} + driver.m_NetworkStack = NetworkStack.CreateForSettings(ref networkInterface, ref settings, out var sendQueue, out var receiveQueue); - /// - /// Constructor for NetworkDriver. - /// - /// - /// An array of INetworkParameter. There are currently only two , - /// the and the . - /// - /// Thrown if the value for NetworkDataStreamParameter.size is smaller then zero. - /// Thrown if network interface couldn't be initialized. - internal NetworkDriver(INetworkInterface netIf, INetworkProtocol netProtocol, NetworkSettings settings) - { -#if UNITY_WEBGL - UnityEngine.Debug.LogWarning("Unity Transport is not currently supported in WebGL. NetworkDriver will likely not work as intended."); -#endif - - m_NetworkParams = new Parameters(settings); - - netProtocol.Initialize(settings); - m_NetworkProtocolIndex = InsertInAvailableIndex(s_NetworkProtocols, netProtocol); - m_NetworkProtocolInterface = netProtocol.CreateProtocolInterface(); - - m_NetworkInterfaceIndex = InsertInAvailableIndex(s_NetworkInterfaces, netIf); - - var result = netIf.Initialize(settings); - if (0 != result) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - throw new InvalidOperationException($"Failed to initialize the NetworkInterface. Error Code: {result}."); -#else - UnityEngine.Debug.LogError($"Failed to initialize the NetworkInterface. Error Code: {result}."); -#endif - } + driver.m_PipelineProcessor = new NetworkPipelineProcessor(settings, driver.m_NetworkStack.PacketPadding); - m_NetworkSendInterface = netIf.CreateSendInterface(); + driver.m_DriverSender = new NetworkDriverSender(sendQueue); + driver.m_DriverReceiver = new NetworkDriverReceiver(receiveQueue); - m_PipelineProcessor = new NetworkPipelineProcessor(settings); - m_ParallelSendQueue = new NativeQueue(Allocator.Persistent); #if ENABLE_UNITY_COLLECTIONS_CHECKS - m_PendingBeginSend = new NativeArray(JobsUtility.MaxJobThreadCount * JobsUtility.CacheLineSize / 4, Allocator.Persistent); + driver.m_PendingBeginSend = new NativeArray(JobsUtility.MaxJobThreadCount * JobsUtility.CacheLineSize / 4, Allocator.Persistent); #endif - var stopwatchTime = Stopwatch.GetTimestamp(); - var time = stopwatchTime / (Stopwatch.Frequency / 1000); - m_UpdateTime = m_NetworkParams.config.fixedFrameTimeMS > 0 ? 1 : time; - m_UpdateTimeAdjustment = 0; + driver.m_DefaultHeaderFlags = 0; - m_Rand = new Unity.Mathematics.Random((uint)stopwatchTime); + driver.m_EventQueue = new NetworkEventQueue(NetworkParameterConstants.InitialEventQueueSize); - int initialStreamSize = m_NetworkParams.dataStream.size; - if (initialStreamSize == 0) - initialStreamSize = NetworkParameterConstants.DriverDataStreamSize; - - m_DataStream = new NativeList(initialStreamSize, Allocator.Persistent); - m_DataStream.ResizeUninitialized(initialStreamSize); - m_DataStreamHead = new NativeArray(1, Allocator.Persistent); - - m_DefaultHeaderFlags = 0; + const int reasons = (int)DisconnectReason.Count; + driver.m_DisconnectReasons = new NativeArray(reasons, Allocator.Persistent); + for (var idx = 0; idx < reasons; ++idx) + driver.m_DisconnectReasons[idx] = (byte)idx; - m_NetworkAcceptQueue = new NativeQueue(Allocator.Persistent); + driver.m_InternalState = new NativeArray(2, Allocator.Persistent); - m_ConnectionList = new NativeList(1, Allocator.Persistent); + driver.m_UpdateTime = new NativeArray(2, Allocator.Persistent); - m_FreeList = new NativeQueue(Allocator.Persistent); - m_EventQueue = new NetworkEventQueue(NetworkParameterConstants.InitialEventQueueSize); + var time = TimerHelpers.GetCurrentTimestampMS(); + driver.m_UpdateTime[k_LastUpdateTime] = driver.m_NetworkParams.fixedFrameTimeMS > 0 ? 1 : time; + driver.m_UpdateTime[k_UpdateTimeAdjustment] = 0; - const int reasons = (int)DisconnectReason.Count; - m_DisconnectReasons = new NativeArray(reasons, Allocator.Persistent); - for (var idx = 0; idx < reasons; ++idx) - m_DisconnectReasons[idx] = (byte)idx; + driver.Listening = false; - m_InternalState = new NativeArray(2, Allocator.Persistent); - m_PendingFree = new NativeQueue(Allocator.Persistent); + return driver; + } - m_ProtocolStatus = new NativeReference(Allocator.Persistent); - m_ProtocolStatus.Value = 0; + [Obsolete("Use NetworkDriver.Create(INetworkInterface networkInterface) instead", true)] + public NetworkDriver(INetworkInterface netIf) + => throw new NotImplementedException(); - m_ErrorCodes = new NativeArray((int)ErrorCodeType.NumErrorCodes, Allocator.Persistent); - Listening = false; - } + [Obsolete("Use NetworkDriver.Create(INetworkInterface networkInterface, NetworkSettings settings) instead", true)] + public NetworkDriver(INetworkInterface netIf, NetworkSettings settings) + => throw new NotImplementedException(); // interface implementation ::::::::::::::::::::::::::::::::::::::::::: public void Dispose() @@ -715,38 +536,25 @@ public void Dispose() if (!IsCreated) return; - s_NetworkProtocols[m_NetworkProtocolIndex].Dispose(); - s_NetworkProtocols[m_NetworkProtocolIndex] = null; + m_NetworkStack.Dispose(); - s_NetworkInterfaces[m_NetworkInterfaceIndex].Dispose(); - s_NetworkInterfaces[m_NetworkInterfaceIndex] = null; + m_DriverSender.Dispose(); + m_DriverReceiver.Dispose(); - m_NetworkProtocolIndex = -1; - m_NetworkInterfaceIndex = -1; + m_NetworkSettings.Dispose(); - m_DataStream.Dispose(); - m_DataStreamHead.Dispose(); m_PipelineProcessor.Dispose(); m_EventQueue.Dispose(); m_DisconnectReasons.Dispose(); - m_NetworkAcceptQueue.Dispose(); - m_ConnectionList.Dispose(); - m_FreeList.Dispose(); m_InternalState.Dispose(); - m_PendingFree.Dispose(); - m_ProtocolStatus.Dispose(); - m_ErrorCodes.Dispose(); - m_ParallelSendQueue.Dispose(); + m_UpdateTime.Dispose(); #if ENABLE_UNITY_COLLECTIONS_CHECKS m_PendingBeginSend.Dispose(); #endif } - /// - /// Returns if NetworkDriver has been created - /// public bool IsCreated => m_InternalState.IsCreated; [BurstCompile] @@ -763,23 +571,22 @@ public void Execute() [BurstCompile] struct ClearEventQueue : IJob { - public NativeList dataStream; - public NativeArray dataStreamHead; public NetworkEventQueue eventQueue; + public NetworkDriverReceiver driverReceiver; #if ENABLE_UNITY_COLLECTIONS_CHECKS public NativeArray pendingSend; - [ReadOnly] public NativeList connectionList; - [ReadOnly] public NativeArray internalState; + [ReadOnly] public ConnectionList connectionList; + public long listenState; #endif public void Execute() { #if ENABLE_UNITY_COLLECTIONS_CHECKS - for (int i = 0; i < connectionList.Length; ++i) + for (int i = 0; i < connectionList.Count; ++i) { int conCount = eventQueue.GetCountForConnection(i); - if (conCount != 0 && connectionList[i].State != NetworkConnection.State.Disconnected) + if (conCount != 0 && connectionList.GetConnectionState(connectionList.ConnectionAt(i)) != NetworkConnection.State.Disconnected) { - UnityEngine.Debug.LogError($"Resetting event queue with pending events (Count={conCount}, ConnectionID={i}) Listening: {internalState[InternalStateListening]}"); + UnityEngine.Debug.LogError($"Resetting event queue with pending events (Count={conCount}, ConnectionID={i}) Listening: {listenState}"); } } bool didPrint = false; @@ -798,48 +605,31 @@ public void Execute() } #endif eventQueue.Clear(); - dataStreamHead[0] = 0; + driverReceiver.ClearStream(); } } - private SessionIdToken GenerateRandomSessionIdToken(ref SessionIdToken token) - { - //SessionIdToken token = new SessionIdToken(); - for (uint i = 0; i < SessionIdToken.k_Length; ++i) - { - unsafe - { - token.Value[i] = (byte)(m_Rand.NextUInt() & 0xFF); - } - } - return token; - } - private void UpdateLastUpdateTime() { - var stopwatchTime = Stopwatch.GetTimestamp(); - long now = m_NetworkParams.config.fixedFrameTimeMS > 0 - ? m_UpdateTime + m_NetworkParams.config.fixedFrameTimeMS - : stopwatchTime / (Stopwatch.Frequency / 1000) - m_UpdateTimeAdjustment; - - m_Rand.InitState((uint)stopwatchTime); + long now = m_NetworkParams.fixedFrameTimeMS > 0 + ? LastUpdateTime + m_NetworkParams.fixedFrameTimeMS + : TimerHelpers.GetCurrentTimestampMS() - m_UpdateTime[k_UpdateTimeAdjustment]; - long frameTime = now - m_UpdateTime; - if (m_NetworkParams.config.maxFrameTimeMS > 0 && frameTime > m_NetworkParams.config.maxFrameTimeMS) + long frameTime = now - LastUpdateTime; + if (m_NetworkParams.maxFrameTimeMS > 0 && frameTime > m_NetworkParams.maxFrameTimeMS) { - m_UpdateTimeAdjustment += frameTime - m_NetworkParams.config.maxFrameTimeMS; - now = m_UpdateTime + m_NetworkParams.config.maxFrameTimeMS; + m_UpdateTime[k_UpdateTimeAdjustment] += frameTime - m_NetworkParams.maxFrameTimeMS; + now = LastUpdateTime + m_NetworkParams.maxFrameTimeMS; } - m_UpdateTime = now; + unsafe + { + var ptr = (long *)m_UpdateTime.GetUnsafePtr(); + Interlocked.Exchange(ref ptr[k_LastUpdateTime], now); + } } - /// - /// Schedules update for driver. This should be called once a frame. - /// - /// Job on which to depend. - /// The update job's handle - public JobHandle ScheduleUpdate(JobHandle dep = default) + public JobHandle ScheduleUpdate(JobHandle dependency = default) { UpdateLastUpdateTime(); @@ -848,66 +638,45 @@ public JobHandle ScheduleUpdate(JobHandle dep = default) // Clearing the event queue and receiving/sending data only makes sense if we're bound. if (Bound) { + var connections = m_NetworkStack.Connections; + var clearJob = new ClearEventQueue { - dataStream = m_DataStream, - dataStreamHead = m_DataStreamHead, eventQueue = m_EventQueue, + driverReceiver = m_DriverReceiver, #if ENABLE_UNITY_COLLECTIONS_CHECKS pendingSend = m_PendingBeginSend, - connectionList = m_ConnectionList, - internalState = m_InternalState + connectionList = connections, + listenState = m_InternalState[k_InternalStateListening] #endif }; - var handle = clearJob.Schedule(dep); + var handle = clearJob.Schedule(dependency); handle = updateJob.Schedule(handle); - handle = s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleReceive(new NetworkPacketReceiver {m_Driver = this}, handle); - handle = s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleSend(m_ParallelSendQueue, handle); + handle = m_NetworkStack.ScheduleReceive(ref m_DriverReceiver, ref connections, ref m_EventQueue, ref m_PipelineProcessor, LastUpdateTime, handle); + handle = m_NetworkStack.ScheduleSend(ref m_DriverSender, LastUpdateTime, handle); return handle; } else { - return updateJob.Schedule(dep); + return updateJob.Schedule(dependency); } } - /// - /// Schedules flushing the sendqueue. Should be called in cases where you want the driver to send before the - /// next is called. - /// - /// Job on which to depend. - /// The job handle - public JobHandle ScheduleFlushSend(JobHandle dep) + public JobHandle ScheduleFlushSend(JobHandle dependency) { - return s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleSend(m_ParallelSendQueue, dep); + return m_NetworkStack.ScheduleSend(ref m_DriverSender, LastUpdateTime, dependency); } void InternalUpdate() { - m_PipelineProcessor.Timestamp = m_UpdateTime; - while (m_PendingFree.TryDequeue(out var free)) - { - int ver = m_ConnectionList[free].Version + 1; - if (ver == 0) - ver = 1; - m_ConnectionList[free] = new Connection {Id = free, Version = ver, IsAccepted = 0}; - m_FreeList.Enqueue(free); - } - - CheckTimeouts(); - - if (m_NetworkProtocolInterface.NeedsUpdate) - { - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.Update.Ptr.Invoke(m_UpdateTime, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - } + m_PipelineProcessor.Timestamp = LastUpdateTime; - m_PipelineProcessor.UpdateReceive(this, out var updateCount); + m_PipelineProcessor.UpdateReceive(ref this, out var updateCount); // TODO: Find a good way to establish a good limit (connections*pipelines/2?) - if (updateCount > (m_ConnectionList.Length - m_FreeList.Count) * 64) + if (updateCount > (m_NetworkStack.Connections.Count - m_NetworkStack.Connections.FreeList.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)); @@ -915,32 +684,101 @@ void InternalUpdate() m_DefaultHeaderFlags = UdpCHeader.HeaderFlags.HasPipeline; m_PipelineProcessor.UpdateSend(ToConcurrentSendOnly(), out updateCount); - if (updateCount > (m_ConnectionList.Length - m_FreeList.Count) * 64) + if (updateCount > (m_NetworkStack.Connections.Count - m_NetworkStack.Connections.FreeList.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)); } m_DefaultHeaderFlags = 0; + + // Drop incoming connections if not listening. If we're bound but are not listening + // (say because the user never called Listen or because they called StopListening), + // clients can still establish connections and these connections will be perfectly + // valid from their point of view, except that the server will never answer anything. + if (!Listening) + { + ConnectionId connectionId; + while ((connectionId = m_NetworkStack.Connections.AcceptConnection()) != default) + { + Disconnect(new NetworkConnection(connectionId)); + } + } } - /// - /// Create a new pipeline. - /// - /// - /// An array of stages the pipeline should contain. - /// - /// If the driver is not created properly - /// A connection has already been established + /// Register a custom pipeline stage. + /// + /// Can only be called before a driver is bound (see ). + /// + /// Note that the default pipeline stages (, + /// , , + /// and ) don't need to be registered. Registering a + /// pipeline stage is only required for custom ones. + /// + /// The type of the pipeline stage (must be unmanaged). + /// An instance of the pipeline stage. + /// + /// If collections checks are enabled (ENABLE_UNITY_COLLECTIONS_CHECKS is defined), will be + /// thrown if called after the driver is bound or before it is created. + /// + public void RegisterPipelineStage(T stage) where T : unmanaged, INetworkPipelineStage + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsCreated) + throw new InvalidOperationException("Driver must be constructed before registering pipeline stages."); + if (Bound) + throw new InvalidOperationException("Can't register a pipeline stage after the driver is bound."); +#endif + m_PipelineProcessor.RegisterPipelineStage(stage, m_NetworkSettings); + } + + /// Create a new pipeline from stage types. + /// + /// The order of the different stages is important, as that is the order in which the stages + /// will process a packet when sending messages (the reverse order is used when processing + /// received packets). + /// + /// Array of stages the pipeline should contain. + /// + /// If collections checks are enabled (ENABLE_UNITY_COLLECTIONS_CHECKS is defined), will be + /// thrown if called after the driver has established connections or before it is created. + /// public NetworkPipeline CreatePipeline(params Type[] stages) { #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (!m_InternalState.IsCreated) - throw new InvalidOperationException( - "Driver must be constructed with a populated or empty INetworkParameter params list"); - if (m_ConnectionList.Length > 0) - throw new InvalidOperationException( - "Pipelines cannot be created after establishing connections"); + if (!IsCreated) + throw new InvalidOperationException("Driver must be constructed before creating pipelines."); + if (m_NetworkStack.Connections.Count > 0) + throw new InvalidOperationException("Pipelines can't be created after establishing connections."); +#endif + var stageIds = new NativeArray(stages.Length, Allocator.Temp); + for (int i = 0; i < stages.Length; i++) + stageIds[i] = NetworkPipelineStageId.Get(stages[i]); + return CreatePipeline(stageIds); + } + + /// Create a new pipeline from stage IDs. + /// + /// The order of the different stages is important, as that is the order in which the stages + /// will process a packet when sending messages (the reverse order is used when processing + /// received packets). + /// + /// Note that this method is Burst-compatible. Note also that no reference to the native + /// array is kept internally by the driver. It is thus safe to dispose of it immediately + /// after calling this method (or to use a temporary allocation for the array). + /// + /// Array of stage IDs the pipeline should contain. + /// + /// If collections checks are enabled (ENABLE_UNITY_COLLECTIONS_CHECKS is defined), will be + /// thrown if called after the driver has established connections or before it is created. + /// + public NetworkPipeline CreatePipeline(NativeArray stages) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsCreated) + throw new InvalidOperationException("Driver must be constructed before creating pipelines."); + if (m_NetworkStack.Connections.Count > 0) + throw new InvalidOperationException("Pipelines can't be created after establishing connections."); #endif return m_PipelineProcessor.CreatePipeline(stages); } @@ -953,34 +791,28 @@ public NetworkPipeline CreatePipeline(params Type[] stages) /// If the driver is not created properly /// If bind is called more then once on the driver /// If bind is called after a connection has already been established - public int Bind(NetworkEndPoint endpoint) + public int Bind(NetworkEndpoint endpoint) { - if (s_NetworkInterfaces[m_NetworkInterfaceIndex].CreateInterfaceEndPoint(endpoint, out var ifEndPoint) != 0) - { - return -1; - } - #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (!m_InternalState.IsCreated) + if (!IsCreated) throw new InvalidOperationException( "Driver must be constructed with a populated or empty INetworkParameter params list"); // question: should this really be an error? - if (m_InternalState[InternalStateBound] != 0) + if (Bound) throw new InvalidOperationException( "Bind can only be called once per NetworkDriver"); - if (m_ConnectionList.Length > 0) + if (m_NetworkStack.Connections.Count > 0) throw new InvalidOperationException( "Bind cannot be called after establishing connections"); #endif - var protocolBind = s_NetworkProtocols[m_NetworkProtocolIndex].Bind(s_NetworkInterfaces[m_NetworkInterfaceIndex], ref ifEndPoint); + var result = m_NetworkStack.Bind(ref endpoint); + m_InternalState[k_InternalStateBound] = result == 0 ? 1 : 0; - m_InternalState[InternalStateBound] = protocolBind == 0 ? 1 : 0; - - return protocolBind; + return result; } /// - /// Set the driver to Listen for incoming connections + /// Set the driver to Listen for incomming connections /// /// Returns 0 on success. /// If the driver is not created properly @@ -989,7 +821,7 @@ public int Bind(NetworkEndPoint endpoint) public int Listen() { #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (!m_InternalState.IsCreated) + if (!IsCreated) throw new InvalidOperationException( "Driver must be constructed with a populated or empty INetworkParameter params list"); @@ -1002,30 +834,31 @@ public int Listen() #endif if (!Bound) return -1; - var ret = s_NetworkInterfaces[m_NetworkInterfaceIndex].Listen(); + var ret = m_NetworkStack.Listen(); if (ret == 0) Listening = true; return ret; } + // Offered as a workaround for DOTS until we support Burst-compatible constructors/dispose. + // This is not something we want users to be able to do normally. See MTT-2607. + [Obsolete("The correct way to stop listening is disposing of the driver (and recreating a new one).")] + internal void StopListening() + { + Listening = false; + } + /// /// Checks to see if there are any new connections to Accept. /// - /// If accept fails it returns a default NetworkConnection. + /// If accept fails it returnes a default NetworkConnection. public NetworkConnection Accept() { if (!Listening) return default; - if (!m_NetworkAcceptQueue.TryDequeue(out var id)) - return default; - - var connection = m_ConnectionList[id]; - connection.State = NetworkConnection.State.Connected; - connection.IsAccepted = 1; - SetConnection(connection); - - return new NetworkConnection {m_NetworkId = id, m_NetworkVersion = m_ConnectionList[id].Version}; + var connectionId = m_NetworkStack.Connections.AcceptConnection(); + return new NetworkConnection(connectionId); } /// @@ -1033,58 +866,26 @@ public NetworkConnection Accept() /// /// If connect fails it returns a default NetworkConnection. /// If the driver is not created properly - public NetworkConnection Connect(NetworkEndPoint endpoint) + public NetworkConnection Connect(NetworkEndpoint endpoint) { #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (!m_InternalState.IsCreated) + if (!IsCreated) throw new InvalidOperationException( "Driver must be constructed with a populated or empty INetworkParameter params list"); #endif if (!Bound) { - var nep = endpoint.Family == NetworkFamily.Ipv6 ? NetworkEndPoint.AnyIpv6 : NetworkEndPoint.AnyIpv4; + var nep = endpoint.Family == NetworkFamily.Ipv6 ? NetworkEndpoint.AnyIpv6 : NetworkEndpoint.AnyIpv4; if (Bind(nep) != 0) return default; } - var result = s_NetworkProtocols[m_NetworkProtocolIndex].CreateConnectionAddress( - s_NetworkInterfaces[m_NetworkInterfaceIndex], endpoint, out var address); - if (result != 0) - return default; - - if (!m_FreeList.TryDequeue(out var id)) - { - id = m_ConnectionList.Length; - m_ConnectionList.Add(new Connection {Id = id, Version = 1}); - } - - int ver = m_ConnectionList[id].Version; - var receiveToken = new SessionIdToken(); - GenerateRandomSessionIdToken(ref receiveToken); - var c = new Connection - { - Id = id, - Version = ver, - State = NetworkConnection.State.Connecting, - Address = address, - ConnectAttempts = 1, - LastNonDataSend = m_UpdateTime, - LastReceive = 0, - SendToken = default, - ReceiveToken = receiveToken, - IsAccepted = 0 - }; - - SetConnection(c); - var netcon = new NetworkConnection {m_NetworkId = id, m_NetworkVersion = ver}; + var connectionId = m_NetworkStack.Connections.StartConnecting(ref endpoint); + var networkConnection = new NetworkConnection(connectionId); - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.Connect.Ptr.Invoke(ref c, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - - m_PipelineProcessor.initializeConnection(netcon); - - return netcon; + m_PipelineProcessor.InitializeConnection(networkConnection); + return networkConnection; } /// @@ -1094,16 +895,12 @@ public NetworkConnection Connect(NetworkEndPoint endpoint) /// Return 0 on success. public int Disconnect(NetworkConnection id) { - Connection connection; - if ((connection = GetConnection(id)) == Connection.Null) - return 0; + var connectionState = GetConnectionState(id); - if (connection.State == NetworkConnection.State.Connected) + if (connectionState != NetworkConnection.State.Disconnected) { - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.Disconnect.Ptr.Invoke(ref connection, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); + m_NetworkStack.Connections.StartDisconnecting(ref id.m_ConnectionId); } - RemoveConnection(connection); return 0; } @@ -1111,17 +908,16 @@ public int Disconnect(NetworkConnection id) /// /// Returns the PipelineBuffers for a specific pipeline and stage. /// - /// Pipeline for which to get the buffers. - /// Pipeline for which to get the buffers. - /// Connection for which to the buffers. - /// The buffer used to process read (receive) operations. - /// The buffer used to process write (send) operations. - /// The buffer containing the internal state of the pipeline stage. + /// + /// + /// + /// + /// + /// /// If the the connection is invalid. public void GetPipelineBuffers(NetworkPipeline pipeline, NetworkPipelineStageId stageId, NetworkConnection connection, out NativeArray readProcessingBuffer, out NativeArray writeProcessingBuffer, out NativeArray sharedBuffer) { - if (connection.m_NetworkId < 0 || connection.m_NetworkId >= m_ConnectionList.Length || - m_ConnectionList[connection.m_NetworkId].Version != connection.m_NetworkVersion) + if (m_NetworkStack.Connections.ConnectionAt(connection.InternalId) != connection.m_ConnectionId) { #if ENABLE_UNITY_COLLECTIONS_CHECKS throw new InvalidOperationException("Invalid connection"); @@ -1136,101 +932,71 @@ public void GetPipelineBuffers(NetworkPipeline pipeline, NetworkPipelineStageId m_PipelineProcessor.GetPipelineBuffers(pipeline, stageId, connection, out readProcessingBuffer, out writeProcessingBuffer, out sharedBuffer); } - /// - /// Gets the connection state using the specified - /// - /// The connection - /// The network connection state - public NetworkConnection.State GetConnectionState(NetworkConnection con) + // Keeping the pipeline parameter there to avoid breaking DOTS. + /// + internal unsafe T* GetWriteablePipelineParameter(NetworkPipeline pipeline, NetworkPipelineStageId stageId) + where T : unmanaged, INetworkParameter { - Connection connection; - if ((connection = GetConnection(con)) == Connection.Null) - return NetworkConnection.State.Disconnected; - return connection.State; + return m_PipelineProcessor.GetWriteablePipelineParameter(stageId); } - public NetworkEndPoint RemoteEndPoint(NetworkConnection id) + public NetworkConnection.State GetConnectionState(NetworkConnection con) { - if (id == default) - return default; + var state = m_NetworkStack.Connections.GetConnectionState(con.m_ConnectionId); + return state == NetworkConnection.State.Disconnecting ? NetworkConnection.State.Disconnected : state; + } - Connection connection; - if ((connection = GetConnection(id)) == Connection.Null) - return default; - return s_NetworkProtocols[m_NetworkProtocolIndex].GetRemoteEndPoint(s_NetworkInterfaces[m_NetworkInterfaceIndex], connection.Address); + [Obsolete("RemoteEndPoint has been renamed to GetRemoteEndpoint. (UnityUpgradable) -> GetRemoteEndpoint(*)", false)] + public NetworkEndpoint RemoteEndPoint(NetworkConnection id) + { + return m_NetworkStack.Connections.GetConnectionEndpoint(id.m_ConnectionId); } /// - /// Returns local + /// Get the remote endpoint of a connection (the endpoint used to reach the remote peer on the connection). /// - /// The network end point - public NetworkEndPoint LocalEndPoint() + /// Connection to get the endpoint of. + /// The remote endpoint of the connection. + public NetworkEndpoint GetRemoteEndpoint(NetworkConnection id) { - var ep = s_NetworkInterfaces[m_NetworkInterfaceIndex].LocalEndPoint; - return s_NetworkInterfaces[m_NetworkInterfaceIndex].GetGenericEndPoint(ep); + return m_NetworkStack.Connections.GetConnectionEndpoint(id.m_ConnectionId); + } + + [Obsolete("LocalEndPoint has been renamed to GetLocalEndpoint. (UnityUpgradable) -> GetLocalEndpoint()", false)] + public NetworkEndpoint LocalEndPoint() + { + return m_NetworkStack.GetLocalEndpoint(); } /// - /// Max headersize including optional + /// Get the local endpoint used by the driver (the endpoint remote peers will use to reach this driver). /// - /// The pipeline for which to get the maximum header size. - /// The maximum header size. - public int MaxHeaderSize(NetworkPipeline pipe) + /// The local endpoint of the driver. + public NetworkEndpoint GetLocalEndpoint() { - return ToConcurrentSendOnly().MaxHeaderSize(pipe); + return m_NetworkStack.GetLocalEndpoint(); } - internal int MaxProtocolHeaderSize() + public int MaxHeaderSize(NetworkPipeline pipe) { - return m_NetworkProtocolInterface.PaddingSize; + return ToConcurrentSendOnly().MaxHeaderSize(pipe); } - /// - /// Acquires a DataStreamWriter for starting a asynchronous send. - /// - /// The NetworkPipeline to write through - /// The NetworkConnection id to write through - /// A DataStreamWriter to write to - /// If you require the payload to be of certain size - /// Returns on a successful acquire. Otherwise returns an indicating the error. - /// Will throw a if the connection is in a Connecting state. public int BeginSend(NetworkPipeline pipe, NetworkConnection id, out DataStreamWriter writer, int requiredPayloadSize = 0) { return ToConcurrentSendOnly().BeginSend(pipe, id, out writer, requiredPayloadSize); } - /// - /// Acquires a DataStreamWriter for starting a asynchronous send. - /// - /// The NetworkConnection id to write through - /// A DataStreamWriter to write to - /// If you require the payload to be of certain size - /// Returns on a successful acquire. Otherwise returns an indicating the error. - /// Will throw a if the connection is in a Connecting state. public int BeginSend(NetworkConnection id, out DataStreamWriter writer, int requiredPayloadSize = 0) { return ToConcurrentSendOnly().BeginSend(NetworkPipeline.Null, id, out writer, requiredPayloadSize); } - /// - /// Ends a asynchronous send. - /// - /// If you require the payload to be of certain size. - /// The length of the buffer sent if nothing went wrong. - /// If endsend is called with a matching BeginSend call. - /// If the connection got closed between the call of being and end send. public int EndSend(DataStreamWriter writer) { return ToConcurrentSendOnly().EndSend(writer); } - /// - /// Aborts a asynchronous send. - /// - /// If you require the payload to be of certain size. - /// The length of the buffer sent if nothing went wrong. - /// If endsend is called with a matching BeginSend call. - /// If the connection got closed between the call of being and end send. public void AbortSend(DataStreamWriter writer) { ToConcurrentSendOnly().AbortSend(writer); @@ -1239,9 +1005,8 @@ public void AbortSend(DataStreamWriter writer) /// /// Pops an event /// - /// Connection on which the event occured. - /// Stream reader for the event's data. - /// The event's type + /// + /// /// Returns the type of event received, if the value is a event /// then the DataStreamReader will contain the disconnect reason. If a listening NetworkDriver has received Data /// events from a client, but the NetworkDriver has not Accepted the NetworkConnection yet, the Data event will @@ -1251,13 +1016,6 @@ public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReade return PopEvent(out con, out reader, out var _); } - /// - /// Pops an event - /// - /// Connection on which the event occured. - /// Stream reader for the event's data. - /// Pipeline on which the event was received. - /// The event's type public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader reader, out NetworkPipeline pipeline) { reader = default; @@ -1271,12 +1029,11 @@ public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReade while (true) { type = m_EventQueue.PopEvent(out id, out offset, out size, out pipelineId); + var connectionId = m_NetworkStack.Connections.ConnectionAt(id); //This is in service of not providing any means for a server's / listening NetworkDriver's user-level code to obtain a NetworkConnection handle - //that corresponds to an underlying Connection that lives in m_ConnectionList without having obtained it from Accept() first. This is a stopgap - //change for now to make NetworkDriver's API adhere to that idiom, but a more thorough and longer-term fix would be to properly implement some - //sort of TCP-style SYN/SYN-ACK/ACK handshake, or at least have Accept() be necessary prior to the listener sending a ConnectionAccept packet. - if (id >= 0 && type == NetworkEvent.Type.Data && m_ConnectionList[id].IsAccepted == 0) + //that corresponds to an underlying Connection that lives in m_NetworkStack.Connections without having obtained it from Accept() first. + if (id >= 0 && type == NetworkEvent.Type.Data && !m_NetworkStack.Connections.IsConnectionAccepted(ref connectionId)) { UnityEngine.Debug.LogWarning("A NetworkEvent.Data event was discarded for a connection that had not been accepted yet. To avoid this, consider calling Accept()" + " prior to PopEvent() in your project's network update loop, or only use PopEventForConnection() in conjunction with Accept()."); @@ -1291,10 +1048,10 @@ public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReade if (type == NetworkEvent.Type.Disconnect && offset < 0) reader = new DataStreamReader(m_DisconnectReasons.GetSubArray(math.abs(offset), 1)); else if (size > 0) - reader = new DataStreamReader(((NativeArray)m_DataStream).GetSubArray(offset, size)); + reader = new DataStreamReader(m_DriverReceiver.GetDataStreamSubArray(offset, size)); con = id < 0 ? default - : new NetworkConnection {m_NetworkId = id, m_NetworkVersion = m_ConnectionList[id].Version}; + : new NetworkConnection(m_NetworkStack.Connections.ConnectionAt(id)); return type; } @@ -1302,9 +1059,8 @@ public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReade /// /// Pops an event for a specific connection /// - /// Connection for which to pop the next event. - /// Stream reader for the event's data. - /// The event's type + /// + /// /// Returns the type of event received, if the value is a event /// then the DataStreamReader will contain the disconnect reason. public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader) @@ -1312,28 +1068,21 @@ public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, o return PopEventForConnection(connectionId, out reader, out var _); } - /// - /// Pops an event for a specific connection - /// - /// Connection for which to pop the next event. - /// Stream reader for the event's data. - /// Pipeline on which the event was received. - /// The event's type public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader, out NetworkPipeline pipeline) { reader = default; pipeline = default; - if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length || - m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion) + if (connectionId.InternalId < 0 || connectionId.InternalId >= m_NetworkStack.Connections.Count || + m_NetworkStack.Connections.ConnectionAt(connectionId.InternalId).Version != connectionId.Version) return (int)NetworkEvent.Type.Empty; - var type = m_EventQueue.PopEventForConnection(connectionId.m_NetworkId, out var offset, out var size, out var pipelineId); + var type = m_EventQueue.PopEventForConnection(connectionId.InternalId, out var offset, out var size, out var pipelineId); pipeline = new NetworkPipeline { Id = pipelineId }; if (type == NetworkEvent.Type.Disconnect && offset < 0) reader = new DataStreamReader(m_DisconnectReasons.GetSubArray(math.abs(offset), 1)); else if (size > 0) - reader = new DataStreamReader(((NativeArray)m_DataStream).GetSubArray(offset, size)); + reader = new DataStreamReader(m_DriverReceiver.GetDataStreamSubArray(offset, size)); return type; } @@ -1341,497 +1090,19 @@ public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, o /// /// Returns the size of the EventQueue for a specific connection /// - /// Connection for which to get the event queue size. + /// /// If the connection is valid it returns the size of the event queue otherwise it returns 0. public int GetEventQueueSizeForConnection(NetworkConnection connectionId) { - if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length || - m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion) + if (connectionId.InternalId < 0 || connectionId.InternalId >= m_NetworkStack.Connections.Count || + m_NetworkStack.Connections.ConnectionAt(connectionId.InternalId).Version != connectionId.Version) return 0; - return m_EventQueue.GetCountForConnection(connectionId.m_NetworkId); - } - - // internal helper functions :::::::::::::::::::::::::::::::::::::::::: - void AddConnectEvent(int id) - { - m_EventQueue.PushEvent(new NetworkEvent {connectionId = id, type = NetworkEvent.Type.Connect}); - } - - void AddDisconnectEvent(int id, Error.DisconnectReason reason = DisconnectReason.Default) - { - m_EventQueue.PushEvent(new NetworkEvent { connectionId = id, type = NetworkEvent.Type.Disconnect, status = (int)reason }); - } - - Connection GetConnection(NetworkConnection id) - { - if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length) - return Connection.Null; - - var con = m_ConnectionList[id.m_NetworkId]; - if (con.Version != id.m_NetworkVersion) - return Connection.Null; - return con; - } - - Connection GetConnection(NetworkInterfaceEndPoint address, SessionIdToken sessionId) - { - for (int i = 0; i < m_ConnectionList.Length; i++) - { - if (address == m_ConnectionList[i].Address && m_ConnectionList[i].ReceiveToken == sessionId) - return m_ConnectionList[i]; - } - - return Connection.Null; - } - - Connection GetNewConnection(NetworkInterfaceEndPoint address, SessionIdToken sessionId) - { - for (int i = 0; i < m_ConnectionList.Length; i++) - { - if (address == m_ConnectionList[i].Address && m_ConnectionList[i].SendToken == sessionId) - return m_ConnectionList[i]; - } - - return Connection.Null; - } - - void SetConnection(Connection connection) - { - m_ConnectionList[connection.Id] = connection; - } - - bool RemoveConnection(Connection connection) - { - if (connection.State != NetworkConnection.State.Disconnected && connection == m_ConnectionList[connection.Id]) - { - connection.State = NetworkConnection.State.Disconnected; - m_ConnectionList[connection.Id] = connection; - m_PendingFree.Enqueue(connection.Id); - - return true; - } - - return false; + return m_EventQueue.GetCountForConnection(connectionId.InternalId); } - void UpdateConnection(Connection connection) - { - if (connection == m_ConnectionList[connection.Id]) - SetConnection(connection); - } - - void CheckTimeouts() - { - for (int i = 0; i < m_ConnectionList.Length; ++i) - { - var connection = m_ConnectionList[i]; - if (connection == Connection.Null) - continue; - - long now = m_UpdateTime; - - var netcon = new NetworkConnection {m_NetworkId = connection.Id, m_NetworkVersion = connection.Version}; - - // Check for connect timeout and connection attemps. - // Note that while connecting, LastNonDataSend can only track connection requests. - if (connection.State == NetworkConnection.State.Connecting && - now - connection.LastNonDataSend > m_NetworkParams.config.connectTimeoutMS) - { - if (connection.ConnectAttempts >= m_NetworkParams.config.maxConnectAttempts) - { - Disconnect(netcon); - AddDisconnectEvent(connection.Id, DisconnectReason.MaxConnectionAttempts); - continue; - } - - connection.ConnectAttempts = ++connection.ConnectAttempts; - connection.LastNonDataSend = now; - SetConnection(connection); - - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.Connect.Ptr.Invoke( - ref connection, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - } - - // Check for the disconnect timeout. - if (connection.State == NetworkConnection.State.Connected && - now - connection.LastReceive > m_NetworkParams.config.disconnectTimeoutMS) - { - Disconnect(netcon); - AddDisconnectEvent(connection.Id, DisconnectReason.Timeout); - } - - // Check for the heartbeat timeout. - if (connection.State == NetworkConnection.State.Connected && - connection.DidReceiveData != 0 && - m_NetworkParams.config.heartbeatTimeoutMS > 0 && - now - connection.LastReceive > m_NetworkParams.config.heartbeatTimeoutMS && - now - connection.LastNonDataSend > m_NetworkParams.config.heartbeatTimeoutMS) - { - connection.LastNonDataSend = now; - SetConnection(connection); - - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.ProcessSendPing.Ptr.Invoke(ref connection, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - } - } - } - - /// - /// Gets or sets Receive Error Code - /// public int ReceiveErrorCode { - get => m_ErrorCodes[(int)ErrorCodeType.ReceiveError]; - internal set - { - if (value != 0) - { - UnityEngine.Debug.LogError(FixedString.Format("Error on receive, errorCode = {0}", value)); - } - m_ErrorCodes[(int)ErrorCodeType.ReceiveError] = value; - } - } - - internal bool IsAddressUsed(NetworkInterfaceEndPoint address) - { - for (int i = 0; i < m_ConnectionList.Length; i++) - { - if (address == m_ConnectionList[i].Address) - return true; - } - return false; - } - - internal void AppendPacket(IntPtr dataStream, ref NetworkInterfaceEndPoint endpoint, int dataLen) - { - var command = default(ProcessPacketCommand); - - var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter()); - m_NetworkProtocolInterface.ProcessReceive.Ptr.Invoke(dataStream, ref endpoint, dataLen, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData, ref command); - - switch (command.Type) - { - case ProcessPacketCommandType.AddressUpdate: - { - for (int i = 0; i < m_ConnectionList.Length; i++) - { - if (command.Address == m_ConnectionList[i].Address && command.SessionId == m_ConnectionList[i].ReceiveToken) - m_ConnectionList.ElementAt(i).Address = command.As.AddressUpdate.NewAddress; - } - } break; - - case ProcessPacketCommandType.ConnectionAccept: - { - Connection c = GetConnection(command.Address, command.SessionId); - - if (c != Connection.Null) - { - c.DidReceiveData = 1; - c.LastReceive = m_UpdateTime; - SetConnection(c); - - if (c.State == NetworkConnection.State.Connecting) - { - c.SendToken = command.As.ConnectionAccept.ConnectionToken; - - c.State = NetworkConnection.State.Connected; - c.IsAccepted = 1; - UpdateConnection(c); - AddConnectEvent(c.Id); - } - } - } break; - - case ProcessPacketCommandType.ConnectionReject: - break; - - case ProcessPacketCommandType.ConnectionRequest: - { - if (Listening == false) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogError(string.Format("ConnectionRequest received, but Listening == false. Address: [{0}]", command.Address.ToFixedString())); -#endif - return; - } - - Connection c = GetNewConnection(command.Address, command.SessionId); - if (c == Connection.Null || c.State == NetworkConnection.State.Disconnected) - { - var sessionId = new SessionIdToken(); - GenerateRandomSessionIdToken(ref sessionId); - if (!m_FreeList.TryDequeue(out var id)) - { - id = m_ConnectionList.Length; - m_ConnectionList.Add(new Connection {Id = id, Version = 1}); - } - - int ver = m_ConnectionList[id].Version; - c = new Connection - { - Id = id, - Version = ver, - ReceiveToken = sessionId, - SendToken = command.SessionId, - State = NetworkConnection.State.Connected, - Address = command.Address, - ConnectAttempts = 1, - LastReceive = m_UpdateTime, - IsAccepted = 0 - }; - - m_PipelineProcessor.initializeConnection(new NetworkConnection {m_NetworkId = id, m_NetworkVersion = c.Version}); - m_NetworkAcceptQueue.Enqueue(id); - } - - c.LastNonDataSend = m_UpdateTime; - SetConnection(c); - - m_NetworkProtocolInterface.ProcessSendConnectionAccept.Ptr.Invoke(ref c, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - } break; - - case ProcessPacketCommandType.Disconnect: - { - Connection c = GetConnection(command.Address, command.SessionId); - if (c != Connection.Null) - { - if (RemoveConnection(c)) - AddDisconnectEvent(c.Id, DisconnectReason.ClosedByRemote); - } - } break; - - case ProcessPacketCommandType.Ping: - { - Connection c = GetConnection(command.Address, command.SessionId); - if (c == Connection.Null || c.State != NetworkConnection.State.Connected) - { - return; - } - - c.DidReceiveData = 1; - c.LastReceive = m_UpdateTime; - c.LastNonDataSend = m_UpdateTime; - UpdateConnection(c); - - m_NetworkProtocolInterface.ProcessSendPong.Ptr.Invoke(ref c, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData); - } break; - - case ProcessPacketCommandType.Pong: - { - Connection c = GetConnection(command.Address, command.SessionId); - if (c != Connection.Null) - { - c.DidReceiveData = 1; - c.LastReceive = m_UpdateTime; - UpdateConnection(c); - } - } break; - - case ProcessPacketCommandType.DataWithImplicitConnectionAccept: - { - Connection c = GetConnection(command.Address, command.SessionId); - if (c == Connection.Null) - { - //There used to be a LogError message here that read "DataWithImplicitConnectionAccept received, but GetConnection returned null." - //The log message would occur after a server forcibly disconnected a client while the client was still sending packets. - //There may be more scenarios where this could occur, but those are the expected results in that particular scenario. - //Such a log message does not seem to actually prompt the user to take any action or make any change, and serves as little - //more than noise. - return; - } - - c.DidReceiveData = 1; - c.LastReceive = m_UpdateTime; - UpdateConnection(c); - - if (c.State == NetworkConnection.State.Connecting) - { - c.SendToken = command.As.DataWithImplicitConnectionAccept.ConnectionToken; - - c.State = NetworkConnection.State.Connected; - UpdateConnection(c); - UnityEngine.Assertions.Assert.IsTrue(!Listening); - AddConnectEvent(c.Id); - } - - if (c.State == NetworkConnection.State.Connected) - { - var memoryOffset = PinMemoryTillUpdate(command.As.DataWithImplicitConnectionAccept.Offset + command.As.DataWithImplicitConnectionAccept.Length); - var sliceOffset = memoryOffset + command.As.DataWithImplicitConnectionAccept.Offset; - - if (command.As.DataWithImplicitConnectionAccept.HasPipeline) - { - var netCon = new NetworkConnection {m_NetworkId = c.Id, m_NetworkVersion = c.Version}; - m_PipelineProcessor.Receive(this, netCon, ((NativeArray)m_DataStream).GetSubArray(sliceOffset, command.As.DataWithImplicitConnectionAccept.Length)); - - return; - } - - m_EventQueue.PushEvent(new NetworkEvent - { - connectionId = c.Id, - type = NetworkEvent.Type.Data, - offset = sliceOffset, - size = command.As.DataWithImplicitConnectionAccept.Length - }); - } - } break; - - case ProcessPacketCommandType.Data: - { - Connection c = GetConnection(command.Address, command.SessionId); - if (c == Connection.Null) - { - //There used to be a LogError message here that read "DataWithImplicitConnectionAccept received, but GetConnection returned null." - //The log message would occur after a server forcibly disconnected a client while the client was still sending packets. - //There may be more scenarios where this could occur, but those are the expected results in that particular scenario. - //Such a log message does not seem to actually prompt the user to take any action or make any change, and serves as little - //more than noise. - return; - } - - c.DidReceiveData = 1; - c.LastReceive = m_UpdateTime; - UpdateConnection(c); - - if (c.State == NetworkConnection.State.Connected) - { - var memoryOffset = PinMemoryTillUpdate(command.As.Data.Offset + command.As.Data.Length); - var sliceOffset = memoryOffset + command.As.Data.Offset; - - if (command.As.Data.HasPipeline) - { - var netCon = new NetworkConnection {m_NetworkId = c.Id, m_NetworkVersion = c.Version}; - m_PipelineProcessor.Receive(this, netCon, ((NativeArray)m_DataStream).GetSubArray(sliceOffset, command.As.Data.Length)); - return; - } - - m_EventQueue.PushEvent(new NetworkEvent - { - connectionId = c.Id, - type = NetworkEvent.Type.Data, - offset = sliceOffset, - size = command.As.Data.Length - }); - } - } break; - - case ProcessPacketCommandType.ProtocolStatusUpdate: - m_ProtocolStatus.Value = command.As.ProtocolStatusUpdate.Status; - break; - - case ProcessPacketCommandType.Drop: - break; - } - } - - // Interface for receiving data from a pipeline - internal unsafe void PushDataEvent(NetworkConnection con, int pipelineId, byte* dataPtr, int dataLength) - { - var isInsideOurReceiveBuffer = IsPointerInsideDataStream(dataPtr, dataLength, out var sliceOffset); - if (isInsideOurReceiveBuffer == false) - { - // Pointer is NOT a subset of our receive buffer, we need to copy - var allocatedLength = dataLength; - var ptr = AllocateMemory(ref allocatedLength); // streamBasePtr is not valid after this call - - if (ptr == IntPtr.Zero || allocatedLength < dataLength) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogError("Out of memory in PushDataEvent"); -#endif - return; - } - - UnsafeUtility.MemCpy((byte*)ptr.ToPointer(), dataPtr, dataLength); - - sliceOffset = PinMemoryTillUpdate(dataLength); - } - - m_EventQueue.PushEvent(new NetworkEvent - { - pipelineId = (short)pipelineId, - connectionId = con.m_NetworkId, - type = NetworkEvent.Type.Data, - offset = sliceOffset, - size = dataLength - }); - } - - /// - /// Moves 'head' of allocator for 'length' bytes. Use this to 'pin' memory in till the next update. If you don't call it - it is 'pinned' till the next call to - /// Means every time you call without memory is overriden - /// - /// Bytes to move - /// Returns head of pinned memory - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal int PinMemoryTillUpdate(int length) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (m_DataStreamHead[0] + length > m_DataStream.Length) - throw new ArgumentException("Can't pin allocation past the end of data stream"); -#endif - var result = m_DataStreamHead[0]; - m_DataStreamHead[0] = result + length; - - return result; - } - - /// - /// Returns true if the [dataPtr..dataPtr + dataLength) is inside the data stream. - /// Also, if true, returns the slice to the dataPtr in 'local' coordinate of the dataStream. - /// - /// Start of the data - /// Length of the data - /// If true - offset since the data stream head. If false - 0 - /// Returns true if the [dataPtr..dataPtr + dataLength) is inside the data stream. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool IsPointerInsideDataStream(byte* dataPtr, int dataLength, out int sliceOffset) - { - sliceOffset = 0; - var streamBasePtr = (byte*)m_DataStream.GetUnsafePtr(); - var isInside = dataPtr >= streamBasePtr && dataPtr + dataLength <= streamBasePtr + m_DataStreamHead[0]; - if (isInside) - { - sliceOffset = (int)(dataPtr - streamBasePtr); - } - return isInside; - } - - /// - /// Allocates temporary memory in 's data stream. You don't need to deallocate it - /// If you need to call this function several times - use to move 'head' - /// - /// Size of memory to allocate in bytes. Must be > 0 - /// Pointer to allocated memory or IntPtr.Zero if there is no space left (this function doesn't set ! caller should decide if this is Out of memory or something else) - internal IntPtr AllocateMemory(ref int dataLen) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (dataLen <= 0) - throw new ArgumentException("Can't allocate 0 bytes or less"); -#endif - - var stream = m_DataStream; - var dataStreamHead = m_DataStreamHead[0]; - if (m_NetworkParams.dataStream.size == 0) // if the data stream has dynamic size - { - stream.ResizeUninitializedTillPowerOf2(dataStreamHead + dataLen); - } - else if (dataStreamHead + dataLen > stream.Length) - { - dataLen = stream.Length - dataStreamHead; - if (dataLen <= 0) - { - dataLen = 0; - return IntPtr.Zero; - } - } - - unsafe - { - return new IntPtr((byte*)stream.GetUnsafePtr() + dataStreamHead); - } + get => m_DriverReceiver.Result.ErrorCode; } } } diff --git a/Runtime/NetworkDriverReceiver.cs b/Runtime/NetworkDriverReceiver.cs new file mode 100644 index 0000000..205d023 --- /dev/null +++ b/Runtime/NetworkDriverReceiver.cs @@ -0,0 +1,87 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.Utilities; + +namespace Unity.Networking.Transport +{ + internal struct NetworkDriverReceiver : IDisposable + { + private const int k_InitialDataStreamSize = 4096; + + private PacketsQueue m_ReceiveQueue; + private NativeList m_DataStream; + private OperationResult m_Result; + + internal OperationResult Result => m_Result; + + // TODO: evaluate moving this to the NetworkStack + internal PacketsQueue ReceiveQueue => m_ReceiveQueue; + + internal NetworkDriverReceiver(PacketsQueue receiveQueue) + { + m_ReceiveQueue = receiveQueue; + m_DataStream = new NativeList(k_InitialDataStreamSize, Allocator.Persistent); + m_Result = new OperationResult("receive", Allocator.Persistent); + } + + public void Dispose() + { + if (m_DataStream.IsCreated) + { + m_ReceiveQueue.Dispose(); + m_DataStream.Dispose(); + m_Result.Dispose(); + } + } + + internal NativeArray GetDataStreamSubArray(int offset, int size) + { + return m_DataStream.AsArray().GetSubArray(offset, size); + } + + internal void ClearStream() + { + m_DataStream.Clear(); + } + + private unsafe int AppendToStream(byte* dataPtr, int dataLength) + { + m_DataStream.ResizeUninitializedTillPowerOf2(m_DataStream.Length + dataLength); + var offset = m_DataStream.Length; + + m_DataStream.Length = offset + dataLength; + UnsafeUtility.MemCpy((byte*)m_DataStream.GetUnsafePtr() + offset, dataPtr, dataLength); + + return offset; + } + + internal unsafe int AppendToStream(ref PacketProcessor packetProcessor) + { + m_DataStream.ResizeUninitializedTillPowerOf2(m_DataStream.Length + packetProcessor.Length); + var offset = m_DataStream.Length; + + m_DataStream.Length = offset + packetProcessor.Length; + packetProcessor.CopyPayload((byte*)m_DataStream.GetUnsafePtr() + offset, packetProcessor.Length); + + return offset; + } + + // Interface for receiving data from a pipeline + internal unsafe void PushDataEvent(NetworkConnection con, int pipelineId, byte* dataPtr, int dataLength, ref NetworkEventQueue eventQueue) + { + var sliceOffset = AppendToStream(dataPtr, dataLength); + + eventQueue.PushEvent(new NetworkEvent + { + pipelineId = (short)pipelineId, + connectionId = con.InternalId, + type = NetworkEvent.Type.Data, + offset = sliceOffset, + size = dataLength + }); + } + } +} diff --git a/Runtime/NetworkDriverReceiver.cs.meta b/Runtime/NetworkDriverReceiver.cs.meta new file mode 100644 index 0000000..2cdc27b --- /dev/null +++ b/Runtime/NetworkDriverReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87d5ce23ec42e4646819a054f6118b7e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NetworkDriverSender.cs b/Runtime/NetworkDriverSender.cs new file mode 100644 index 0000000..cf0d39b --- /dev/null +++ b/Runtime/NetworkDriverSender.cs @@ -0,0 +1,143 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + internal struct NetworkDriverSender : IDisposable + { + private PacketsQueue m_SendQueue; + private NativeQueue m_PendingSendQueue; + + // It makes more sense to have the SendQueue in the NetworkStack, however the concurrent + // version of the NetworkDriverSender requires its pool to acquire the buffers, so for now + // we keep it here. + internal PacketsQueue SendQueue => m_SendQueue; + + internal NetworkDriverSender(PacketsQueue sendQueue) + { + m_SendQueue = sendQueue; + m_PendingSendQueue = new NativeQueue(Allocator.Persistent); + } + + public void Dispose() + { + if (m_PendingSendQueue.IsCreated) + { + m_PendingSendQueue.Dispose(); + m_SendQueue.Dispose(); + } + } + + internal JobHandle FlushPackets(JobHandle dependency) + { + return new DequeuePacketsJob + { + Queue = m_SendQueue, + PendingSendQueue = m_PendingSendQueue, + } + .Schedule(dependency); + } + + [BurstCompile] + private struct DequeuePacketsJob : IJob + { + public PacketsQueue Queue; + public NativeQueue PendingSendQueue; + + public void Execute() + { + var count = PendingSendQueue.Count; + for (int i = 0; i < count; i++) + { + Queue.EnqueuePacket(PendingSendQueue.Dequeue(), out _); + } + } + } + + internal Concurrent ToConcurrent() + { + return new Concurrent + { + m_SendQueue = m_SendQueue, + m_PendingSendQueue = m_PendingSendQueue.AsParallelWriter(), + }; + } + + internal struct Concurrent + { + [ReadOnly] internal PacketsQueue m_SendQueue; + internal NativeQueue.ParallelWriter m_PendingSendQueue; + + public int BeginSend(out NetworkInterfaceSendHandle sendHandle, uint packetSize = 0) + { + sendHandle = default; + + if (packetSize == 0) + packetSize = NetworkParameterConstants.MTU; + else if (packetSize > NetworkParameterConstants.MTU) + return (int)Error.StatusCode.NetworkPacketOverflow; + + if (m_SendQueue.TryAcquireBuffer(out var bufferIndex)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_SendQueue.GetMetadataRef(bufferIndex) = default; +#endif + + var buffer = m_SendQueue.GetPacketBuffer(bufferIndex); + + sendHandle.id = bufferIndex; + sendHandle.data = buffer.Payload; + sendHandle.capacity = (int)packetSize; + + return (int)Error.StatusCode.Success; + } + + return (int)Error.StatusCode.NetworkSendQueueFull; + } + + public void AbortSend(ref NetworkInterfaceSendHandle sendHandle) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_SendQueue.GetMetadataRef(sendHandle.id) != default) + { + throw new InvalidOperationException("Trying to abort a send of an already EndSend packet."); + } +#endif + m_SendQueue.ReleaseBuffer(sendHandle.id); + } + + public unsafe int EndSend(ref NetworkEndpoint destination, ref NetworkInterfaceSendHandle sendHandle, int padding = 0, ConnectionId connectionId = default) + { + var bufferIndex = sendHandle.id; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!m_SendQueue.IsUsed(bufferIndex)) + { + throw new InvalidOperationException(string.Format("Trying to EndSend a packet with a non-acquired buffer (idx: {0}).", bufferIndex)); + } + + if (m_SendQueue.GetMetadataRef(bufferIndex) != default) + { + throw new InvalidOperationException("Trying to EndSend a packet twice."); + } +#endif + + m_SendQueue.GetMetadataRef(bufferIndex) = new PacketMetadata + { + DataOffset = padding, + DataLength = sendHandle.size, + DataCapacity = sendHandle.capacity, + Connection = connectionId, + }; + + m_SendQueue.GetEndpointRef(bufferIndex) = destination; + + m_PendingSendQueue.Enqueue(bufferIndex); + + return sendHandle.size; + } + } + } +} diff --git a/Runtime/NetworkDriverSender.cs.meta b/Runtime/NetworkDriverSender.cs.meta new file mode 100644 index 0000000..a38c3a2 --- /dev/null +++ b/Runtime/NetworkDriverSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b59f91ca62f704c3daeeacb06a42f95c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NetworkEndPoint.cs b/Runtime/NetworkEndpoint.cs similarity index 57% rename from Runtime/NetworkEndPoint.cs rename to Runtime/NetworkEndpoint.cs index 6be8aa0..cd8fb29 100644 --- a/Runtime/NetworkEndPoint.cs +++ b/Runtime/NetworkEndpoint.cs @@ -11,53 +11,35 @@ namespace Unity.Networking.Transport { /// - /// Indicates the protocol family of the address (analogous of AF_* in sockets API). + /// NetworkFamily indicates what type of underlying medium we are using. /// public enum NetworkFamily { - /// Invalid network family. Invalid = 0, - /// IPv4 (analogous to AF_INET). Ipv4 = 2, - /// IPv6 (analogous to AF_INET6). Ipv6 = 23 } - /// - /// Describes a raw network endpoint (typically IP and port number). - /// + [Obsolete("NetworkEndPoint has been renamed to NetworkEndpoint. (UnityUpgradable) -> NetworkEndpoint", true)] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public struct NetworkEndPoint {} + [StructLayout(LayoutKind.Sequential)] - public unsafe struct NetworkEndPoint + public unsafe struct NetworkEndpoint : IEquatable { - /// - /// Types of addresses with special handling. - /// - enum AddressType - { - /// Type to use when binding to any available address. - Any = 0, - /// Loopback address (e.g. localhost connection). - Loopback = 1 - } - + enum AddressType { Any = 0, Loopback = 1 } private const int rawIpv4Length = 4; - private const int rawIpv6Length = 16; - private const int rawDataLength = 16; // Maximum space needed to hold a IPv6 Address #if !UNITY_2021_1_OR_NEWER && !UNITY_DOTSRUNTIME private const int rawLength = rawDataLength + 4; // SizeOf #else private const int rawLength = rawDataLength + 8; // SizeOf #endif - private static readonly bool IsLittleEndian = true; - + internal Binding.Baselib_NetworkAddress rawNetworkAddress; - /// - /// Returns the length of the raw network endpoint in bytes. - /// public int Length { get @@ -75,19 +57,13 @@ public int Length } } - /// - /// Initializes a new instance of . - /// - static NetworkEndPoint() + static NetworkEndpoint() { uint test = 1; byte* test_b = (byte*)&test; IsLittleEndian = test_b[0] == 1; } - /// - /// Gets or sets port number of the endpoint. - /// public ushort Port { get => (ushort)(rawNetworkAddress.port1 | (rawNetworkAddress.port0 << 8)); @@ -98,19 +74,12 @@ public ushort Port } } - /// - /// Gets or sets of the endpoint. - /// public NetworkFamily Family { get => FromBaselibFamily((Binding.Baselib_NetworkAddress_Family)rawNetworkAddress.family); set => rawNetworkAddress.family = (byte)ToBaselibFamily(value); } - /// - /// Gets the raw bytes for the endpoint. - /// - /// Native array containing the raw bytes (uses temporary allocation). public NativeArray GetRawAddressBytes() { var bytes = new NativeArray(Length, Allocator.Temp); @@ -118,12 +87,6 @@ public NativeArray GetRawAddressBytes() return bytes; } - /// - /// Directly sets the raw bytes of the endpoint using the specified bytes and family. - /// - /// Raw bytes to use for the endpoint. - /// Endpoint's address family. - /// Length of bytes doesn't match family. public void SetRawAddressBytes(NativeArray bytes, NetworkFamily family = NetworkFamily.Ipv4) { if ((family == NetworkFamily.Ipv4 && bytes.Length != rawIpv4Length) || @@ -149,9 +112,6 @@ public void SetRawAddressBytes(NativeArray bytes, NetworkFamily family = N } } - /// - /// Gets or sets the value of the raw port number. - /// public ushort RawPort { get @@ -166,68 +126,40 @@ public ushort RawPort } } - /// - /// Gets the endpoint's representation as a . - /// - public string Address => AddressAsString(); + public string Address => ToString(); - /// - /// Whether the endpoint is valid or not. - /// public bool IsValid => Family != 0; - /// - /// Gets an IPv4 endpoint that can be used to bind to any address available (0.0.0.0:0). - /// - public static NetworkEndPoint AnyIpv4 => CreateAddress(0); - - /// - /// Gets an IPv4 loopback endpoint (127.0.0.1:0). - /// - public static NetworkEndPoint LoopbackIpv4 => CreateAddress(0, AddressType.Loopback); - - /// - /// Gets an IPv6 endpoint that can be used to bind to any address available ([::0]:0). - /// - public static NetworkEndPoint AnyIpv6 => CreateAddress(0, AddressType.Any, NetworkFamily.Ipv6); - - /// - /// Gets an IPv6 loopback endpoint ([::1]:0). - /// - public static NetworkEndPoint LoopbackIpv6 => CreateAddress(0, AddressType.Loopback, NetworkFamily.Ipv6); - - /// - /// Use the given port number for this endpoint. - /// - /// The port number. - /// The endpoint (this). - public NetworkEndPoint WithPort(ushort port) + public static NetworkEndpoint AnyIpv4 => CreateAddress(0); + public static NetworkEndpoint LoopbackIpv4 => CreateAddress(0, AddressType.Loopback); + + public static NetworkEndpoint AnyIpv6 => CreateAddress(0, AddressType.Any, NetworkFamily.Ipv6); + public static NetworkEndpoint LoopbackIpv6 => CreateAddress(0, AddressType.Loopback, NetworkFamily.Ipv6); + + public NetworkEndpoint WithPort(ushort port) { var ep = this; ep.Port = port; return ep; } - /// - /// Whether the endpoint is using a loopback address. - /// public bool IsLoopback => (this == LoopbackIpv4.WithPort(Port)) || (this == LoopbackIpv6.WithPort(Port)); - - /// - /// Whether the endpoint is using an "any" address. - /// public bool IsAny => (this == AnyIpv4.WithPort(Port)) || (this == AnyIpv6.WithPort(Port)); - /// - /// Try to parse the given address and port into a new . - /// - /// String representation of the address. - /// Port number. - /// Return value for the parsed endpoint. - /// Address family of 'address'. - /// Whether the endpoint was successfully parsed or not. - public static bool TryParse(string address, ushort port, out NetworkEndPoint endpoint, NetworkFamily family = NetworkFamily.Ipv4) + // Returns true if we can fully parse the input and return a valid endpoint + public static bool TryParse(string address, ushort port, out NetworkEndpoint endpoint, NetworkFamily family = NetworkFamily.Ipv4) { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (family == NetworkFamily.Ipv6) + { +#if UNITY_SWITCH + throw new ArgumentException("IPv6 is not supported on Switch."); +#elif (UNITY_PS4 || UNITY_PS5) + throw new ArgumentException("IPv6 is not supported on PlayStation platforms."); +#endif + } +#endif // ENABLE_UNITY_COLLECTIONS_CHECKS + UnsafeUtility.SizeOf(); endpoint = default; @@ -253,15 +185,8 @@ public static bool TryParse(string address, ushort port, out NetworkEndPoint end return endpoint.IsValid; } - /// - /// Same as , except an endpoint is always returned. If the given - /// address, port, and family don't represent a valid endpoint, the default one is returned. - /// - /// String representation of the address. - /// Return value for the parsed endpoint. - /// Address family of 'address'. - /// Parsed network endpoint (default if parsing failed). - public static NetworkEndPoint Parse(string address, ushort port, NetworkFamily family = NetworkFamily.Ipv4) + // Returns a default address if parsing fails + public static NetworkEndpoint Parse(string address, ushort port, NetworkFamily family = NetworkFamily.Ipv4) { if (TryParse(address, port, out var endpoint, family)) return endpoint; @@ -269,21 +194,26 @@ public static NetworkEndPoint Parse(string address, ushort port, NetworkFamily f return default; } - public static bool operator==(NetworkEndPoint lhs, NetworkEndPoint rhs) + public static bool operator==(NetworkEndpoint lhs, NetworkEndpoint rhs) { return lhs.Compare(rhs); } - public static bool operator!=(NetworkEndPoint lhs, NetworkEndPoint rhs) + public static bool operator!=(NetworkEndpoint lhs, NetworkEndpoint rhs) { return !lhs.Compare(rhs); } - + public override bool Equals(object other) { - return this == (NetworkEndPoint)other; + return this == (NetworkEndpoint)other; + } + + public bool Equals(NetworkEndpoint other) + { + return this == other; } - + public override int GetHashCode() { var p = (byte*)UnsafeUtility.AddressOf(ref rawNetworkAddress); @@ -299,8 +229,8 @@ public override int GetHashCode() return result; } } - - bool Compare(NetworkEndPoint other) + + bool Compare(NetworkEndpoint other) { var p = (byte*)UnsafeUtility.AddressOf(ref rawNetworkAddress); var p1 = (byte*)UnsafeUtility.AddressOf(ref other.rawNetworkAddress); @@ -360,28 +290,28 @@ internal static FixedString128Bytes AddressToString(ref Binding.Baselib_NetworkA } return str; } - - private string AddressAsString() + + public override string ToString() { - return AddressToString(ref rawNetworkAddress).ToString(); + return ToFixedString().ToString(); } - public override string ToString() + public FixedString128Bytes ToFixedString() { - return AddressToString(ref rawNetworkAddress).ToString(); + return AddressToString(ref rawNetworkAddress); } - + private static ushort ByteSwap(ushort val) { return (ushort)(((val & 0xff) << 8) | (val >> 8)); } - + private static uint ByteSwap(uint val) { return (uint)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | (val >> 24)); } - static NetworkEndPoint CreateAddress(ushort port, AddressType type = AddressType.Any, NetworkFamily family = NetworkFamily.Ipv4) + static NetworkEndpoint CreateAddress(ushort port, AddressType type = AddressType.Any, NetworkFamily family = NetworkFamily.Ipv4) { #if ENABLE_UNITY_COLLECTIONS_CHECKS UnityEngine.Debug.Assert(UnsafeUtility.SizeOf() == rawLength); @@ -397,7 +327,7 @@ static NetworkEndPoint CreateAddress(ushort port, AddressType type = AddressType ipv4Loopback = ByteSwap(ipv4Loopback); } - var ep = new NetworkEndPoint + var ep = new NetworkEndpoint { Family = family, RawPort = port @@ -436,117 +366,12 @@ static Binding.Baselib_NetworkAddress_Family ToBaselibFamily(NetworkFamily famil } } - /// - /// Representation of an endpoint, to be used internally by . - /// - public unsafe struct NetworkInterfaceEndPoint : IEquatable + [Obsolete("Use NetworkEndpoint instead", true)] + public struct NetworkInterfaceEndPoint : IEquatable { - /// - /// Maximum length of the interface endpoint's raw representation. - /// - public const int k_MaxLength = 56; - - /// - /// Actual length of the interface endpoint's raw representation. - /// - public int dataLength; - - /// - /// Raw representation of the interface endpoint. - /// - public fixed byte data[k_MaxLength]; - - /// - /// Whether the interface endpoint is valid or not. - /// - public bool IsValid => dataLength != 0; - - public static bool operator==(NetworkInterfaceEndPoint lhs, NetworkInterfaceEndPoint rhs) - { - return lhs.Equals(rhs); - } - - public static bool operator!=(NetworkInterfaceEndPoint lhs, NetworkInterfaceEndPoint rhs) - { - return !lhs.Equals(rhs); - } - - public override bool Equals(object other) - { - return Equals((NetworkInterfaceEndPoint)other); - } - - public override int GetHashCode() - { - fixed(byte* p = data) - unchecked - { - var result = 0; - - for (int i = 0; i < dataLength; i++) - { - result = (result * 31) ^ (int)p[i]; - } - - return result; - } - } - public bool Equals(NetworkInterfaceEndPoint other) { - // baselib doesn't return consistent lengths under posix, so lengths can - // only be used as a shortcut if only one addresses a blank. - if (dataLength != other.dataLength && (dataLength <= 0 || other.dataLength <= 0)) - return false; - - fixed(void* p = this.data) - { - return UnsafeUtility.MemCmp(p, other.data, math.min(dataLength, other.dataLength)) == 0; - } - } - - /// - /// Returns the as a . - /// - public FixedString64Bytes ToFixedString() - { - if (IsValid == false) - return (FixedString64Bytes)"Not Valid"; - - var n = dataLength; - var res = new FixedString64Bytes(); - - if (n == 4) - { - res.Append(data[0]); - res.Append('.'); - res.Append(data[1]); - res.Append('.'); - res.Append(data[2]); - res.Append('.'); - res.Append(data[3]); - - return res; - } - - res.Append((FixedString32Bytes)"0x"); - fixed(byte* p = this.data) - { - for (var i = 0; i < n; i += 2) - { - var ushortP = (ushort*)(p + i); - res.AppendHex(*ushortP); - } - } - return res; - } - - /// - /// Returns the as a . - /// - public override string ToString() - { - return ToFixedString().ToString(); + throw new NotImplementedException(); } } } diff --git a/Runtime/NetworkEndPoint.cs.meta b/Runtime/NetworkEndpoint.cs.meta similarity index 100% rename from Runtime/NetworkEndPoint.cs.meta rename to Runtime/NetworkEndpoint.cs.meta diff --git a/Runtime/NetworkEventQueue.cs b/Runtime/NetworkEventQueue.cs index d7dd83f..bfb75d8 100644 --- a/Runtime/NetworkEventQueue.cs +++ b/Runtime/NetworkEventQueue.cs @@ -6,54 +6,34 @@ namespace Unity.Networking.Transport { - /// Represents an event on a connection. [StructLayout(LayoutKind.Explicit)] public struct NetworkEvent { - /// The different types of events that can be returned for a connection. + /// + /// NetworkEvent.Type enumerates available network events for this driver. + /// public enum Type : short { - /// No event actually occured. Should be ignored. Empty = 0, - /// Data was received on the connection. Data, - /// The connection is now established. Connect, - /// The connection is now closed. Disconnect } - /// The type of the event. [FieldOffset(0)] internal Type type; - - /// The pipeline on which the event was received (for Data events). [FieldOffset(2)] internal short pipelineId; - - /// Internal ID of the connection. [FieldOffset(4)] internal int connectionId; - - /// Status of the event. Used to store the Disconnect reason. [FieldOffset(8)] internal int status; - - /// Offset of the event's data in the internal data stream. [FieldOffset(8)] internal int offset; - - /// Size of the event's data. [FieldOffset(12)] internal int size; } - /// A queue to store per connection. internal struct NetworkEventQueue : IDisposable { private int MaxEvents { get { return m_ConnectionEventQ.Length / (m_ConnectionEventHeadTail.Length / 2); } } - - /// - /// Initializes a new instance of a . - /// - /// The queue size per connection. public NetworkEventQueue(int queueSizePerConnection) { m_MasterEventQ = new NativeQueue(Allocator.Persistent); @@ -64,9 +44,6 @@ public NetworkEventQueue(int queueSizePerConnection) m_ConnectionEventHeadTail.Add(0); } - /// - /// Disposes of the queue. - /// public void Dispose() { m_MasterEventQ.Dispose(); @@ -74,28 +51,13 @@ public void Dispose() m_ConnectionEventHeadTail.Dispose(); } - /// - /// Pops an event from the queue. The returned stream is only valid until the call to the - /// method or until the main driver updates. - /// - /// ID of the connection the event is on. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// The of the event. + // The returned stream is valid until PopEvent is called again or until the main driver updates + public NetworkEvent.Type PopEvent(out int id, out int offset, out int size) { return PopEvent(out id, out offset, out size, out var _); } - /// - /// Pops an event from the queue. The returned stream is only valid until the call to the - /// method or until the main driver updates. - /// - /// ID of the connection the event is on. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// Pipeline on which the data event was received. - /// The of the event. public NetworkEvent.Type PopEvent(out int id, out int offset, out int size, out int pipelineId) { offset = 0; @@ -119,28 +81,11 @@ public NetworkEvent.Type PopEvent(out int id, out int offset, out int size, out } } - /// - /// Pops an event from the queue for a specific connection. The returned stream is only - /// valid until the call to the method or until the main driver updates. - /// - /// ID of the connection we want the event from. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// The of the event. public NetworkEvent.Type PopEventForConnection(int connectionId, out int offset, out int size) { return PopEventForConnection(connectionId, out offset, out size, out var _); } - /// - /// Pops an event from the queue for a specific connection. The returned stream is only - /// valid until the call to the method or until the main driver updates. - /// - /// ID of the connection we want the event from. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// Pipeline on which the data event was received. - /// The of the event. public NetworkEvent.Type PopEventForConnection(int connectionId, out int offset, out int size, out int pipelineId) { offset = 0; @@ -171,11 +116,6 @@ public NetworkEvent.Type PopEventForConnection(int connectionId, out int offset, return ev.type; } - /// - /// Get the number of events in the queue for a given connection. - /// - /// The ID of the connection to get event count of. - /// The number of events for the connection. public int GetCountForConnection(int connectionId) { if (connectionId < 0 || connectionId >= m_ConnectionEventHeadTail.Length / 2) @@ -239,7 +179,7 @@ struct SubQueueItem private NativeQueue m_MasterEventQ; private NativeList m_ConnectionEventQ; private NativeList m_ConnectionEventHeadTail; - + public Concurrent ToConcurrent() { Concurrent concurrent; @@ -247,7 +187,7 @@ public Concurrent ToConcurrent() concurrent.m_ConnectionEventHeadTail = new Concurrent.ConcurrentConnectionQueue(m_ConnectionEventHeadTail); return concurrent; } - + public struct Concurrent { [NativeContainer] @@ -298,28 +238,11 @@ private int MaxEvents get { return m_ConnectionEventQ.Length / (m_ConnectionEventHeadTail.Length / 2); } } - /// - /// Pops an event from the queue for a specific connection. The returned stream is only - /// valid until the call to the method or until the main driver updates. - /// - /// ID of the connection we want the event from. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// The of the event. public NetworkEvent.Type PopEventForConnection(int connectionId, out int offset, out int size) { return PopEventForConnection(connectionId, out offset, out size, out var _); } - /// - /// Pops an event from the queue for a specific connection. The returned stream is only - /// valid until the call to the method or until the main driver updates. - /// - /// ID of the connection we want the event from. - /// Offset of the event's data in the stream. - /// Size of the event's data in the stream. - /// Pipeline on which the data event was received. - /// The of the event. public NetworkEvent.Type PopEventForConnection(int connectionId, out int offset, out int size, out int pipelineId) { offset = 0; diff --git a/Runtime/NetworkInterfaceUnmanagedWrapper.cs b/Runtime/NetworkInterfaceUnmanagedWrapper.cs new file mode 100644 index 0000000..8aed743 --- /dev/null +++ b/Runtime/NetworkInterfaceUnmanagedWrapper.cs @@ -0,0 +1,270 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.Utilities; + +namespace Unity.Networking.Transport +{ + public unsafe struct NetworkInterfaceUnmanagedWrapper : INetworkInterface where T : INetworkInterface + { + private static ManagedCallWrapper s_LocalEndpoint_FPtr; + private static ManagedCallWrapper s_Bind_FPtr; + private static ManagedCallWrapper s_Dispose_FPtr; + private static ManagedCallWrapper s_Initialize_FPtr; + private static ManagedCallWrapper s_Listen_FPtr; + private static ManagedCallWrapper s_ScheduleReceive_FPtr; + private static ManagedCallWrapper s_ScheduleSend_FPtr; + + private static void InitializeFunctionPointers() + { + if (s_LocalEndpoint_FPtr.IsCreated) + return; + + s_LocalEndpoint_FPtr = new ManagedCallWrapper(&LocalEndpointWrapper); + s_Bind_FPtr = new ManagedCallWrapper(&BindWrapper); + s_Dispose_FPtr = new ManagedCallWrapper(&DisposeWrapper); + s_Initialize_FPtr = new ManagedCallWrapper(&InitializeWrapper); + s_Listen_FPtr = new ManagedCallWrapper(&ListenWrapper); + s_ScheduleReceive_FPtr = new ManagedCallWrapper(&ScheduleReceiveWrapper); + s_ScheduleSend_FPtr = new ManagedCallWrapper(&ScheduleSendWrapper); + } + + private ManagedReference m_NetworkInterfaceReference; + + internal ManagedReference NetworkInterfaceReference => m_NetworkInterfaceReference; + + internal NetworkInterfaceUnmanagedWrapper(ref T networkInterface) + { + InitializeFunctionPointers(); + m_NetworkInterfaceReference = new ManagedReference(ref networkInterface); + } + + private struct LocalEndpoint_Arguments + { + public ManagedReference InterfaceReference; + public NetworkEndpoint Return; + } + private static void LocalEndpointWrapper(void* argumentsPtr, int argumentsSize) + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, argumentsSize); + arguments.Return = arguments.InterfaceReference.Element.LocalEndpoint; + } + + public NetworkEndpoint LocalEndpoint + { + get + { + var arguments = new LocalEndpoint_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + }; + + s_LocalEndpoint_FPtr.Invoke(ref arguments); + + return arguments.Return; + } + } + + + private struct Bind_Arguments + { + public ManagedReference InterfaceReference; + public NetworkEndpoint Endpoint; + public int Return; + } + private static void BindWrapper(void* argumentsPtr, int argumentsSize) + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, argumentsSize); + arguments.Return = arguments.InterfaceReference.Element.Bind(arguments.Endpoint); + } + + public int Bind(NetworkEndpoint endpoint) + { + var arguments = new Bind_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + Endpoint = endpoint, + }; + + s_Bind_FPtr.Invoke(ref arguments); + + return arguments.Return; + } + + private struct Dispose_Arguments + { + public ManagedReference InterfaceReference; + } + private static void DisposeWrapper(void* argumentsPtr, int argumentsSize) + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, argumentsSize); + arguments.InterfaceReference.Element.Dispose(); + } + + public void Dispose() + { + var arguments = new Dispose_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + }; + s_Dispose_FPtr.Invoke(ref arguments); + m_NetworkInterfaceReference.Dispose(); + } + + private struct Initialize_Arguments + { + public ManagedReference InterfaceReference; + public NetworkSettings NetworkSettings; + public int PacketPadding; + public int Return; + } + private static void InitializeWrapper(void* argumentsPtr, int argumentsSize) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (argumentsSize != UnsafeUtility.SizeOf()) + throw new InvalidOperationException($"The requested argument type size does not match the provided one"); +#endif + // workaround for NetworkSettings being managed + var arguments = UnsafeUtility.ReadArrayElement(argumentsPtr, 0); + arguments.Return = arguments.InterfaceReference.Element.Initialize(ref arguments.NetworkSettings, ref arguments.PacketPadding); + UnsafeUtility.WriteArrayElement(argumentsPtr, 0, arguments); + } + + public int Initialize(ref NetworkSettings settings, ref int packetPadding) + { + var arguments = new Initialize_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + NetworkSettings = settings, + PacketPadding = packetPadding, + }; + // workaround for NetworkSettings being managed + var unmanagedArguments = stackalloc byte[UnsafeUtility.SizeOf()]; + UnsafeUtility.WriteArrayElement(unmanagedArguments, 0, arguments); + s_Initialize_FPtr.Invoke(unmanagedArguments, UnsafeUtility.SizeOf()); + arguments = UnsafeUtility.ReadArrayElement(unmanagedArguments, 0); + + // As they are ref arguments we need to reassign them in case they changed + settings = arguments.NetworkSettings; + packetPadding = arguments.PacketPadding; + + return arguments.Return; + } + + private struct Listen_Arguments + { + public ManagedReference InterfaceReference; + public int Return; + } + private static void ListenWrapper(void* argumentsPtr, int argumentsSize) + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, argumentsSize); + arguments.Return = arguments.InterfaceReference.Element.Listen(); + } + + public int Listen() + { + var arguments = new Listen_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + }; + s_Listen_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + private struct ScheduleReceive_Arguments + { + public ManagedReference InterfaceReference; + public ReceiveJobArguments Arguments; + public JobHandle Dependency; + public JobHandle Return; + } + private static void ScheduleReceiveWrapper(void* argumentsPtr, int argumentsSize) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (argumentsSize != UnsafeUtility.SizeOf()) + throw new InvalidOperationException($"The requested argument type size does not match the provided one"); +#endif + // workaround for NetworkPacketReceiver being managed + var arguments = UnsafeUtility.ReadArrayElement(argumentsPtr, 0); + arguments.Return = arguments.InterfaceReference.Element.ScheduleReceive(ref arguments.Arguments, arguments.Dependency); + UnsafeUtility.WriteArrayElement(argumentsPtr, 0, arguments); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments receiveJobArguments, JobHandle dep) + { + var arguments = new ScheduleReceive_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + Arguments = receiveJobArguments, + Dependency = dep, + }; + // workaround for NetworkSettings being managed + var unmanagedArguments = stackalloc byte[UnsafeUtility.SizeOf()]; + UnsafeUtility.WriteArrayElement(unmanagedArguments, 0, arguments); + s_ScheduleReceive_FPtr.Invoke(unmanagedArguments, UnsafeUtility.SizeOf()); + arguments = UnsafeUtility.ReadArrayElement(unmanagedArguments, 0); + return arguments.Return; + } + + private struct ScheduleSend_Arguments + { + public ManagedReference InterfaceReference; + public SendJobArguments Arguments; + public JobHandle Dependency; + public JobHandle Return; + } + private static void ScheduleSendWrapper(void* argumentsPtr, int argumentsSize) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (argumentsSize != UnsafeUtility.SizeOf()) + throw new InvalidOperationException($"The requested argument type size does not match the provided one"); +#endif + // workaround for NetworkPacketReceiver being managed + var arguments = UnsafeUtility.ReadArrayElement(argumentsPtr, 0); + arguments.Return = arguments.InterfaceReference.Element.ScheduleSend(ref arguments.Arguments, arguments.Dependency); + UnsafeUtility.WriteArrayElement(argumentsPtr, 0, arguments); + } + + public JobHandle ScheduleSend(ref SendJobArguments sendJobArguments, JobHandle dep) + { + var arguments = new ScheduleSend_Arguments + { + InterfaceReference = m_NetworkInterfaceReference, + Arguments = sendJobArguments, + Dependency = dep, + }; + // workaround for NetworkSettings being managed + var unmanagedArguments = stackalloc byte[UnsafeUtility.SizeOf()]; + UnsafeUtility.WriteArrayElement(unmanagedArguments, 0, arguments); + s_ScheduleSend_FPtr.Invoke(unmanagedArguments, UnsafeUtility.SizeOf()); + arguments = UnsafeUtility.ReadArrayElement(unmanagedArguments, 0); + return arguments.Return; + } + } + + public static class ManagedNetworkInterfaceExtensions + { + /// + /// Creates an unmanaged wrapper for a managed INetworkInterface. + /// + /// The type of the managed INetworkInterface + /// The INetworkInterface instance to wrap. + /// Returns the unmanaged wrapper instance for the network interface. + /// Throws an InvalidOperationException if the type network interface is already an unamanged type. + public static NetworkInterfaceUnmanagedWrapper WrapToUnmanaged(this T networkInterface) where T : INetworkInterface + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (networkInterface == null) + throw new ArgumentNullException(nameof(networkInterface)); + + if (!typeof(T).IsValueType) + throw new InvalidOperationException($"Non struct NetworkInterfaces ({typeof(T)}) are not supported yet due to a bug on windows (#1412477)."); + + if (UnsafeUtility.IsUnmanaged()) + throw new InvalidOperationException($"The network interface type {typeof(T).Name} is already an unmanaged type."); +#endif + return new NetworkInterfaceUnmanagedWrapper(ref networkInterface); + } + } +} diff --git a/Runtime/NetworkInterfaceUnmanagedWrapper.cs.meta b/Runtime/NetworkInterfaceUnmanagedWrapper.cs.meta new file mode 100644 index 0000000..188d84c --- /dev/null +++ b/Runtime/NetworkInterfaceUnmanagedWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abb10982beb17473fb47ae289cefd0b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NetworkLayerWrapper.cs b/Runtime/NetworkLayerWrapper.cs new file mode 100644 index 0000000..a97782e --- /dev/null +++ b/Runtime/NetworkLayerWrapper.cs @@ -0,0 +1,132 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using BurstRuntime = Unity.Burst.BurstRuntime; + +namespace Unity.Networking.Transport +{ + internal unsafe struct NetworkLayerWrapper : IDisposable + { + private void* m_RawLayerData; + private long m_TypeHash; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private int m_RawLayerDataSize; +#endif + + private ManagedCallWrapper m_Dispose_FPtr; + private ManagedCallWrapper m_ScheduleReceive_FPtr; + private ManagedCallWrapper m_ScheduleSend_FPtr; + + static public NetworkLayerWrapper Create(ref T layer) where T : unmanaged, INetworkLayer + { + var wrapper = new NetworkLayerWrapper + { + m_RawLayerData = UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), Allocator.Persistent), + m_TypeHash = BurstRuntime.GetHashCode64(), +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_RawLayerDataSize = UnsafeUtility.SizeOf(), +#endif + m_Dispose_FPtr = new ManagedCallWrapper(&DisposeWrapper), + m_ScheduleReceive_FPtr = new ManagedCallWrapper(&ScheduleReceiveWrapper), + m_ScheduleSend_FPtr = new ManagedCallWrapper(&ScheduleSendWrapper), + }; + + UnsafeUtility.CopyStructureToPtr(ref layer, wrapper.m_RawLayerData); + + return wrapper; + } + + public bool IsType() where T : unmanaged, INetworkLayer + { + return m_TypeHash == BurstRuntime.GetHashCode64(); + } + + public unsafe ref T CastRef() where T : unmanaged, INetworkLayer + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsType()) + throw new InvalidCastException(); + + if (m_RawLayerDataSize != UnsafeUtility.SizeOf()) + throw new InvalidOperationException(); +#endif + return ref UnsafeUtility.AsRef(m_RawLayerData); + } + + // Dispose wrapper + public void Dispose() + { + var arguments = new DisposeArguments { LayerPtr = m_RawLayerData }; + m_Dispose_FPtr.Invoke(ref arguments); + + UnsafeUtility.Free(m_RawLayerData, Allocator.Persistent); + } + + private struct DisposeArguments + { + public void* LayerPtr; + } + + private static void DisposeWrapper(void* argumentsPtr, int size) where T : unmanaged, INetworkLayer + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + UnsafeUtility.AsRef(arguments.LayerPtr).Dispose(); + } + + // ScheduleReceive wrapper + public JobHandle ScheduleReceive(ref ReceiveJobArguments jobArguments, JobHandle dependency) + { + var arguments = new ScheduleReceiveArguments + { + LayerPtr = m_RawLayerData, + JobArguments = jobArguments, + Dependency = dependency, + }; + m_ScheduleReceive_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + private struct ScheduleReceiveArguments + { + public void* LayerPtr; + public ReceiveJobArguments JobArguments; + public JobHandle Dependency; + public JobHandle Return; + } + + private static void ScheduleReceiveWrapper(void* argumentsPtr, int size) where T : unmanaged, INetworkLayer + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + arguments.Return = UnsafeUtility.AsRef(arguments.LayerPtr).ScheduleReceive(ref arguments.JobArguments, arguments.Dependency); + } + + // ScheduleSend wrapper + public JobHandle ScheduleSend(ref SendJobArguments jobArguments, JobHandle dependency) + { + var arguments = new ScheduleSendArguments + { + LayerPtr = m_RawLayerData, + JobArguments = jobArguments, + Dependency = dependency, + }; + m_ScheduleSend_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + private struct ScheduleSendArguments + { + public void* LayerPtr; + public SendJobArguments JobArguments; + public JobHandle Dependency; + public JobHandle Return; + } + + private static void ScheduleSendWrapper(void* argumentsPtr, int size) where T : unmanaged, INetworkLayer + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + arguments.Return = UnsafeUtility.AsRef(arguments.LayerPtr).ScheduleSend(ref arguments.JobArguments, arguments.Dependency); + } + } +} diff --git a/Runtime/NetworkLayerWrapper.cs.meta b/Runtime/NetworkLayerWrapper.cs.meta new file mode 100644 index 0000000..36ceaa8 --- /dev/null +++ b/Runtime/NetworkLayerWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24762f4bbb2a643df9b4f43480fc2ca4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/NetworkParams.cs b/Runtime/NetworkParams.cs index 2f06e58..f3d3752 100644 --- a/Runtime/NetworkParams.cs +++ b/Runtime/NetworkParams.cs @@ -1,4 +1,4 @@ -using System; +using Unity.Collections; namespace Unity.Networking.Transport { @@ -22,10 +22,6 @@ public struct NetworkParameterConstants { /// The default size of the event queue. public const int InitialEventQueueSize = 100; - - /// - /// The invalid connection id - /// public const int InvalidConnectionId = -1; /// @@ -38,40 +34,27 @@ public struct NetworkParameterConstants public const int MaxConnectAttempts = 60; /// The default disconnect timeout attempts value. This value can be overridden using the public const int DisconnectTimeoutMS = 30 * 1000; - /// The default inactivity timeout after which a heartbeat is sent. This This value can be overridden using the + /// The default inactivity timeout after which a heartbeat is sent. This value can be overridden using the public const int HeartbeatTimeoutMS = 500; - /// - /// The max size of any packet that can be sent + /// The default inactivity timeout after which re-establishing the connection is attempted. + /// This value can be overridden using the . + /// + public const int ReconnectionTimeoutMS = 2000; + /// + /// The default capacity of the receive queue. This value can be overridden using the + /// + public const int ReceiveQueueCapacity = 64; + /// + /// The default capacity of the send queue. This value can be overridden using the + /// + public const int SendQueueCapacity = 64; + /// + /// Maximum size of a packet that can be sent by the transport. /// public const int MTU = 1400; } - /// - /// The NetworkDataStreamParameter is used to set a fixed data stream size. - /// - /// The will grow on demand if the size is set to zero. - public struct NetworkDataStreamParameter : INetworkParameter - { - internal const int k_DefaultSize = 0; - - /// Size of the default - public int size; - - public bool Validate() - { - var valid = true; - - if (size < 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(size)} value ({size}) must be greater or equal to 0"); - } - - return valid; - } - } - /// /// The NetworkConfigParameter is used to set specific parameters that the driver uses. /// @@ -91,12 +74,18 @@ public struct NetworkConfigParameter : INetworkParameter public int disconnectTimeoutMS; /// A timeout in milliseconds after which a heartbeat is sent if there is no activity. public int heartbeatTimeoutMS; + /// A timeout in milliseconds after which reconnection is attempted if there is no activity. + public int reconnectionTimeoutMS; /// The maximum amount of time a single frame can advance timeout values. /// The main use for this parameter is to not get disconnects at frame spikes when both endpoints lives in the same process. public int maxFrameTimeMS; /// A fixed amount of time to use for an interval between ScheduleUpdate. This is used instead of a clock. /// The main use for this parameter is tests where determinism is more important than correctness. public int fixedFrameTimeMS; + /// The capacity of the receive queue. + public int receiveQueueCapacity; + /// The capacity of the send queue. + public int sendQueueCapacity; public bool Validate() { @@ -122,6 +111,11 @@ public bool Validate() valid = false; UnityEngine.Debug.LogError($"{nameof(heartbeatTimeoutMS)} value ({heartbeatTimeoutMS}) must be greater or equal to 0"); } + if (reconnectionTimeoutMS < 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(reconnectionTimeoutMS)} value ({reconnectionTimeoutMS}) must be greater or equal to 0"); + } if (maxFrameTimeMS < 0) { valid = false; @@ -132,6 +126,16 @@ public bool Validate() valid = false; UnityEngine.Debug.LogError($"{nameof(fixedFrameTimeMS)} value ({fixedFrameTimeMS}) must be greater or equal to 0"); } + if (receiveQueueCapacity <= 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(receiveQueueCapacity)} value ({receiveQueueCapacity}) must be greater than 0"); + } + if (sendQueueCapacity <= 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(sendQueueCapacity)} value ({sendQueueCapacity}) must be greater than 0"); + } return valid; } @@ -139,55 +143,17 @@ public bool Validate() public static class CommonNetworkParametersExtensions { - /// - /// Sets the values for the - /// - /// - [Obsolete("In Unity Transport 2.0, the data stream size will always be dynamically-sized and this API will be removed.")] - public static ref NetworkSettings WithDataStreamParameters(ref this NetworkSettings settings, int size = NetworkDataStreamParameter.k_DefaultSize) - { - var parameter = new NetworkDataStreamParameter - { - size = size, - }; - - settings.AddRawParameterStruct(ref parameter); - - return ref settings; - } - - /// - /// Gets the - /// - /// Returns the values for the - public static NetworkDataStreamParameter GetDataStreamParameters(ref this NetworkSettings settings) - { - if (!settings.TryGet(out var parameters)) - { - parameters.size = NetworkDataStreamParameter.k_DefaultSize; - } - - return parameters; - } - - /// - /// Sets the values for the - /// - /// - /// - /// - /// - /// - /// - /// public static ref NetworkSettings WithNetworkConfigParameters( ref this NetworkSettings settings, int connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS, int maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts, int disconnectTimeoutMS = NetworkParameterConstants.DisconnectTimeoutMS, int heartbeatTimeoutMS = NetworkParameterConstants.HeartbeatTimeoutMS, + int reconnectionTimeoutMS = NetworkParameterConstants.ReconnectionTimeoutMS, int maxFrameTimeMS = 0, - int fixedFrameTimeMS = 0 + int fixedFrameTimeMS = 0, + int receiveQueueCapacity = NetworkParameterConstants.ReceiveQueueCapacity, + int sendQueueCapacity = NetworkParameterConstants.SendQueueCapacity ) { var parameter = new NetworkConfigParameter @@ -196,8 +162,11 @@ public static ref NetworkSettings WithNetworkConfigParameters( maxConnectAttempts = maxConnectAttempts, disconnectTimeoutMS = disconnectTimeoutMS, heartbeatTimeoutMS = heartbeatTimeoutMS, + reconnectionTimeoutMS = reconnectionTimeoutMS, maxFrameTimeMS = maxFrameTimeMS, fixedFrameTimeMS = fixedFrameTimeMS, + receiveQueueCapacity = receiveQueueCapacity, + sendQueueCapacity = sendQueueCapacity, }; settings.AddRawParameterStruct(ref parameter); @@ -205,20 +174,19 @@ public static ref NetworkSettings WithNetworkConfigParameters( return ref settings; } - /// - /// Gets the - /// - /// Returns the values for the public static NetworkConfigParameter GetNetworkConfigParameters(ref this NetworkSettings settings) { if (!settings.TryGet(out var parameters)) { - parameters.connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS; - parameters.maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts; - parameters.disconnectTimeoutMS = NetworkParameterConstants.DisconnectTimeoutMS; - parameters.heartbeatTimeoutMS = NetworkParameterConstants.HeartbeatTimeoutMS; - parameters.maxFrameTimeMS = 0; - parameters.fixedFrameTimeMS = 0; + parameters.connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS; + parameters.maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts; + parameters.disconnectTimeoutMS = NetworkParameterConstants.DisconnectTimeoutMS; + parameters.heartbeatTimeoutMS = NetworkParameterConstants.HeartbeatTimeoutMS; + parameters.reconnectionTimeoutMS = NetworkParameterConstants.ReconnectionTimeoutMS; + parameters.receiveQueueCapacity = NetworkParameterConstants.ReceiveQueueCapacity; + parameters.sendQueueCapacity = NetworkParameterConstants.SendQueueCapacity; + parameters.maxFrameTimeMS = 0; + parameters.fixedFrameTimeMS = 0; } return parameters; diff --git a/Runtime/NetworkPipeline.cs b/Runtime/NetworkPipeline.cs index 9d10c2b..9f26ff6 100644 --- a/Runtime/NetworkPipeline.cs +++ b/Runtime/NetworkPipeline.cs @@ -1,50 +1,25 @@ using System; using System.Threading; +using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -using Unity.Burst; using System.Collections.Generic; using System.Diagnostics; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; using System.Runtime.InteropServices; +using BurstRuntime = Unity.Burst.BurstRuntime; +using static Unity.Networking.Transport.NetworkPipelineStage; namespace Unity.Networking.Transport { - /// - /// The inbound send buffer - /// public unsafe struct InboundSendBuffer { - /// - /// The buffer - /// public byte* buffer; - - /// - /// The buffer with headers - /// public byte* bufferWithHeaders; - - /// - /// The buffer length - /// public int bufferLength; - - /// - /// The buffer with headers length - /// public int bufferWithHeadersLength; - - /// - /// The header padding - /// public int headerPadding; - /// - /// Sets the buffer frombuffer with headers - /// - public void SetBufferFrombufferWithHeaders() + public void SetBufferFromBufferWithHeaders() { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (bufferWithHeadersLength < headerPadding) @@ -54,26 +29,11 @@ public void SetBufferFrombufferWithHeaders() bufferLength = bufferWithHeadersLength - headerPadding; } } - /// - /// The inbound recv buffer - /// public unsafe struct InboundRecvBuffer { - /// - /// The buffer - /// public byte* buffer; - - /// - /// The buffer length - /// public int bufferLength; - /// - /// Slices the offset - /// - /// The offset - /// The slice public InboundRecvBuffer Slice(int offset) { #if ENABLE_UNITY_COLLECTIONS_CHECKS @@ -86,92 +46,27 @@ public InboundRecvBuffer Slice(int offset) return slice; } } - /// - /// The network pipeline context - /// public unsafe struct NetworkPipelineContext { - /// - /// The static instance buffer - /// public byte* staticInstanceBuffer; - - /// - /// The internal shared process buffer - /// public byte* internalSharedProcessBuffer; - - /// - /// The internal process buffer - /// public byte* internalProcessBuffer; - - /// - /// The header - /// public DataStreamWriter header; - - /// - /// The timestamp - /// public long timestamp; - - /// - /// The static instance buffer length - /// public int staticInstanceBufferLength; - - /// - /// The internal shared process buffer length - /// public int internalSharedProcessBufferLength; - - /// - /// The internal process buffer length - /// public int internalProcessBufferLength; - - /// - /// The accumulated header capacity - /// public int accumulatedHeaderCapacity; } - /// - /// The network pipeline stage interface - /// public unsafe interface INetworkPipelineStage { - /// - /// Statics the initialize using the specified static instance buffer - /// - /// The static instance buffer - /// The static instance buffer length - /// The param - /// The network pipeline stage NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings); - /// - /// Gets the value of the static size - /// int StaticSize { get; } } - /// - /// The network pipeline stage - /// public unsafe struct NetworkPipelineStage { - /// - /// Initializes a new instance of the class - /// - /// The receive - /// The send - /// The initialize connection - /// The receive capacity - /// The send capacity - /// The header capacity - /// The shared state capacity - /// The payload capacity public NetworkPipelineStage(TransportFunctionPointer Receive, TransportFunctionPointer Send, TransportFunctionPointer InitializeConnection, @@ -189,177 +84,78 @@ public NetworkPipelineStage(TransportFunctionPointer Receive, this.HeaderCapacity = HeaderCapacity; this.SharedStateCapacity = SharedStateCapacity; this.PayloadCapacity = PayloadCapacity; - StaticStateStart = StaticStateCapcity = 0; + StaticStateStart = StaticStateCapacity = 0; } - /// - /// The requests enum - /// [Flags] public enum Requests { - /// - /// The none requests - /// None = 0, - /// - /// The resume requests - /// Resume = 1, - /// - /// The update requests - /// Update = 2, - /// - /// The send update requests - /// SendUpdate = 4, - /// - /// The error requests - /// Error = 8 } - /// - /// The receive delegate - /// + // Be careful when changing the signature of the following delegates. + // They are unsafely casted to function pointers with matching arguments. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void ReceiveDelegate(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref Requests requests, int systemHeadersSize); - /// - /// The send delegate - /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int SendDelegate(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref Requests requests, int systemHeadersSize); - /// - /// The initialize connection delegate - /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void InitializeConnectionDelegate(byte* staticInstanceBuffer, int staticInstanceBufferLength, byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, byte* sharedProcessBuffer, int sharedProcessBufferLength); - /// - /// Receive function pointer - /// public TransportFunctionPointer Receive; - - /// - /// Send function pointer - /// public TransportFunctionPointer Send; - - /// - /// InitializeConnection function pointer - /// public TransportFunctionPointer InitializeConnection; - /// - /// The receive capacity - /// public readonly int ReceiveCapacity; - - /// - /// The send capacity - /// public readonly int SendCapacity; - - /// - /// The header capacity - /// public readonly int HeaderCapacity; - - /// - /// The shared state capacity - /// public readonly int SharedStateCapacity; - - /// - /// The payload capacity - /// public readonly int PayloadCapacity; internal int StaticStateStart; - internal int StaticStateCapcity; + internal int StaticStateCapacity; } - public struct NetworkPipelineStageId + /// Identifier for a pipeline stage. + public struct NetworkPipelineStageId : IEquatable { - internal int Index; - internal int IsValid; - } + private long m_TypeHash; - /// - /// The network pipeline stage collection class - /// - public static class NetworkPipelineStageCollection - { - /// - /// Initializes a new instance of the class - /// - static NetworkPipelineStageCollection() + internal static NetworkPipelineStageId Get(Type stage) { - m_stages = new List(); - RegisterPipelineStage(new NullPipelineStage()); - RegisterPipelineStage(new FragmentationPipelineStage()); - RegisterPipelineStage(new ReliableSequencedPipelineStage()); - RegisterPipelineStage(new UnreliableSequencedPipelineStage()); - RegisterPipelineStage(new SimulatorPipelineStage()); - RegisterPipelineStage(new SimulatorPipelineStageInSend()); + return new NetworkPipelineStageId { m_TypeHash = BurstRuntime.GetHashCode64(stage) }; } - /// - /// Registers the pipeline stage using the specified stage - /// - /// The stage - public static void RegisterPipelineStage(INetworkPipelineStage stage) + /// Get the stage ID for the given stage type. + public static NetworkPipelineStageId Get() where T : unmanaged, INetworkPipelineStage { - for (int i = 0; i < m_stages.Count; ++i) - { - if (m_stages[i].GetType() == stage.GetType()) - { - // TODO: should this be an error? - m_stages[i] = stage; - return; - } - } - m_stages.Add(stage); + return new NetworkPipelineStageId { m_TypeHash = BurstRuntime.GetHashCode64() }; } - /// - /// Gets the stage id using the specified stage type - /// - /// The stage type - /// Pipeline stage {stageType} is not registered - /// The network pipeline stage id - public static NetworkPipelineStageId GetStageId(Type stageType) - { - for (int i = 0; i < m_stages.Count; ++i) - { - if (stageType == m_stages[i].GetType()) - return new NetworkPipelineStageId {Index = i, IsValid = 1}; - } -#if ENABLE_UNITY_COLLECTIONS_CHECKS - throw new InvalidOperationException($"Pipeline stage {stageType} is not registered"); -#else - UnityEngine.Debug.LogError($"Pipeline stage {stageType} is not registered"); - return default; -#endif - } + public override int GetHashCode() => (int)m_TypeHash; + + public override bool Equals(object other) => this == (NetworkPipelineStageId)other; - internal static List m_stages; + public bool Equals(NetworkPipelineStageId other) => m_TypeHash == other.m_TypeHash; + + public static bool operator==(NetworkPipelineStageId lhs, NetworkPipelineStageId rhs) => + lhs.m_TypeHash == rhs.m_TypeHash; + + public static bool operator!=(NetworkPipelineStageId lhs, NetworkPipelineStageId rhs) => + lhs.m_TypeHash != rhs.m_TypeHash; } - /// - /// The network pipeline - /// - public struct NetworkPipeline + public struct NetworkPipeline : IEquatable { internal int Id; - /// - /// Returns default unreliable Null - /// public static NetworkPipeline Null => default; public static bool operator==(NetworkPipeline lhs, NetworkPipeline rhs) @@ -388,70 +184,6 @@ public bool Equals(NetworkPipeline connection) } } - - public static class NetworkPipelineParametersExtensions - { - /// - /// Sets the values for the - /// - /// - [Obsolete("Will be removed in Unity Transport 2.0.")] - public static ref NetworkSettings WithPipelineParameters( - ref this NetworkSettings settings, - int initialCapacity = NetworkPipelineParams.k_DefaultInitialCapacity - ) - { - var parameter = new NetworkPipelineParams - { - initialCapacity = initialCapacity, - }; - - settings.AddRawParameterStruct(ref parameter); - - return ref settings; - } - - /// - /// Gets the - /// - /// Returns the values for the - public static NetworkPipelineParams GetPipelineParameters(ref this NetworkSettings settings) - { - if (!settings.TryGet(out var parameters)) - { - parameters.initialCapacity = NetworkPipelineParams.k_DefaultInitialCapacity; - } - - return parameters; - } - } - - /// - /// The network pipeline params - /// - public struct NetworkPipelineParams : INetworkParameter - { - internal const int k_DefaultInitialCapacity = 0; - - /// - /// The initial capacity - /// - public int initialCapacity; - - public bool Validate() - { - var valid = true; - - if (initialCapacity < 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(initialCapacity)} value ({initialCapacity}) must be greater or equal to 0"); - } - - return valid; - } - } - internal struct NetworkPipelineProcessor : IDisposable { public const int Alignment = 8; @@ -471,26 +203,27 @@ public Concurrent ToConcurrent() { var concurrent = new Concurrent { - m_StageCollection = m_StageCollection, + m_StageStructs = m_StageStructs, m_StaticInstanceBuffer = m_StaticInstanceBuffer, m_Pipelines = m_Pipelines, - m_StageList = m_StageList, + m_PipelineStagesIndices = m_PipelineStagesIndices, m_AccumulatedHeaderCapacity = m_AccumulatedHeaderCapacity, m_SendStageNeedsUpdateWrite = m_SendStageNeedsUpdateRead.AsParallelWriter(), sizePerConnection = sizePerConnection, sendBuffer = m_SendBuffer, sharedBuffer = m_SharedBuffer, m_timestamp = m_timestamp, + m_MaxPacketHeaderSize = m_MaxPacketHeaderSize, }; return concurrent; } public struct Concurrent { - [ReadOnly] internal NativeArray m_StageCollection; - [ReadOnly] internal NativeArray m_StaticInstanceBuffer; + [ReadOnly] internal NativeList m_StageStructs; + [ReadOnly] internal NativeList m_StaticInstanceBuffer; [ReadOnly] internal NativeList m_Pipelines; - [ReadOnly] internal NativeList m_StageList; + [ReadOnly] internal NativeList m_PipelineStagesIndices; [ReadOnly] internal NativeList m_AccumulatedHeaderCapacity; internal NativeQueue.ParallelWriter m_SendStageNeedsUpdateWrite; [ReadOnly] internal NativeArray sizePerConnection; @@ -498,6 +231,7 @@ public struct Concurrent [ReadOnly] internal NativeList sharedBuffer; [ReadOnly] internal NativeList sendBuffer; [ReadOnly] internal NativeArray m_timestamp; + internal int m_MaxPacketHeaderSize; public int SendHeaderCapacity(NetworkPipeline pipeline) { @@ -522,10 +256,10 @@ public unsafe int Send(NetworkDriver.Concurrent driver, NetworkPipeline pipeline return (int)Error.StatusCode.NetworkSendHandleInvalid; } - var connectionId = connection.m_NetworkId; + var connectionId = connection.InternalId; // TODO: not really read-only, just hacking the safety system - NativeArray tmpBuffer = sendBuffer; + NativeArray tmpBuffer = sendBuffer.AsArray(); int* sendBufferLock = (int*)tmpBuffer.GetUnsafeReadOnlyPtr(); sendBufferLock += connectionId * sizePerConnection[SendSizeOffset] / 4; @@ -558,8 +292,7 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta NetworkPipelineContext ctx = default(NetworkPipelineContext); ctx.timestamp = m_timestamp[0]; var p = m_Pipelines[pipeline.Id - 1]; - var connectionId = connection.m_NetworkId; - var systemHeaderSize = driver.MaxProtocolHeaderSize(); + var connectionId = connection.InternalId; // If the call comes from update, the sendHandle is set to default. bool inUpdateCall = sendHandle.data == IntPtr.Zero; @@ -593,18 +326,19 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta } for (int i = 0; i < startStage; ++i) { - internalBufferOffset += (m_StageCollection[m_StageList[p.FirstStageIndex + i]].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - internalSharedBufferOffset += (m_StageCollection[m_StageList[p.FirstStageIndex + i]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - headerSize -= m_StageCollection[m_StageList[p.FirstStageIndex + i]].HeaderCapacity; + internalBufferOffset += (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i]].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + internalSharedBufferOffset += (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + headerSize -= m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i]].HeaderCapacity; } } for (int i = startStage; i < p.NumStages; ++i) { - int stageHeaderCapacity = m_StageCollection[m_StageList[p.FirstStageIndex + i]].HeaderCapacity; + var networkPipelineStage = m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i]]; + int stageHeaderCapacity = networkPipelineStage.HeaderCapacity; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (stageHeaderCapacity > headerSize) - throw new InvalidOperationException("The stage does not contain enough header space to send the message"); + throw new InvalidOperationException($"Stage {i} '{networkPipelineStage}' does not contain enough header space to send the message"); #endif inboundBuffer.headerPadding = headerSize; headerSize -= stageHeaderCapacity; @@ -621,7 +355,7 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta var prevInbound = inboundBuffer; NetworkPipelineStage.Requests requests = NetworkPipelineStage.Requests.None; - var sendResult = ProcessSendStage(i, internalBufferOffset, internalSharedBufferOffset, p, ref resumeQ, ref ctx, ref inboundBuffer, ref requests, systemHeaderSize); + var sendResult = ProcessSendStage(i, internalBufferOffset, internalSharedBufferOffset, p, ref resumeQ, ref ctx, ref inboundBuffer, ref requests, m_MaxPacketHeaderSize); if ((requests & NetworkPipelineStage.Requests.Update) != 0) AddSendUpdate(connection, i, pipeline, currentUpdates); @@ -635,14 +369,14 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta #if ENABLE_UNITY_COLLECTIONS_CHECKS if (inboundBuffer.headerPadding != prevInbound.headerPadding) - throw new InvalidOperationException("Changing the header padding in a pipeline is not supported"); + throw new InvalidOperationException($"Changing the header padding in a pipeline is not supported. Stage {i} '{networkPipelineStage}'."); #endif if (inboundBuffer.buffer != prevInbound.buffer) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (inboundBuffer.buffer != inboundBuffer.bufferWithHeaders + inboundBuffer.headerPadding || inboundBuffer.bufferLength + inboundBuffer.headerPadding > inboundBuffer.bufferWithHeadersLength) - throw new InvalidOperationException("When creating an internal buffer in pipelines the buffer must be a subset of the buffer with headers"); + throw new InvalidOperationException($"When creating an internal buffer in pipelines the buffer must be a subset of the buffer with headers. Stage {i} '{networkPipelineStage}'."); #endif // Copy header to new buffer so it is part of the payload UnsafeUtility.MemCpy(inboundBuffer.bufferWithHeaders + headerSize, ctx.header.AsNativeArray().GetUnsafeReadOnlyPtr(), ctx.header.Length); @@ -651,7 +385,7 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta else { if (inboundBuffer.bufferWithHeaders != prevInbound.bufferWithHeaders) - throw new InvalidOperationException("Changing the send buffer with headers without changing the buffer is not supported"); + throw new InvalidOperationException($"Changing the send buffer with headers without changing the buffer is not supported. Stage {i} '{networkPipelineStage}'."); } #endif if (ctx.header.Length < stageHeaderCapacity) @@ -702,7 +436,8 @@ internal unsafe int ProcessPipelineSend(NetworkDriver.Concurrent driver, int sta if (driver.BeginSend(connection, out var writer) == 0) { writer.WriteByte((byte)pipeline.Id); - writer.WriteBytes(inboundBuffer.buffer, inboundBuffer.bufferLength); + writer.WriteBytesUnsafe(inboundBuffer.buffer, inboundBuffer.bufferLength); + if ((retval = driver.EndSend(writer)) <= 0) { UnityEngine.Debug.Log(FixedString.Format("An error occurred during EndSend. ErrorCode: {0}", retval)); @@ -729,10 +464,10 @@ private unsafe int ProcessSendStage(int startStage, int internalBufferOffset, in PipelineImpl p, ref NativeList resumeQ, ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) { var stageIndex = p.FirstStageIndex + startStage; - var pipelineStage = m_StageCollection[m_StageList[stageIndex]]; + var pipelineStage = m_StageStructs[m_PipelineStagesIndices[stageIndex]]; ctx.accumulatedHeaderCapacity = m_AccumulatedHeaderCapacity[stageIndex]; ctx.staticInstanceBuffer = (byte*)m_StaticInstanceBuffer.GetUnsafeReadOnlyPtr() + pipelineStage.StaticStateStart; - ctx.staticInstanceBufferLength = pipelineStage.StaticStateCapcity; + ctx.staticInstanceBufferLength = pipelineStage.StaticStateCapacity; ctx.internalProcessBuffer = (byte*)sendBuffer.GetUnsafeReadOnlyPtr() + internalBufferOffset; ctx.internalProcessBufferLength = pipelineStage.SendCapacity; @@ -740,17 +475,18 @@ private unsafe int ProcessSendStage(int startStage, int internalBufferOffset, in ctx.internalSharedProcessBufferLength = pipelineStage.SharedStateCapacity; requests = NetworkPipelineStage.Requests.None; - var retval = pipelineStage.Send.Ptr.Invoke(ref ctx, ref inboundBuffer, ref requests, systemHeaderSize); + var retval = ((delegate * unmanaged[Cdecl] < ref NetworkPipelineContext, ref InboundSendBuffer, ref Requests, int, int >)pipelineStage.Send.Ptr.Value)(ref ctx, ref inboundBuffer, ref requests, systemHeaderSize); if ((requests & NetworkPipelineStage.Requests.Resume) != 0) resumeQ.Add(startStage); return retval; } } - private NativeArray m_StageCollection; - private NativeArray m_StaticInstanceBuffer; - private NativeList m_StageList; + private NativeList m_StageStructs; + private NativeList m_StageIds; + private NativeList m_PipelineStagesIndices; private NativeList m_AccumulatedHeaderCapacity; private NativeList m_Pipelines; + private NativeList m_StaticInstanceBuffer; private NativeList m_ReceiveBuffer; private NativeList m_SendBuffer; private NativeList m_SharedBuffer; @@ -762,6 +498,8 @@ private unsafe int ProcessSendStage(int startStage, int internalBufferOffset, in private NativeArray m_timestamp; + private int m_MaxPacketHeaderSize; + private const int SendSizeOffset = 0; private const int RecveiveSizeOffset = 1; private const int SharedSizeOffset = 2; @@ -778,35 +516,17 @@ internal struct PipelineImpl public int payloadCapacity; } - public unsafe NetworkPipelineProcessor(NetworkSettings settings) + public unsafe NetworkPipelineProcessor(NetworkSettings settings, int maxPacketHeaderSize) { - NetworkPipelineParams config = settings.GetPipelineParameters(); - - int staticBufferSize = 0; - for (int i = 0; i < NetworkPipelineStageCollection.m_stages.Count; ++i) - { - staticBufferSize += NetworkPipelineStageCollection.m_stages[i].StaticSize; - staticBufferSize = (staticBufferSize + 15) & (~15); - } - m_StaticInstanceBuffer = new NativeArray(staticBufferSize, Allocator.Persistent); - m_StageCollection = new NativeArray(NetworkPipelineStageCollection.m_stages.Count, Allocator.Persistent); - staticBufferSize = 0; - for (int i = 0; i < NetworkPipelineStageCollection.m_stages.Count; ++i) - { - var stageStruct = NetworkPipelineStageCollection.m_stages[i].StaticInitialize((byte*)m_StaticInstanceBuffer.GetUnsafePtr() + staticBufferSize, NetworkPipelineStageCollection.m_stages[i].StaticSize, settings); - stageStruct.StaticStateStart = staticBufferSize; - stageStruct.StaticStateCapcity = NetworkPipelineStageCollection.m_stages[i].StaticSize; - m_StageCollection[i] = stageStruct; - staticBufferSize += NetworkPipelineStageCollection.m_stages[i].StaticSize; - staticBufferSize = (staticBufferSize + 15) & (~15); - } - - m_StageList = new NativeList(16, Allocator.Persistent); + m_StaticInstanceBuffer = new NativeList(0, Allocator.Persistent); + m_StageStructs = new NativeList(8, Allocator.Persistent); + m_StageIds = new NativeList(8, Allocator.Persistent); + m_PipelineStagesIndices = new NativeList(16, Allocator.Persistent); m_AccumulatedHeaderCapacity = new NativeList(16, Allocator.Persistent); m_Pipelines = new NativeList(16, Allocator.Persistent); - m_ReceiveBuffer = new NativeList(config.initialCapacity, Allocator.Persistent); - m_SendBuffer = new NativeList(config.initialCapacity, Allocator.Persistent); - m_SharedBuffer = new NativeList(config.initialCapacity, Allocator.Persistent); + m_ReceiveBuffer = new NativeList(0, Allocator.Persistent); + m_SendBuffer = new NativeList(0, Allocator.Persistent); + m_SharedBuffer = new NativeList(0, Allocator.Persistent); sizePerConnection = new NativeArray(3, Allocator.Persistent); // Store an int for the spinlock first in each connections send buffer, round up to alignment of 8 sizePerConnection[SendSizeOffset] = Alignment; @@ -814,11 +534,21 @@ public unsafe NetworkPipelineProcessor(NetworkSettings settings) m_SendStageNeedsUpdate = new NativeList(128, Allocator.Persistent); m_SendStageNeedsUpdateRead = new NativeQueue(Allocator.Persistent); m_timestamp = new NativeArray(1, Allocator.Persistent); + m_MaxPacketHeaderSize = maxPacketHeaderSize; + + RegisterPipelineStage(new NullPipelineStage(), settings); + RegisterPipelineStage(new FragmentationPipelineStage(), settings); + RegisterPipelineStage(new ReliableSequencedPipelineStage(), settings); + RegisterPipelineStage(new UnreliableSequencedPipelineStage(), settings); + RegisterPipelineStage(new SimulatorPipelineStage(), settings); + #pragma warning disable CS0618 + RegisterPipelineStage(new SimulatorPipelineStageInSend(), settings); + #pragma warning restore CS0618 } public void Dispose() { - m_StageList.Dispose(); + m_PipelineStagesIndices.Dispose(); m_AccumulatedHeaderCapacity.Dispose(); m_ReceiveBuffer.Dispose(); m_SendBuffer.Dispose(); @@ -829,7 +559,8 @@ public void Dispose() m_SendStageNeedsUpdate.Dispose(); m_SendStageNeedsUpdateRead.Dispose(); m_timestamp.Dispose(); - m_StageCollection.Dispose(); + m_StageStructs.Dispose(); + m_StageIds.Dispose(); m_StaticInstanceBuffer.Dispose(); } @@ -839,11 +570,34 @@ public long Timestamp internal set { m_timestamp[0] = value; } } - public unsafe void initializeConnection(NetworkConnection con) + public unsafe void RegisterPipelineStage(T stage, NetworkSettings settings) where T : unmanaged, INetworkPipelineStage + { + // Resize the static buffer. + var oldStaticBufferSize = m_StaticInstanceBuffer.Length; + if (stage.StaticSize > 0) + { + var newStaticBufferSize = oldStaticBufferSize + stage.StaticSize; + newStaticBufferSize = (newStaticBufferSize + 15) & (~15); + m_StaticInstanceBuffer.ResizeUninitialized(newStaticBufferSize); + } + + // Create the stage structure. + var staticBufferOffset = oldStaticBufferSize; + var staticBufferPtr = (byte*)m_StaticInstanceBuffer.GetUnsafePtr() + staticBufferOffset; + var stageStruct = stage.StaticInitialize(staticBufferPtr, stage.StaticSize, settings); + stageStruct.StaticStateStart = staticBufferOffset; + stageStruct.StaticStateCapacity = stage.StaticSize; + + // Add the stage structure and ID to internal lists. + m_StageStructs.Add(stageStruct); + m_StageIds.Add(NetworkPipelineStageId.Get()); + } + + public unsafe void InitializeConnection(NetworkConnection con) { - var requiredReceiveSize = (con.m_NetworkId + 1) * sizePerConnection[RecveiveSizeOffset]; - var requiredSendSize = (con.m_NetworkId + 1) * sizePerConnection[SendSizeOffset]; - var requiredSharedSize = (con.m_NetworkId + 1) * sizePerConnection[SharedSizeOffset]; + var requiredReceiveSize = (con.InternalId + 1) * sizePerConnection[RecveiveSizeOffset]; + var requiredSendSize = (con.InternalId + 1) * sizePerConnection[SendSizeOffset]; + var requiredSharedSize = (con.InternalId + 1) * sizePerConnection[SharedSizeOffset]; if (m_ReceiveBuffer.Length < requiredReceiveSize) m_ReceiveBuffer.ResizeUninitialized(requiredReceiveSize); if (m_SendBuffer.Length < requiredSendSize) @@ -851,11 +605,11 @@ public unsafe void initializeConnection(NetworkConnection con) if (m_SharedBuffer.Length < requiredSharedSize) m_SharedBuffer.ResizeUninitialized(requiredSharedSize); - UnsafeUtility.MemClear((byte*)m_ReceiveBuffer.GetUnsafePtr() + con.m_NetworkId * sizePerConnection[RecveiveSizeOffset], sizePerConnection[RecveiveSizeOffset]); - UnsafeUtility.MemClear((byte*)m_SendBuffer.GetUnsafePtr() + con.m_NetworkId * sizePerConnection[SendSizeOffset], sizePerConnection[SendSizeOffset]); - UnsafeUtility.MemClear((byte*)m_SharedBuffer.GetUnsafePtr() + con.m_NetworkId * sizePerConnection[SharedSizeOffset], sizePerConnection[SharedSizeOffset]); + UnsafeUtility.MemClear((byte*)m_ReceiveBuffer.GetUnsafePtr() + con.InternalId * sizePerConnection[RecveiveSizeOffset], sizePerConnection[RecveiveSizeOffset]); + UnsafeUtility.MemClear((byte*)m_SendBuffer.GetUnsafePtr() + con.InternalId * sizePerConnection[SendSizeOffset], sizePerConnection[SendSizeOffset]); + UnsafeUtility.MemClear((byte*)m_SharedBuffer.GetUnsafePtr() + con.InternalId * sizePerConnection[SharedSizeOffset], sizePerConnection[SharedSizeOffset]); - InitializeStages(con.m_NetworkId); + InitializeStages(con.InternalId); } unsafe void InitializeStages(int networkId) @@ -874,7 +628,7 @@ unsafe void InitializeStages(int networkId) stage < pipeline.FirstStageIndex + pipeline.NumStages; stage++) { - var pipelineStage = m_StageCollection[m_StageList[stage]]; + var pipelineStage = m_StageStructs[m_PipelineStagesIndices[stage]]; var sendProcessBuffer = (byte*)m_SendBuffer.GetUnsafePtr() + sendBufferOffset; var sendProcessBufferLength = pipelineStage.SendCapacity; var recvProcessBuffer = (byte*)m_ReceiveBuffer.GetUnsafePtr() + recvBufferOffset; @@ -883,8 +637,8 @@ unsafe void InitializeStages(int networkId) var sharedProcessBufferLength = pipelineStage.SharedStateCapacity; var staticInstanceBuffer = (byte*)m_StaticInstanceBuffer.GetUnsafePtr() + pipelineStage.StaticStateStart; - var staticInstanceBufferLength = pipelineStage.StaticStateCapcity; - pipelineStage.InitializeConnection.Ptr.Invoke(staticInstanceBuffer, staticInstanceBufferLength, + var staticInstanceBufferLength = pipelineStage.StaticStateCapacity; + ((delegate * unmanaged[Cdecl] < byte*, int, byte*, int, byte*, int, byte*, int, void >)pipelineStage.InitializeConnection.Ptr.Value)(staticInstanceBuffer, staticInstanceBufferLength, sendProcessBuffer, sendProcessBufferLength, recvProcessBuffer, recvProcessBufferLength, sharedProcessBuffer, sharedProcessBufferLength); @@ -896,10 +650,17 @@ unsafe void InitializeStages(int networkId) } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - private void ValidateStages(params Type[] stages) + private void ValidateStages(NativeArray stages) { - var reliableIndex = Array.IndexOf(stages, typeof(ReliableSequencedPipelineStage)); - var fragmentedIndex = Array.IndexOf(stages, typeof(FragmentationPipelineStage)); + var reliableIndex = -1; + var fragmentedIndex = -1; + for (int i = 0; i < stages.Length; i++) + { + if (stages[i] == NetworkPipelineStageId.Get()) + reliableIndex = i; + if (stages[i] == NetworkPipelineStageId.Get()) + fragmentedIndex = i; + } // Check that fragmentation doesn't follow the reliability pipeline. This order is not // supported since the reliability pipeline can't handle packets larger than the MTU. @@ -907,14 +668,7 @@ private void ValidateStages(params Type[] stages) throw new InvalidOperationException("Cannot create pipeline with ReliableSequenced followed by Fragmentation stage. Should reverse their order."); } - /// - /// Create a new NetworkPipeline. - /// - /// The stages we want the pipeline to contain. - /// A valid pipeline is returned. - /// Thrown if you try to create more then 255 pipelines. - /// Thrown if you try to use a invalid pipeline stage. - public NetworkPipeline CreatePipeline(params Type[] stages) + public NetworkPipeline CreatePipeline(NativeArray stages) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (m_Pipelines.Length > 255) @@ -927,25 +681,25 @@ public NetworkPipeline CreatePipeline(params Type[] stages) var headerCap = 0; var payloadCap = 0; var pipeline = new PipelineImpl(); - pipeline.FirstStageIndex = m_StageList.Length; + pipeline.FirstStageIndex = m_PipelineStagesIndices.Length; pipeline.NumStages = stages.Length; for (int i = 0; i < stages.Length; i++) { - var stageId = NetworkPipelineStageCollection.GetStageId(stages[i]).Index; + var stageIndex = m_StageIds.IndexOf(stages[i]); #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (stageId < 0) - throw new InvalidOperationException("Trying to create pipeline with invalid stage " + stages[i]); + if (stageIndex == -1) + throw new InvalidOperationException("Trying to create pipeline with invalid stage."); #endif - m_StageList.Add(stageId); + m_PipelineStagesIndices.Add(stageIndex); m_AccumulatedHeaderCapacity.Add(headerCap); // For every stage, compute how much header space has already bee used by other stages when sending // Make sure all data buffers are aligned - receiveCap += (m_StageCollection[stageId].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - sendCap += (m_StageCollection[stageId].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - headerCap += m_StageCollection[stageId].HeaderCapacity; - sharedCap += (m_StageCollection[stageId].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + receiveCap += (m_StageStructs[stageIndex].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + sendCap += (m_StageStructs[stageIndex].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + headerCap += m_StageStructs[stageIndex].HeaderCapacity; + sharedCap += (m_StageStructs[stageIndex].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); if (payloadCap == 0) { - payloadCap = m_StageCollection[stageId].PayloadCapacity; // The first non-zero stage determines the pipeline capacity + payloadCap = m_StageStructs[stageIndex].PayloadCapacity; // The first non-zero stage determines the pipeline capacity } } @@ -969,20 +723,18 @@ public void GetPipelineBuffers(NetworkPipeline pipelineId, NetworkPipelineStageI out NativeArray readProcessingBuffer, out NativeArray writeProcessingBuffer, out NativeArray sharedBuffer) { + if (pipelineId.Id - 1 < 0 || pipelineId.Id - 1 >= m_Pipelines.Length) + { #if ENABLE_UNITY_COLLECTIONS_CHECKS - if (pipelineId.Id < 1) - throw new InvalidOperationException("The specified pipeline is not valid"); - if (stageId.IsValid == 0) - throw new InvalidOperationException("The specified pipeline stage is not valid"); + throw new InvalidOperationException("The specified pipeline is not valid."); #else - if (pipelineId.Id < 1 || stageId.IsValid == 0) - { writeProcessingBuffer = default; readProcessingBuffer = default; sharedBuffer = default; return; - } #endif + } + var pipeline = m_Pipelines[pipelineId.Id - 1]; int recvBufferOffset = pipeline.receiveBufferOffset + sizePerConnection[RecveiveSizeOffset] * connection.InternalId; @@ -990,25 +742,25 @@ public void GetPipelineBuffers(NetworkPipeline pipelineId, NetworkPipelineStageI int sharedBufferOffset = pipeline.sharedBufferOffset + sizePerConnection[SharedSizeOffset] * connection.InternalId; int stageIndexInList; - bool stageNotFound = true; + bool stageFound = true; for (stageIndexInList = pipeline.FirstStageIndex; stageIndexInList < pipeline.FirstStageIndex + pipeline.NumStages; stageIndexInList++) { - if (m_StageList[stageIndexInList] == stageId.Index) + if (m_StageIds[m_PipelineStagesIndices[stageIndexInList]] == stageId) { - stageNotFound = false; + stageFound = true; break; } - sendBufferOffset += (m_StageCollection[m_StageList[stageIndexInList]].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - recvBufferOffset += (m_StageCollection[m_StageList[stageIndexInList]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - sharedBufferOffset += (m_StageCollection[m_StageList[stageIndexInList]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + sendBufferOffset += (m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].SendCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + recvBufferOffset += (m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + sharedBufferOffset += (m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); } - if (stageNotFound) + if (!stageFound) { #if ENABLE_UNITY_COLLECTIONS_CHECKS - throw new InvalidOperationException($"Could not find stage ID {stageId} make sure the type for this stage ID is added when the pipeline is created."); + throw new InvalidOperationException("Could not find stage ID. Make sure the type for this stage ID is added when the pipeline is created."); #else writeProcessingBuffer = default; readProcessingBuffer = default; @@ -1017,9 +769,42 @@ public void GetPipelineBuffers(NetworkPipeline pipelineId, NetworkPipelineStageI #endif } - writeProcessingBuffer = ((NativeArray)m_SendBuffer).GetSubArray(sendBufferOffset, m_StageCollection[m_StageList[stageIndexInList]].SendCapacity); - readProcessingBuffer = ((NativeArray)m_ReceiveBuffer).GetSubArray(recvBufferOffset, m_StageCollection[m_StageList[stageIndexInList]].ReceiveCapacity); - sharedBuffer = ((NativeArray)m_SharedBuffer).GetSubArray(sharedBufferOffset, m_StageCollection[m_StageList[stageIndexInList]].SharedStateCapacity); + writeProcessingBuffer = m_SendBuffer.AsArray().GetSubArray(sendBufferOffset, m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].SendCapacity); + readProcessingBuffer = m_ReceiveBuffer.AsArray().GetSubArray(recvBufferOffset, m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].ReceiveCapacity); + sharedBuffer = m_SharedBuffer.AsArray().GetSubArray(sharedBufferOffset, m_StageStructs[m_PipelineStagesIndices[stageIndexInList]].SharedStateCapacity); + } + + /// + /// Returns a writable pointer to the associated with this stage. + /// Note that Parameters are not designed to be runtime-modified, so this API is unsafe and experimental. + /// NetCode use-case: Runtime editing of simulator params. + /// + internal unsafe T* GetWriteablePipelineParameter(NetworkPipelineStageId stageId) + where T : unmanaged, INetworkParameter + { + var stageIndex = m_StageIds.IndexOf(stageId); + if (stageIndex == -1) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("Could not find stage ID. Make sure the type for this stage ID is added when the pipeline is created."); +#else + return null; +#endif + } + + var pipelineStage = m_StageStructs[stageIndex]; + var staticInstanceBuffer = (byte*)m_StaticInstanceBuffer.GetUnsafePtr() + pipelineStage.StaticStateStart; + var staticInstanceBufferLength = pipelineStage.StaticStateCapacity; + if (staticInstanceBufferLength != sizeof(T)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException($"GetPipelineParameterBuffer type mismatch! Expected staticInstanceBufferLength ({staticInstanceBufferLength}) to be of sizeof({typeof(T)}) ({sizeof(T)})!"); +#else + return null; +#endif + } + + return (T*)staticInstanceBuffer; } internal struct UpdatePipeline @@ -1032,7 +817,7 @@ internal struct UpdatePipeline internal unsafe void UpdateSend(NetworkDriver.Concurrent driver, out int updateCount) { // Clear the send lock since it cannot be kept here and can be lost if there are exceptions in send - NativeArray tmpBuffer = m_SendBuffer; + NativeArray tmpBuffer = m_SendBuffer.AsArray(); int* sendBufferLock = (int*)tmpBuffer.GetUnsafePtr(); for (int connectionOffset = 0; connectionOffset < m_SendBuffer.Length; connectionOffset += sizePerConnection[SendSizeOffset]) sendBufferLock[connectionOffset / 4] = 0; @@ -1085,7 +870,7 @@ private static void AddSendUpdate(NetworkConnection connection, int stageId, Net currentUpdates.Add(newUpdate); } - public void UpdateReceive(NetworkDriver driver, out int updateCount) + public void UpdateReceive(ref NetworkDriver driver, out int updateCount) { NativeArray receiveUpdates = new NativeArray(m_ReceiveStageNeedsUpdate.Length, Allocator.Temp); @@ -1102,36 +887,38 @@ public void UpdateReceive(NetworkDriver driver, out int updateCount) for (int i = 0; i < updateCount; ++i) { UpdatePipeline updateItem = receiveUpdates[i]; - ProcessReceiveStagesFrom(driver, updateItem.stage, updateItem.pipeline, updateItem.connection, default); + var receiver = driver.Receiver; + var eventQueue = driver.EventQueue; + ProcessReceiveStagesFrom(ref receiver, ref eventQueue, updateItem.stage, updateItem.pipeline, updateItem.connection, default); } } - public unsafe void Receive(NetworkDriver driver, NetworkConnection connection, NativeArray buffer) + public unsafe void Receive(byte pipelineId, ref NetworkDriverReceiver receiver, ref NetworkEventQueue eventQueue, ref NetworkConnection connection, byte* buffer, int bufferLength) { - byte pipelineId = buffer[0]; if (pipelineId == 0 || pipelineId > m_Pipelines.Length) { - UnityEngine.Debug.LogError("Received a packet with an invalid pipeline."); + UnityEngine.Debug.LogError( + $"Received a packet with an invalid pipeline ({pipelineId}, should be between 1 and {m_Pipelines.Length}). Possible mismatch between pipeline definitions on each end of the connection."); return; } var p = m_Pipelines[pipelineId - 1]; int startStage = p.NumStages - 1; InboundRecvBuffer inBuffer; - inBuffer.buffer = (byte*)buffer.GetUnsafePtr() + 1; - inBuffer.bufferLength = buffer.Length - 1; - ProcessReceiveStagesFrom(driver, startStage, new NetworkPipeline {Id = pipelineId}, connection, inBuffer); + inBuffer.buffer = buffer; + inBuffer.bufferLength = bufferLength; + ProcessReceiveStagesFrom(ref receiver, ref eventQueue, startStage, new NetworkPipeline {Id = pipelineId}, connection, inBuffer); } - private unsafe void ProcessReceiveStagesFrom(NetworkDriver driver, int startStage, NetworkPipeline pipeline, + private unsafe void ProcessReceiveStagesFrom(ref NetworkDriverReceiver receiver, ref NetworkEventQueue eventQueue, int startStage, NetworkPipeline pipeline, NetworkConnection connection, InboundRecvBuffer buffer) { var p = m_Pipelines[pipeline.Id - 1]; - var connectionId = connection.m_NetworkId; + var connectionId = connection.InternalId; var resumeQ = new NativeList(16, Allocator.Temp); int resumeQStart = 0; - var systemHeaderSize = driver.MaxProtocolHeaderSize(); + var systemHeaderSize = m_MaxPacketHeaderSize; var inboundBuffer = buffer; @@ -1151,8 +938,8 @@ private unsafe void ProcessReceiveStagesFrom(NetworkDriver driver, int startStag // Adjust offset accounting for stages in front of the starting stage, since we're parsing the stages in reverse order for (int st = 0; st < startStage; ++st) { - internalBufferOffset += (m_StageCollection[m_StageList[p.FirstStageIndex + st]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); - internalSharedBufferOffset += (m_StageCollection[m_StageList[p.FirstStageIndex + st]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + internalBufferOffset += (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + st]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + internalSharedBufferOffset += (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + st]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); } for (int i = startStage; i >= 0; --i) @@ -1184,16 +971,16 @@ private unsafe void ProcessReceiveStagesFrom(NetworkDriver driver, int startStag if (i > 0) { internalBufferOffset -= - (m_StageCollection[m_StageList[p.FirstStageIndex + i - 1]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i - 1]].ReceiveCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); internalSharedBufferOffset -= - (m_StageCollection[m_StageList[p.FirstStageIndex + i - 1]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); + (m_StageStructs[m_PipelineStagesIndices[p.FirstStageIndex + i - 1]].SharedStateCapacity + AlignmentMinusOne) & (~AlignmentMinusOne); } needsUpdate = false; } if (inboundBuffer.bufferLength != 0) - driver.PushDataEvent(connection, pipeline.Id, inboundBuffer.buffer, inboundBuffer.bufferLength); + receiver.PushDataEvent(connection, pipeline.Id, inboundBuffer.buffer, inboundBuffer.bufferLength, ref eventQueue); if (resumeQStart >= resumeQ.Length) { @@ -1211,29 +998,22 @@ private unsafe void ProcessReceiveStage(int stage, NetworkPipeline pipeline, int { var p = m_Pipelines[pipeline.Id - 1]; - var stageId = m_StageList[p.FirstStageIndex + stage]; - var pipelineStage = m_StageCollection[stageId]; + var stageId = m_PipelineStagesIndices[p.FirstStageIndex + stage]; + var pipelineStage = m_StageStructs[stageId]; ctx.staticInstanceBuffer = (byte*)m_StaticInstanceBuffer.GetUnsafePtr() + pipelineStage.StaticStateStart; - ctx.staticInstanceBufferLength = pipelineStage.StaticStateCapcity; + ctx.staticInstanceBufferLength = pipelineStage.StaticStateCapacity; ctx.internalProcessBuffer = (byte*)m_ReceiveBuffer.GetUnsafePtr() + internalBufferOffset; ctx.internalProcessBufferLength = pipelineStage.ReceiveCapacity; ctx.internalSharedProcessBuffer = (byte*)m_SharedBuffer.GetUnsafePtr() + internalSharedBufferOffset; ctx.internalSharedProcessBufferLength = pipelineStage.SharedStateCapacity; NetworkPipelineStage.Requests requests = NetworkPipelineStage.Requests.None; - pipelineStage.Receive.Ptr.Invoke(ref ctx, ref inboundBuffer, ref requests, systemHeadersSize); + ((delegate * unmanaged[Cdecl] < ref NetworkPipelineContext, ref InboundRecvBuffer, ref Requests, int, void >)pipelineStage.Receive.Ptr.Value)(ref ctx, ref inboundBuffer, ref requests, systemHeadersSize); if ((requests & NetworkPipelineStage.Requests.Resume) != 0) resumeQ.Add(stage); needsUpdate = (requests & NetworkPipelineStage.Requests.Update) != 0; needsSendUpdate = (requests & NetworkPipelineStage.Requests.SendUpdate) != 0; } - - [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - public static void ValidateSendHandle(NetworkInterfaceSendHandle handle) - { - if (handle.data == IntPtr.Zero) - throw new ArgumentException($"Value for NetworkDataStreamParameter.size must be larger then zero."); - } } } diff --git a/Runtime/NetworkProtocol.cs b/Runtime/NetworkProtocol.cs deleted file mode 100644 index 06d40d2..0000000 --- a/Runtime/NetworkProtocol.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Unity.Collections.LowLevel.Unsafe; - -namespace Unity.Networking.Transport -{ - internal interface INetworkProtocol : IDisposable - { - /// - /// This is call when initializing the NetworkDriver. If the protocol requires custom paramters, they can be passed - /// to the NetworkDriver initialization. - /// - void Initialize(NetworkSettings settings); - - /// - /// Returns a burst compatible NetworkProtocol struct containing the function pointers and custom UserData for the protocol. - /// - NetworkProtocol CreateProtocolInterface(); - - /// - /// This method should bind the NetworkInterface to the local endpoint and perform any - /// custom binding behaviour for the protocol. - /// - int Bind(INetworkInterface networkInterface, ref NetworkInterfaceEndPoint localEndPoint); - - /// - /// Create a new connection address for the endPoint using the passed NetworkInterface. - /// Some protocols - as relay - could decide to use virtual addressed that not necessarily - /// maps 1 - 1 to a endpoint. - /// - int CreateConnectionAddress(INetworkInterface networkInterface, NetworkEndPoint endPoint, out NetworkInterfaceEndPoint address); - - NetworkEndPoint GetRemoteEndPoint(INetworkInterface networkInterface, NetworkInterfaceEndPoint address); - } - - /// - /// This is a Burst compatible struct that contains all the function pointers that the NetworkDriver - /// uses for processing messages with a particular protocol. - /// - internal struct NetworkProtocol - { - /// - /// Computes the size required for allocating a packet for the connection with this protocol. The dataCapacity received - /// can be modified to reflect the resulting payload capacity of the packet, if it gets reduced the NetworkDriver will - /// return a NetworkPacketOverflow error. The payloadOffset return value is the possition where the payload data needs - /// to be stored in the packet. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int ComputePacketOverheadDelegate(ref NetworkDriver.Connection connection, out int payloadOffset); - - /// - /// Process a receiving packet and returns a ProcessPacketCommand that will indicate to the NetworkDriver what actions - /// need to be performed and what to do with the message. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ProcessReceiveDelegate(IntPtr stream, ref NetworkInterfaceEndPoint address, int size, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData, ref ProcessPacketCommand command); - - /// - /// Process a sending packet. When this method is called, the packet is ready to be sent through the sendInterface. - /// Here the protocol could perform some final steps as, for instance, filling some header fields. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int ProcessSendDelegate(ref NetworkDriver.Connection connection, bool hasPipeline, ref NetworkSendInterface sendInterface, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// This method should send a protocol specific connect confirmation message from a server to a client using the connection. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ProcessSendConnectionAcceptDelegate(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// Try to establish a connection (from a client to a server). May only be called by a - /// client, and may be called multiple times for a connection (when retrying an attempt). - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ConnectDelegate(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// Close a connection. This method should notify the remote peer of the disconnection. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void DisconnectDelegate(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// This method should send a protocol specific heartbeat request (ping) message on the connection. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ProcessSendPingDelegate(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// This method should send a protocol specific heartbeat response (pong) message on the connection. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void ProcessSendPongDelegate(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - /// - /// This method is called every NetworkDriver tick and can be used for performing protocol update operations. - /// One common case is sending protocol specific packets for keeping the connections alive or retrying failed ones. - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void UpdateDelegate(long updateTime, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData); - - public TransportFunctionPointer ComputePacketOverhead; - public TransportFunctionPointer ProcessReceive; - public TransportFunctionPointer ProcessSend; - public TransportFunctionPointer ProcessSendConnectionAccept; - public TransportFunctionPointer Connect; - public TransportFunctionPointer Disconnect; - public TransportFunctionPointer ProcessSendPing; - public TransportFunctionPointer ProcessSendPong; - public TransportFunctionPointer Update; - - /// - /// Raw pointer that is going to be passed to the function pointer and that can contain protocol specific data. - /// - [NativeDisableUnsafePtrRestriction] public IntPtr UserData; - - /// - /// The maximun size of the header of a data packet for this protocol. - /// - public int MaxHeaderSize; - - /// - /// The maximun size of the footer of a data packet for this protocol. - /// - public int MaxFooterSize; - - /// - /// The maximun amount of bytes that are not payload data for a packet for this protocol. - /// - public int PaddingSize => MaxHeaderSize + MaxFooterSize; - - /// - /// If true - UpdateDelegate will be called - /// - public bool NeedsUpdate; - - public NetworkProtocol( - TransportFunctionPointer computePacketOverhead, - TransportFunctionPointer processReceive, - TransportFunctionPointer processSend, - TransportFunctionPointer processSendConnectionAccept, - TransportFunctionPointer connect, - TransportFunctionPointer disconnect, - TransportFunctionPointer processSendPing, - TransportFunctionPointer processSendPong, - TransportFunctionPointer update, - bool needsUpdate, - IntPtr userData, - int maxHeaderSize, - int maxFooterSize - ) - { - ComputePacketOverhead = computePacketOverhead; - ProcessReceive = processReceive; - ProcessSend = processSend; - ProcessSendConnectionAccept = processSendConnectionAccept; - Connect = connect; - Disconnect = disconnect; - ProcessSendPing = processSendPing; - ProcessSendPong = processSendPong; - Update = update; - NeedsUpdate = needsUpdate; - UserData = userData; - MaxHeaderSize = maxHeaderSize; - MaxFooterSize = maxFooterSize; - } - } - - /// - /// The type of commands that the NetworkDriver can process from a received packet. - /// - internal enum ProcessPacketCommandType : byte - { - /// Do not perform any extra action. - Drop = 0, // Keepit 0 to make it the default. - - /// Find and update the address for a connection. - AddressUpdate, - - /// - /// The connection has been accepted by the server and can be completed. - /// - ConnectionAccept, - - /// The connection has been rejected by the server. - ConnectionReject, - - /// A connection request was received from a client. - ConnectionRequest, - - /// A data message has been received for an stablished connection. - Data, - - /// The connection is requesting to disconnect. - Disconnect, - - /// Combination of ConnectionAccept and Data. - DataWithImplicitConnectionAccept, - - /// - /// A hearbeat request (ping) was received. - /// - Ping, - - /// - /// A heartbeat response (pong) was received. - /// - Pong, - - /// - /// A change in the protocol status occurred. - /// - ProtocolStatusUpdate, - } - - /// - /// Contains the command type and command data required by the NetworkDriver to process a packet. - /// - internal struct ProcessPacketCommand - { - /// - /// The type of the command to process - /// - public ProcessPacketCommandType Type; - - /// - /// The endpoint from which the packet was received - /// - public NetworkInterfaceEndPoint Address; - - /// - /// The session token of the packet - /// - public SessionIdToken SessionId; - - /// - /// Details specific to the packet type - /// - public ProcessPacketCommandAs As; - - // Acts like a C/C++ union type. - [StructLayout(LayoutKind.Explicit)] - public struct ProcessPacketCommandAs - { - [FieldOffset(0)] public AsAddressUpdate AddressUpdate; - [FieldOffset(0)] public AsConnectionAccept ConnectionAccept; - [FieldOffset(0)] public AsData Data; - [FieldOffset(0)] public AsDataWithImplicitConnectionAccept DataWithImplicitConnectionAccept; - [FieldOffset(0)] public AsProtocolStatusUpdate ProtocolStatusUpdate; - - public struct AsAddressUpdate - { - public NetworkInterfaceEndPoint NewAddress; - } - - public struct AsConnectionAccept - { - public SessionIdToken ConnectionToken; - } - - public struct AsData - { - public int Offset; - public int Length; - public byte HasPipelineByte; - - public bool HasPipeline => HasPipelineByte != 0; - } - - public struct AsDataWithImplicitConnectionAccept - { - public int Offset; - public int Length; - public byte HasPipelineByte; - public SessionIdToken ConnectionToken; - - public bool HasPipeline => HasPipelineByte != 0; - } - - public struct AsProtocolStatusUpdate - { - public int Status; - } - } - } -} diff --git a/Runtime/NetworkProtocols.cs b/Runtime/NetworkProtocols.cs index 6c56764..1ceb086 100644 --- a/Runtime/NetworkProtocols.cs +++ b/Runtime/NetworkProtocols.cs @@ -3,30 +3,18 @@ namespace Unity.Networking.Transport.Protocols { - internal enum UdpCProtocol - { - ConnectionRequest, - ConnectionReject, - ConnectionAccept, - Disconnect, - Data, - Ping, - Pong, - } - [StructLayout(LayoutKind.Sequential)] internal unsafe struct UdpCHeader { [Flags] public enum HeaderFlags : byte { - HasConnectToken = 0x1, - HasPipeline = 0x2 + HasPipeline = 0x1 } - public const int Length = 2 + SessionIdToken.k_Length; //explanation of constant 2 in this expression = sizeof(Type) + sizeof(HeaderFlags) + public const int Length = 2 + ConnectionToken.k_Length; //explanation of constant 2 in this expression = sizeof(Type) + sizeof(HeaderFlags) public byte Type; public HeaderFlags Flags; - public SessionIdToken SessionToken; + public ConnectionToken ConnectionToken; } } diff --git a/Runtime/NetworkSettings.cs b/Runtime/NetworkSettings.cs index 4ffa667..82bcbfa 100644 --- a/Runtime/NetworkSettings.cs +++ b/Runtime/NetworkSettings.cs @@ -20,9 +20,10 @@ private struct ParameterSlice private const int k_MapInitialCapacity = 8; - private NativeHashMap m_ParameterOffsets; + private NativeParallelHashMap m_ParameterOffsets; private NativeList m_Parameters; private byte m_Initialized; + private byte m_ReadOnly; private bool EnsureInitializedOrError() { @@ -30,7 +31,7 @@ private bool EnsureInitializedOrError() { m_Initialized = 1; m_Parameters = new NativeList(Allocator.Temp); - m_ParameterOffsets = new NativeHashMap(k_MapInitialCapacity, Allocator.Temp); + m_ParameterOffsets = new NativeParallelHashMap(k_MapInitialCapacity, Allocator.Temp); } if (!m_Parameters.IsCreated) @@ -56,8 +57,30 @@ private bool EnsureInitializedOrError() public NetworkSettings(Allocator allocator) { m_Initialized = 1; + m_ReadOnly = 0; m_Parameters = new NativeList(allocator); - m_ParameterOffsets = new NativeHashMap(k_MapInitialCapacity, allocator); + m_ParameterOffsets = new NativeParallelHashMap(k_MapInitialCapacity, allocator); + } + + internal NetworkSettings(NetworkSettings from, Allocator allocator) + { + m_Initialized = 1; + m_ReadOnly = 0; + + if (from.m_Initialized == 0) + { + m_Parameters = new NativeList(allocator); + m_ParameterOffsets = new NativeParallelHashMap(k_MapInitialCapacity, allocator); + return; + } + + m_Parameters = new NativeList(from.m_Parameters.Length, allocator); + m_Parameters.AddRangeNoResize(from.m_Parameters); + + var keys = from.m_ParameterOffsets.GetKeyArray(Allocator.Temp); + m_ParameterOffsets = new NativeParallelHashMap(keys.Length, allocator); + for (int i = 0; i < keys.Length; i++) + m_ParameterOffsets.Add(keys[i], from.m_ParameterOffsets[keys[i]]); } public void Dispose() @@ -72,6 +95,15 @@ public void Dispose() } } + /// Get a read-only copy of the settings. + /// A read-only copy of the settings. + public NetworkSettings AsReadOnly() + { + var settings = this; + settings.m_ReadOnly = 1; + return settings; + } + /// /// Adds a new parameter to the list. There must be only one instance per parameter type. /// @@ -83,6 +115,12 @@ public unsafe void AddRawParameterStruct(ref T parameter) where T : unmanaged if (!EnsureInitializedOrError()) return; + if (m_ReadOnly != 0) + { + UnityEngine.Debug.LogError("NetworkSettings structure is read-only, modifications are not allowed."); + return; + } + ValidateParameterOrError(ref parameter); var typeHash = BurstRuntime.GetHashCode64(); @@ -147,132 +185,5 @@ internal static void ValidateParameterOrError(ref T parameter) where T : INet #endif } } - - // TODO: Remove when INetworkParameter[] constructors are deprecated on NetworkDriver - #region LEGACY - -#if !UNITY_DOTSRUNTIME - internal static unsafe NetworkSettings FromArray(params INetworkParameter[] parameters) - { - var networkParameters = new NetworkSettings(Allocator.Temp); - - for (int i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var type = parameter.GetType(); - - // LEGACY transformations: Some parameters were previously assuming that - // the default 0 values converted to the actual default value. -#if !UNITY_WEBGL - if (type == typeof(BaselibNetworkParameter)) - { - var p = (BaselibNetworkParameter)parameter; - - if (p.receiveQueueCapacity == 0) - p.receiveQueueCapacity = BaselibNetworkParameterExtensions.k_defaultRxQueueSize; - - if (p.sendQueueCapacity == 0) - p.sendQueueCapacity = BaselibNetworkParameterExtensions.k_defaultTxQueueSize; - - if (p.maximumPayloadSize == 0) - p.maximumPayloadSize = BaselibNetworkParameterExtensions.k_defaultMaximumPayloadSize; - - parameter = p; - } -#endif - if (type == typeof(Relay.RelayNetworkParameter)) - { - var p = (Relay.RelayNetworkParameter)parameter; - - if (p.RelayConnectionTimeMS == 0) - p.RelayConnectionTimeMS = Relay.RelayNetworkParameter.k_DefaultConnectionTimeMS; - - parameter = p; - } -#if ENABLE_MANAGED_UNITYTLS - else if (type == typeof(Unity.Networking.Transport.TLS.SecureNetworkProtocolParameter)) - { - var p = (Unity.Networking.Transport.TLS.SecureNetworkProtocolParameter)parameter; - - if (p.SSLHandshakeTimeoutMin == 0) - p.SSLHandshakeTimeoutMin = Unity.Networking.Transport.TLS.SecureNetworkProtocol.DefaultParameters.SSLHandshakeTimeoutMin; - - if (p.SSLHandshakeTimeoutMax == 0) - p.SSLHandshakeTimeoutMax = Unity.Networking.Transport.TLS.SecureNetworkProtocol.DefaultParameters.SSLHandshakeTimeoutMax; - - parameter = p; - } -#endif - - try - { - ValidateParameterOrError(ref parameter); - } - catch (Exception e) - { - networkParameters.Dispose(); - throw e; - } - - var typeHash = BurstRuntime.GetHashCode64(type); - var parameterSlice = new ParameterSlice - { - Offset = networkParameters.m_Parameters.Length, - Size = UnsafeUtility.SizeOf(type), - }; - - networkParameters.m_ParameterOffsets.Add(typeHash, parameterSlice); - networkParameters.m_Parameters.Resize(networkParameters.m_Parameters.Length + parameterSlice.Size, NativeArrayOptions.UninitializedMemory); - - var valuePtr = ((byte*)networkParameters.m_Parameters.GetUnsafePtr() + parameterSlice.Offset); - var parameterPtr = (byte*)UnsafeUtility.PinGCObjectAndGetAddress(parameter, out var gcHandle) + ObjectHeaderOffset; - - UnsafeUtility.MemCpy(valuePtr, parameterPtr, parameterSlice.Size); - UnsafeUtility.ReleaseGCObject(gcHandle); - } - - return networkParameters; - } - - internal unsafe bool TryGet(Type parameterType, out INetworkParameter parameter) - { - parameter = default; - - if (!m_Parameters.IsCreated) - return false; - - var typeHash = BurstRuntime.GetHashCode64(parameterType); - - if (m_ParameterOffsets.TryGetValue(typeHash, out var parameterSlice)) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (UnsafeUtility.SizeOf(parameterType) != parameterSlice.Size) - throw new ArgumentException($"The size of the parameter type {parameterType} ({UnsafeUtility.SizeOf(parameterType)}) is different to the stored size ({parameterSlice.Size})"); - - if (m_Parameters.Length < parameterSlice.Offset + parameterSlice.Size) - throw new OverflowException($"The registered parameter slice is out of bounds of the parameters buffer"); -#endif - parameter = Activator.CreateInstance(parameterType) as INetworkParameter; - var parameterPtr = (byte*)UnsafeUtility.PinGCObjectAndGetAddress(parameter, out var gcHandle) + ObjectHeaderOffset; - UnsafeUtility.MemCpy(parameterPtr, (byte*)m_Parameters.GetUnsafeReadOnlyPtr() + parameterSlice.Offset, parameterSlice.Size); - UnsafeUtility.ReleaseGCObject(gcHandle); - return true; - } - - return false; - } - - internal static int ObjectHeaderOffset => UnsafeUtility.SizeOf(); - - private unsafe struct ObjectOffsetType - { - void* v0; - #if !ENABLE_CORECLR - void* v1; - #endif - } -#endif // !UNITY_DOTSRUNTIME - - #endregion } } diff --git a/Runtime/NetworkSettings.cs.meta b/Runtime/NetworkSettings.cs.meta index 56eb60e..152cbc1 100644 --- a/Runtime/NetworkSettings.cs.meta +++ b/Runtime/NetworkSettings.cs.meta @@ -8,4 +8,4 @@ MonoImporter: icon: {instanceID: 0} userData: assetBundleName: - assetBundleVariant: \ No newline at end of file + assetBundleVariant: diff --git a/Runtime/NetworkStack.cs b/Runtime/NetworkStack.cs new file mode 100644 index 0000000..1203fae --- /dev/null +++ b/Runtime/NetworkStack.cs @@ -0,0 +1,368 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Networking.Transport.Relay; +#if ENABLE_MANAGED_UNITYTLS +using Unity.Networking.Transport.TLS; +#endif +using BurstRuntime = Unity.Burst.BurstRuntime; + +namespace Unity.Networking.Transport +{ + internal unsafe struct NetworkStack : IDisposable + { + private struct NetworkInterfaceFunctions + { + private ManagedCallWrapper m_NetworkInterface_Bind_FPtr; + private ManagedCallWrapper m_NetworkInterface_Listen_FPtr; + private ManagedCallWrapper m_NetworkInterface_GetLocalEndpoint_FPtr; + + internal static NetworkInterfaceFunctions Create() where N : unmanaged, INetworkInterface + { + var functions = new NetworkInterfaceFunctions(); + functions.m_NetworkInterface_Bind_FPtr = new ManagedCallWrapper(&BindWrapper); + functions.m_NetworkInterface_Listen_FPtr = new ManagedCallWrapper(&ListenWrapper); + functions.m_NetworkInterface_GetLocalEndpoint_FPtr = new ManagedCallWrapper(&GetLocalEndpointWrapper); + return functions; + } + + internal int Bind(ref NetworkStack stack, ref NetworkEndpoint endpoint) + { + var arguments = new BindArguments + { + Stack = stack, + Endpoint = endpoint, + }; + m_NetworkInterface_Bind_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + internal int Listen(ref NetworkStack stack) + { + var arguments = new ListenArguments + { + Stack = stack, + }; + m_NetworkInterface_Listen_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + internal NetworkEndpoint GetLocalEndpoint(ref NetworkStack stack) + { + var arguments = new GetLocalEndpointArguments + { + Stack = stack, + }; + m_NetworkInterface_GetLocalEndpoint_FPtr.Invoke(ref arguments); + return arguments.Return; + } + + private struct BindArguments + { + public NetworkStack Stack; + public NetworkEndpoint Endpoint; + public int Return; + } + static private void BindWrapper(void* argumentsPtr, int size) where N : unmanaged, INetworkInterface + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + + if (arguments.Stack.TryGetLayer>(out var layer)) + { + arguments.Return = layer.Bind(ref arguments.Endpoint); + return; + } + + arguments.Return = -1; + } + + private struct GetLocalEndpointArguments + { + public NetworkStack Stack; + public NetworkEndpoint Return; + } + static private void GetLocalEndpointWrapper(void* argumentsPtr, int size) where N : unmanaged, INetworkInterface + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + + if (arguments.Stack.TryGetLayer>(out var layer)) + { + arguments.Return = layer.GetLocalEndpoint(); + return; + } + + arguments.Return = default; + } + + private struct ListenArguments + { + public NetworkStack Stack; + public int Return; + } + static private void ListenWrapper(void* argumentsPtr, int size) where N : unmanaged, INetworkInterface + { + ref var arguments = ref ManagedCallWrapper.ArgumentsFromPtr(argumentsPtr, size); + + if (arguments.Stack.TryGetLayer>(out var layer)) + { + arguments.Return = layer.Listen(); + return; + } + } + } + + // TODO: disabling the safety check here for now, but we need to remove it asap. + // As NetworkStack is in NetworkDriver and the driver is passed to multiple + // jobs being schedulled at the same time we schedule stack update, we + // are not supposed to write here. We know that for now we don't read/write + // this in any other place, so it's fine, but moving pipelines to a layer should + // allow us to fix it. + [NativeDisableContainerSafetyRestriction] + private NativeList m_Layers; + [NativeDisableContainerSafetyRestriction] + private NativeList m_AccumulatedPacketPadding; + + private int m_TotalPacketPadding; + private ConnectionList m_Connections; + + private NetworkInterfaceFunctions m_NetworkInterfaceFunctions; + + + // TODO: for now we add an extra byte for pipeline id, should move to its own layer + internal int PacketPadding => m_TotalPacketPadding + 1; + + internal ConnectionList Connections => m_Connections; + + internal static void Initialize(out NetworkStack stack) + { + stack = default; + stack.m_Layers = new NativeList(0, Allocator.Persistent); + stack.m_AccumulatedPacketPadding = new NativeList(0, Allocator.Persistent); + } + + internal static NetworkStack CreateForSettings + (ref N networkInterface, ref NetworkSettings networkSettings, + out PacketsQueue sendQueue, out PacketsQueue receiveQueue) where N : unmanaged, INetworkInterface + { + Initialize(out var stack); + + stack.m_NetworkInterfaceFunctions = NetworkInterfaceFunctions.Create(); + + stack.AddLayer(new BottomLayer(), ref networkSettings); + stack.AddLayer(new NetworkInterfaceLayer(networkInterface), ref networkSettings); + + // We get again the interface as ref so the CreateQueues method is applied to the actual layer interface copy + ref var interfaceRef = ref stack.m_Layers.ElementAt(stack.m_Layers.Length - 1).CastRef>().m_NetworkInterface; + CreateQueues(ref interfaceRef, ref networkSettings, out sendQueue, out receiveQueue); + // assign it so the new values are copied back + networkInterface = interfaceRef; + + // 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) + { + 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); + + // 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. + if (networkSettings.TryGet(out _)) + stack.AddLayer(new SimulatorLayer(), ref networkSettings); + + var isRelay = networkSettings.TryGet(out _); + + // Determine if we need to add a DTLS layer or not. These layers can only be added on + // non-WebGL platforms that support UnityTLS. Hence the complicated #if condition. +#if ENABLE_MANAGED_UNITYTLS && (!UNITY_WEBGL || UNITY_EDITOR) + var isSecure = isRelay + ? networkSettings.GetRelayParameters().ServerData.IsSecure == 1 + : networkSettings.TryGet(out _); + + if (isSecure && networkInterface is UDPNetworkInterface) + stack.AddLayer(new DTLSLayer(), ref networkSettings); +#endif + + if (isRelay) + stack.AddLayer(new RelayLayer(), ref networkSettings); + + stack.AddLayer(new SimpleConnectionLayer(), ref networkSettings); + stack.AddLayer(new TopLayer(), ref networkSettings); + + return stack; + } + + public void Dispose() + { + var layersCount = m_Layers.Length; + for (int i = 0; i < layersCount; i++) + { + m_Layers.ElementAt(i).Dispose(); + } + + m_Layers.Dispose(); + m_AccumulatedPacketPadding.Dispose(); + } + + internal void AddLayer(T layer, ref NetworkSettings settings) where T : unmanaged, INetworkLayer + => AddLayer(ref layer, ref settings); + + internal void AddLayer(ref T layer, ref NetworkSettings settings) where T : unmanaged, INetworkLayer + { + var oldConnections = m_Connections; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var oldPadding = m_TotalPacketPadding; + var result = layer.Initialize(ref settings, ref m_Connections, ref m_TotalPacketPadding); + if (m_TotalPacketPadding < oldPadding) + { + throw new InvalidOperationException($"Layer {typeof(T).ToString()} has decreased total padding. Negative packet paddings are invalid."); + } +#else + var result = layer.Initialize(ref settings, ref m_Connections, ref m_TotalPacketPadding); +#endif + + if (result != 0) + UnityEngine.Debug.LogError($"Failed to initialize the NetworkStack. Layer {typeof(T).ToString()} with error Code: {result}."); + + m_Layers.Add(NetworkLayerWrapper.Create(ref layer)); + m_AccumulatedPacketPadding.Add(m_TotalPacketPadding); + + // If a new connection list has been created we notify the BottomLayer so it will do the + // proper cleanup on every udpate. + if (m_Connections.IsCreated && oldConnections != m_Connections) + m_Layers.ElementAt(0).CastRef().AddConnectionList(ref m_Connections); + } + + internal bool TryGetLayer(out T layer) where T : unmanaged, INetworkLayer + { + foreach (var layerWrapper in m_Layers) + { + if (layerWrapper.IsType()) + { + layer = layerWrapper.CastRef(); + return true; + } + } + layer = default; + return false; + } + + internal static unsafe void CreateQueues(ref N networkInterface, ref NetworkSettings settings, out PacketsQueue sendQueue, out PacketsQueue receiveQueue) + where N : unmanaged, INetworkInterface + { + var networkConfig = settings.GetNetworkConfigParameters(); + var sendQueueCapacity = networkConfig.sendQueueCapacity; + var receiveQueueCapacity = networkConfig.receiveQueueCapacity; + +#if !UNITY_WEBGL || UNITY_EDITOR + if (BurstRuntime.GetHashCode64() == BurstRuntime.GetHashCode64()) + { + fixed(void* interfacePtr = &networkInterface) + { + ref var udpInterface = ref *(UDPNetworkInterface*)interfacePtr; + udpInterface.CreateQueues(sendQueueCapacity, receiveQueueCapacity, out sendQueue, out receiveQueue); + } + } + else +#endif + { + receiveQueue = new PacketsQueue(receiveQueueCapacity); + sendQueue = new PacketsQueue(sendQueueCapacity); + } + + if (sendQueue.Capacity != networkConfig.sendQueueCapacity) + { + sendQueue.Dispose(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException(string.Format( + "The provided buffers count ({0}) must be equal to the sendQueueCapacity ({1})", + sendQueue.Capacity, + networkConfig.sendQueueCapacity)); +#else + UnityEngine.Debug.LogError(string.Format( + "The provided buffers count ({0}) must be equal to the sendQueueCapacity ({1})", + sendQueue.Capacity, + networkConfig.sendQueueCapacity)); +#endif + } + + if (receiveQueue.Capacity != networkConfig.receiveQueueCapacity) + { + receiveQueue.Dispose(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException(string.Format( + "The provided buffers count ({0}) must be equal to the receiveQueueCapacity ({1})", + receiveQueue.Capacity, + networkConfig.receiveQueueCapacity)); +#else + UnityEngine.Debug.LogError(string.Format( + "The provided buffers count ({0}) must be equal to the receiveQueueCapacity ({1})", + receiveQueue.Capacity, + networkConfig.receiveQueueCapacity)); +#endif + } + } + + internal int Bind(ref NetworkEndpoint endpoint) => m_NetworkInterfaceFunctions.Bind(ref this, ref endpoint); + + internal int Listen() => m_NetworkInterfaceFunctions.Listen(ref this); + + internal NetworkEndpoint GetLocalEndpoint() => m_NetworkInterfaceFunctions.GetLocalEndpoint(ref this); + + internal JobHandle ScheduleReceive(ref NetworkDriverReceiver driverReceiver, ref ConnectionList connectionList, + ref NetworkEventQueue eventQueue, ref NetworkPipelineProcessor pipelineProcessor, long time, JobHandle dependency) + { + var jobArguments = new ReceiveJobArguments + { + ReceiveQueue = driverReceiver.ReceiveQueue, + DriverReceiver = driverReceiver, + ReceiveResult = driverReceiver.Result, + EventQueue = eventQueue, + PipelineProcessor = pipelineProcessor, + Time = time, + }; + + var length = m_Layers.Length; + for (var i = 0; i < length; ++i) + dependency = m_Layers.ElementAt(i).ScheduleReceive(ref jobArguments, dependency); + + return dependency; + } + + internal JobHandle ScheduleSend(ref NetworkDriverSender driverSender, long time, JobHandle dependency) + { + var jobArguments = new SendJobArguments + { + SendQueue = driverSender.SendQueue, + Time = time, + }; + + dependency = driverSender.FlushPackets(dependency); + + for (var i = m_Layers.Length - 1; i >= 0; --i) + { + jobArguments.SendQueue.SetDefaultDataOffset(m_AccumulatedPacketPadding[i]); + dependency = m_Layers.ElementAt(i).ScheduleSend(ref jobArguments, dependency); + } + + return dependency; + } + } +} diff --git a/Runtime/NetworkStack.cs.meta b/Runtime/NetworkStack.cs.meta new file mode 100644 index 0000000..7384542 --- /dev/null +++ b/Runtime/NetworkStack.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5bb5126930985a42a34e64822f9cc14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/OperationResult.cs b/Runtime/OperationResult.cs new file mode 100644 index 0000000..1b759a3 --- /dev/null +++ b/Runtime/OperationResult.cs @@ -0,0 +1,42 @@ +using System; +using Unity.Collections; + +namespace Unity.Networking.Transport +{ + /// + /// Stores the result of a NetworkDriver operation. + /// + public struct OperationResult : IDisposable + { + private FixedString64Bytes m_Label; + private NativeReference m_ErrorCode; + + internal OperationResult(FixedString64Bytes label, Allocator allocator) + { + m_Label = label; + m_ErrorCode = new NativeReference(allocator); + } + + /// + /// Allows to get and set the error code for the operation. + /// + /// Setting an error code different to zero will log it. + public int ErrorCode + { + get => m_ErrorCode.Value; + set + { + if (value != 0) + { + UnityEngine.Debug.LogError(FixedString.Format("Error on {0}, errorCode = {1}", m_Label, value)); + } + m_ErrorCode.Value = value; + } + } + + public void Dispose() + { + m_ErrorCode.Dispose(); + } + } +} diff --git a/Runtime/OperationResult.cs.meta b/Runtime/OperationResult.cs.meta new file mode 100644 index 0000000..136aed1 --- /dev/null +++ b/Runtime/OperationResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e48fb8773edb4f1ba97400eefc393fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PacketBuffer.cs b/Runtime/PacketBuffer.cs new file mode 100644 index 0000000..8c325f1 --- /dev/null +++ b/Runtime/PacketBuffer.cs @@ -0,0 +1,11 @@ +using System; + +namespace Unity.Networking.Transport +{ + internal struct PacketBuffer + { + public IntPtr Metadata; + public IntPtr Payload; + public IntPtr Endpoint; + } +} diff --git a/Runtime/PacketBuffer.cs.meta b/Runtime/PacketBuffer.cs.meta new file mode 100644 index 0000000..7edb9eb --- /dev/null +++ b/Runtime/PacketBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 153b0c83a3ad4f94aafa7c9882564e9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PacketMetadata.cs b/Runtime/PacketMetadata.cs new file mode 100644 index 0000000..64ba889 --- /dev/null +++ b/Runtime/PacketMetadata.cs @@ -0,0 +1,46 @@ +namespace Unity.Networking.Transport +{ + internal struct PacketMetadata + { + public int DataLength; + public int DataOffset; + public int DataCapacity; + public ConnectionId Connection; + + public override bool Equals(object obj) + { + return this == (PacketMetadata)obj; + } + + public override int GetHashCode() + { + var hash = 1; + hash = 31 * hash + DataLength; + hash = 31 * hash + DataOffset; + hash = 31 * hash + DataCapacity; + hash = 31 * hash + Connection.GetHashCode(); + return hash; + } + + public override string ToString() + { + return string.Format("PacketMetadata(offset: {0}, length: {1}, capacity: {2})", DataOffset, DataLength, DataCapacity); + } + + public static bool operator==(PacketMetadata lhs, PacketMetadata rhs) + { + return lhs.DataLength == rhs.DataLength && + lhs.DataOffset == rhs.DataOffset && + lhs.DataCapacity == rhs.DataCapacity && + lhs.Connection == rhs.Connection; + } + + public static bool operator!=(PacketMetadata lhs, PacketMetadata rhs) + { + return lhs.DataLength != rhs.DataLength || + lhs.DataOffset != rhs.DataOffset || + lhs.DataCapacity != rhs.DataCapacity || + lhs.Connection != rhs.Connection; + } + } +} diff --git a/Runtime/PacketMetadata.cs.meta b/Runtime/PacketMetadata.cs.meta new file mode 100644 index 0000000..cb36662 --- /dev/null +++ b/Runtime/PacketMetadata.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64c30722d382b485199363e58710a260 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PacketProcessor.cs b/Runtime/PacketProcessor.cs new file mode 100644 index 0000000..a9b54b8 --- /dev/null +++ b/Runtime/PacketProcessor.cs @@ -0,0 +1,257 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Networking.Transport +{ + /// + /// Provides an API for processing packets. + /// + public unsafe struct PacketProcessor + { + internal PacketsQueue m_Queue; + + internal int m_BufferIndex; + + private ref PacketMetadata PacketMetadataRef => ref m_Queue.GetMetadataRef(m_BufferIndex); + private PacketBuffer PacketBuffer => m_Queue.GetPacketBuffer(m_BufferIndex); + + /// + /// Whether the packet processor is valid. + /// + public bool IsCreated => m_Queue.IsCreated; + /// + /// The size of the current data of the packet. + /// + public int Length => PacketMetadataRef.DataLength; + /// + /// The current offset in the packet buffer of the data. + /// + public int Offset => PacketMetadataRef.DataOffset; + /// + /// The total capacity of the packet. + /// + public int Capacity => PacketMetadataRef.DataCapacity; + /// + /// The available amount of bytes at the end of the packet data. + /// + public int BytesAvailableAtEnd => Capacity - (Offset + Length); + /// + /// The available amount of bytes at the begining of the packet data. + /// + public int BytesAvailableAtStart => Offset; + /// + /// A reference to the Endpoint of the packet. + /// + public ref NetworkEndpoint EndpointRef => ref *((NetworkEndpoint*)PacketBuffer.Endpoint); + /// + /// A reference to the connection ID of the packet. + /// + internal ref ConnectionId ConnectionRef => ref PacketMetadataRef.Connection; + + /// + /// Gets a reference to the payload data reinterpreted to the type T. + /// + /// The type of the data. + /// The offset from the start of the payload. + /// Returns a reference to the Payload data + /// Throws an execption if there is no enough bytes in the payload for the specified type. + public ref T GetPayloadDataRef(int offset = 0) where T : unmanaged + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (UnsafeUtility.SizeOf() + offset > Length) + throw new ArgumentException($"The requested type {typeof(T).ToString()} does not fit in the payload data ({Length - offset})"); +#endif + return ref *(T*)(((byte*)GetUnsafePayloadPtr()) + Offset + offset); + } + + /// + /// Copies the provided bytes at the end of the packet and increases its size accordingly. + /// + /// The pointer to the data to copy. + /// The size in bytes to copy. + /// Throws an exception if there are not enough bytes available at the end of the packet. + public void AppendToPayload(void* dataPtr, int size) + { + if (size > BytesAvailableAtEnd) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The requested data size ({size}) does not fit at the end of the payload ({BytesAvailableAtEnd} Bytes available)"); +#else + UnityEngine.Debug.LogError($"The requested data size ({size}) does not fit at the end of the payload ({BytesAvailableAtEnd} Bytes available)"); + return; +#endif + } + UnsafeUtility.MemCpy(((byte*)PacketBuffer.Payload) + Offset + Length, dataPtr, size); + PacketMetadataRef.DataLength += size; + } + + /// + /// + /// The packet processor to copy the data from. + /// Throws an exception if there are not enough bytes available at the end of the packet. + public void AppendToPayload(PacketProcessor processor) + { + AppendToPayload((byte*)processor.GetUnsafePayloadPtr() + processor.Offset, processor.Length); + } + + /// + /// Copies the provided value at the end of the packet and increases its size accordingly. + /// + /// The type of the data to copy. + /// The value to copy. + /// Throws an exception if there are not enough bytes available at the end of the packet. + public void AppendToPayload(T value) where T : unmanaged + { + var size = UnsafeUtility.SizeOf(); + if (size > BytesAvailableAtEnd) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The requested data size ({size}) does not fit at the end of the payload ({BytesAvailableAtEnd} Bytes available)"); +#else + UnityEngine.Debug.LogError($"The requested data size ({size}) does not fit at the end of the payload ({BytesAvailableAtEnd} Bytes available)"); + return; +#endif + } + UnsafeUtility.MemCpy(((byte*)PacketBuffer.Payload + Offset + Length), &value, size); + PacketMetadataRef.DataLength += size; + } + + /// + /// Copies the provided value at the start of the packet and increases its size accordingly. + /// + /// The type of the data to copy. + /// The value to copy. + /// Throws an exception if there are not enough bytes available at the end of the packet. + public void PrependToPayload(T value) where T : unmanaged + { + var size = UnsafeUtility.SizeOf(); + if (size > BytesAvailableAtStart) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The requested data size ({size}) does not fit at the start of the payload ({BytesAvailableAtStart} Bytes available)"); +#else + UnityEngine.Debug.LogError($"The requested data size ({size}) does not fit at the start of the payload ({BytesAvailableAtStart} Bytes available)"); + return; +#endif + } + UnsafeUtility.MemCpy((byte*)PacketBuffer.Payload + Offset - size, &value, size); + PacketMetadataRef.DataOffset -= size; + PacketMetadataRef.DataLength += size; + } + + /// + /// Gets and removes the data at the start of the payload reinterpreted to the type T. + /// + /// The type of the data. + /// The extracted data value + /// Throws an execption if there is no enough bytes in the payload for the specified type. + public T RemoveFromPayloadStart() where T : unmanaged + { + var size = UnsafeUtility.SizeOf(); + if (size > Length) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The size of the required type ({size}) does not fit in the payload ({Length})."); +#else + UnityEngine.Debug.LogError($"The size of the required type ({size}) does not fit in the payload ({Length})."); + return default(T); +#endif + } + var value = GetPayloadDataRef(0); + PacketMetadataRef.DataOffset += size; + PacketMetadataRef.DataLength -= size; + return value; + } + + /// + /// Fill the buffer with the data at the start of the payload. + /// + /// Pointer to the start of the buffer to fill. + /// Size of the buffer to fill. + /// If provided buffer is larger than payload. + public void RemoveFromPayloadStart(void* ptr, int size) + { + if (size > Length) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The size of the buffer ({size}) is larger than the payload ({Length})."); +#else + UnityEngine.Debug.LogError($"The size of the buffer ({size}) is larger than the payload ({Length})."); + return; +#endif + } + UnsafeUtility.MemCpy(ptr, ((byte*)PacketBuffer.Payload) + Offset, size); + PacketMetadataRef.DataOffset += size; + PacketMetadataRef.DataLength -= size; + } + + /// + /// Copies the current packet data to the destination pointer. + /// + /// The destination pointer where the data will be copied. + /// Returns the ammount of bytes copied. + /// Throws an exception if the packet has overflowed the buffer. + public int CopyPayload(void* destinationPtr, int size) + { + if (Length <= 0) + return 0; + + var copiedBytes = Length; + + if (size < Length) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new ArgumentException($"The payload size ({Length}) does not fit in the provided pointer ({size})"); +#else + UnityEngine.Debug.LogError($"The payload size ({Length}) does not fit in the provided pointer ({size})"); + copiedBytes = size; +#endif + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (Offset < 0) + throw new OverflowException("Packet DataOffset must be >= 0"); + if (Offset + Length > Capacity) + throw new OverflowException("Packet data overflows packet capacity"); +#endif + + UnsafeUtility.MemCpy(destinationPtr, ((byte*)PacketBuffer.Payload) + Offset, copiedBytes); + + return copiedBytes; + } + + /// + /// Gets the unsafe pointer of the packet data. + /// + /// A pointer to the packet data. + public void* GetUnsafePayloadPtr() + { + return (byte*)PacketBuffer.Payload; + } + + /// + /// Manually sets the packet metadata. + /// + /// The new size of the packet + /// The new offset of the packet + /// Throws an ArgumentException if the size and offset does not fit in the packet. + internal void SetUnsafeMetadata(int size, int offset = 0) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (offset + size > Capacity || offset < 0 || size < 0) + throw new ArgumentException($"The requested data size ({size}) and offset ({offset}) does not fit in the payload ({Capacity} Bytes available)"); +#endif + PacketMetadataRef.DataLength = size; + PacketMetadataRef.DataOffset = offset; + } + + /// + /// Drops the packet. + /// + public void Drop() + { + SetUnsafeMetadata(0); + } + } +} diff --git a/Runtime/PacketProcessor.cs.meta b/Runtime/PacketProcessor.cs.meta new file mode 100644 index 0000000..5bbc49b --- /dev/null +++ b/Runtime/PacketProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a53c9f2089ac043f288db02ac527ae2e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/PacketsQueue.cs b/Runtime/PacketsQueue.cs new file mode 100644 index 0000000..61c075d --- /dev/null +++ b/Runtime/PacketsQueue.cs @@ -0,0 +1,424 @@ +using System; +using System.Diagnostics; +using System.Threading; +using UnityEngine.Assertions; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.Networking.Transport.Utilities.LowLevel.Unsafe; + +namespace Unity.Networking.Transport +{ + /// + /// A queue of packets with an internal pool of preallocated packet buffers + /// + public unsafe struct PacketsQueue : IDisposable + { + private NativeList m_Queue; + private UnsafeAtomicFreeList m_FreeList; + [NativeDisableParallelForRestriction] private NativeArray m_Buffers; + [NativeDisableUnsafePtrRestriction] private IntPtr m_PayloadsPtr; + [NativeDisableUnsafePtrRestriction] private IntPtr m_EndpointsPtr; + private int m_Capacity; + private int m_MetadataSize; + private int m_PayloadSize; + private int m_EndpointSize; + private int m_DefaultDataOffset; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [NativeDisableUnsafePtrRestriction] private IntPtr m_BuffersUsage; +#endif + + internal int BuffersInUse => m_FreeList.InUse; + internal int BuffersAvailable => m_Capacity - m_FreeList.InUse; + internal int PayloadCapacity => m_PayloadSize; + internal int EndpointCapacity => m_EndpointSize; + public int Capacity => m_Capacity; + public int Count => m_Queue.Length; + + public bool IsCreated => m_Buffers.IsCreated; + + internal ref PacketMetadata GetMetadataRef(int bufferIndex) => ref *(PacketMetadata*)(m_Buffers[bufferIndex].Metadata); + internal ref NetworkEndpoint GetEndpointRef(int bufferIndex) => ref *(NetworkEndpoint*)(m_Buffers[bufferIndex].Endpoint); + + internal PacketBuffer GetPacketBuffer(int bufferIndex) => m_Buffers[bufferIndex]; + + private static void Initialize(out PacketsQueue packetsQueue, int metadataSize, int payloadSize, int endpointSize, int capacity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (metadataSize != UnsafeUtility.SizeOf()) + { + throw new ArgumentException($"Metadata size ({metadataSize}) must be {UnsafeUtility.SizeOf()}"); + } +#endif + packetsQueue.m_Capacity = capacity; + packetsQueue.m_MetadataSize = metadataSize; + packetsQueue.m_PayloadSize = payloadSize; + packetsQueue.m_EndpointSize = endpointSize; + packetsQueue.m_DefaultDataOffset = 0; + packetsQueue.m_PayloadsPtr = IntPtr.Zero; + packetsQueue.m_EndpointsPtr = IntPtr.Zero; + packetsQueue.m_FreeList = new UnsafeAtomicFreeList(capacity, Allocator.Persistent); + packetsQueue.m_Buffers = new NativeArray(capacity, Allocator.Persistent); + packetsQueue.m_Queue = new NativeList(capacity, Allocator.Persistent); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var buffersUsageSize = (int)math.ceil((float)packetsQueue.m_Capacity / UnsafeUtility.SizeOf()); + packetsQueue.m_BuffersUsage = new IntPtr(UnsafeUtility.Malloc(buffersUsageSize, UnsafeUtility.AlignOf(), Allocator.Persistent)); + UnsafeUtility.MemClear((void*)packetsQueue.m_BuffersUsage, buffersUsageSize); +#endif + } + + /// + /// Created a pool and allocated the buffers + /// + /// The ammount of packets available + internal PacketsQueue(int capacity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (capacity <= 0) + throw new ArgumentException($"{nameof(capacity)} must be > 0"); +#endif + var metadataSize = UnsafeUtility.SizeOf(); + var endpointSize = UnsafeUtility.SizeOf(); + var payloadSize = NetworkParameterConstants.MTU; + + // PacketMetadata is prepended to the payload + var payloadAndMetadataSize = payloadSize + metadataSize; + + Initialize(out this, + metadataSize, + payloadSize, + endpointSize, + capacity); + + payloadAndMetadataSize = AddPaddingToAlign(payloadAndMetadataSize, JobsUtility.CacheLineSize); + endpointSize = AddPaddingToAlign(endpointSize, JobsUtility.CacheLineSize); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + Assert.IsTrue(payloadAndMetadataSize >= m_MetadataSize + m_PayloadSize); + Assert.IsTrue(endpointSize >= m_EndpointSize); +#endif + + m_PayloadsPtr = new IntPtr(UnsafeUtility.Malloc(capacity * payloadAndMetadataSize, JobsUtility.CacheLineSize, Allocator.Persistent)); + m_EndpointsPtr = new IntPtr(UnsafeUtility.Malloc(capacity * endpointSize, JobsUtility.CacheLineSize, Allocator.Persistent)); + + new FillBufferPointers + { + Buffers = m_Buffers, + Payloads = m_PayloadsPtr, + Endpoints = m_EndpointsPtr, + PayloadAndMetadataSize = payloadAndMetadataSize, + EndpointSize = endpointSize, + }.Run(); + } + + /// + /// Creates a new pool using preallocated memory + /// + /// The size in bytes of a Metadata buffer + /// The size in bytes of a Payload buffer + /// The size in bytes of a Endpoint buffer + /// The ammount of packets available + /// An array containing the preallocated PacketBuffers to use + internal PacketsQueue(int metadataSize, int payloadSize, int endpointSize, int capacity, NativeArray buffers) + { + Initialize(out this, + metadataSize, + payloadSize, + endpointSize, + capacity); + + buffers.CopyTo(m_Buffers); + } + + private static int AddPaddingToAlign(int size, int alignment) + { + return alignment * (int)math.ceil((float)size / (float)alignment); + } + + [BurstCompile] + private struct FillBufferPointers : IJob + { + public NativeArray Buffers; + [NativeDisableUnsafePtrRestriction] public IntPtr Payloads; + [NativeDisableUnsafePtrRestriction] public IntPtr Endpoints; + public int PayloadAndMetadataSize; + public int EndpointSize; + + public void Execute() + { + var payloadsPtr = (byte*)Payloads; + var endpointsPtr = (byte*)Endpoints; + var metadataSize = UnsafeUtility.SizeOf(); + + for (int i = 0; i < Buffers.Length; i++) + { + Buffers[i] = new PacketBuffer + { + Metadata = new IntPtr(payloadsPtr + i * PayloadAndMetadataSize), + Payload = new IntPtr(payloadsPtr + i * PayloadAndMetadataSize + metadataSize), + Endpoint = new IntPtr(endpointsPtr + i * EndpointSize), + }; + } + } + } + + public void Dispose() + { + if (IsCreated) + { + if (m_PayloadsPtr != IntPtr.Zero) + { + UnsafeUtility.Free((void*)m_PayloadsPtr, Allocator.Persistent); + m_PayloadsPtr = IntPtr.Zero; + } + + if (m_EndpointsPtr != IntPtr.Zero) + { + UnsafeUtility.Free((void*)m_EndpointsPtr, Allocator.Persistent); + m_EndpointsPtr = IntPtr.Zero; + } + + m_FreeList.Dispose(); + m_Buffers.Dispose(); + m_Queue.Dispose(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnsafeUtility.Free((void*)m_BuffersUsage, Allocator.Persistent); + m_BuffersUsage = IntPtr.Zero; +#endif + } + } + + /// + /// Gets the packet processor for the packetIndex. + /// + /// The index of the packet in the current send queue. + /// The packet processor for the packet in the provided index. + /// Throws an exception if the index provided is not valid. + public PacketProcessor this[int packetIndex] + { + get + { + var bufferIndex = m_Queue[packetIndex]; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (bufferIndex < 0) + throw new IndexOutOfRangeException(string.Format("Cannot access the queued packet with index {0} because it has been released", packetIndex)); +#endif + + return GetPacketProcessor(bufferIndex); + } + } + + internal PacketProcessor GetPacketProcessor(int bufferIndex) + { + return new PacketProcessor + { + m_Queue = this, + m_BufferIndex = bufferIndex, + }; + } + + /// + /// Acquires a new packet buffer from the packets pool if there are available and + /// returns its PacketProcessor. + /// + /// The PacketProcessor of the new packet. + /// Returns true if the packet was created. Returns false if there are no buffers available. + public unsafe bool EnqueuePacket(out PacketProcessor packetProcessor) + { + if (TryAcquireBuffer(out var bufferIndex)) + { + GetMetadataRef(bufferIndex) = new PacketMetadata { DataOffset = m_DefaultDataOffset }; + EnqueueAndGetProcessor(bufferIndex, out packetProcessor); + return true; + } + + packetProcessor = default; + return false; + } + + // Baselib acquires all the packets at initialization, meaning that it needs to enqueue + // a packet skiping the acquiring step. The bufferIndex is already known. + internal bool EnqueuePacket(int bufferIndex, out PacketProcessor packetProcessor) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (!IsUsed(bufferIndex)) + throw new InvalidOperationException($"Trying to enqueue a packet ({bufferIndex}) but it has not been acquired"); +#endif + + EnqueueAndGetProcessor(bufferIndex, out packetProcessor); + return true; + } + + private void EnqueueAndGetProcessor(int bufferIndex, out PacketProcessor packetProcessor) + { + m_Queue.Add(bufferIndex); + packetProcessor = GetPacketProcessor(bufferIndex); + GetMetadataRef(bufferIndex).DataCapacity = PayloadCapacity; + } + + /// + /// Coppies all the packets from the origin queue. + /// + /// The queue that contains the packets to enqueue. + public void EnqueuePackets(ref PacketsQueue originQueue) + { + var count = originQueue.Count; + + for (int i = 0; i < count; i++) + { + var packet = originQueue[i]; + + if (packet.Length == 0) + continue; + + if (EnqueuePacket(out var packetProcessor)) + { + packet.CopyPayload((byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset, packet.Length); + packetProcessor.SetUnsafeMetadata(packet.Length, packetProcessor.Offset); + packetProcessor.ConnectionRef = packet.ConnectionRef; + packetProcessor.EndpointRef = packet.EndpointRef; + } + else + { + // There are no more available packet buffers so we can't continue copying. + return; + } + } + } + + internal int DequeuePacketNoRelease(int packetIndex) + { + var bufferIndex = m_Queue[packetIndex]; + m_Queue[packetIndex] = -1; + return bufferIndex; + } + + private unsafe void DropPacket(int packetIndex) + { + var bufferIndex = DequeuePacketNoRelease(packetIndex); + + if (bufferIndex < 0) + return; + + GetMetadataRef(bufferIndex) = default; + ReleaseBuffer(bufferIndex); + } + + /// + /// Removes and releases all the packets in the queue. + /// + public void Clear() + { + for (int i = 0; i < m_Queue.Length; i++) + { + DropPacket(i); + } + + m_Queue.Clear(); + } + + internal void UnsafeResetAcquisitionState() + { + m_FreeList.Reset(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + for (int i = 0; i < Capacity; i++) + { + if (IsUsed(i)) + MarkAsNotUsed(i); + } +#endif + } + + internal bool TryAcquireBuffer(out int bufferIndex) + { + bufferIndex = m_FreeList.Pop(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (bufferIndex >= 0) + { + CheckIndex(bufferIndex); + MarkAsUsed(bufferIndex); + } +#endif + return bufferIndex >= 0; + } + + internal void ReleaseBuffer(int bufferIndex) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckIndex(bufferIndex); + MarkAsNotUsed(bufferIndex); +#endif + m_FreeList.Push(bufferIndex); + } + + internal void SetDefaultDataOffset(int offset) + { + m_DefaultDataOffset = offset; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void CheckIndex(int bufferIndex) + { + if (bufferIndex < 0 || bufferIndex >= m_Capacity) + throw new IndexOutOfRangeException($"The buffer index {bufferIndex} is out of range [0, {m_Capacity - 1}]"); + } + + private static readonly int k_ULongSizeInBits = UnsafeUtility.SizeOf() * 8; + + internal bool IsUsed(int bufferIndex) + { + var intIndex = bufferIndex / k_ULongSizeInBits; + var bitIndex = bufferIndex % k_ULongSizeInBits; + + var usage = (ulong)Interlocked.Read(ref ((long*)m_BuffersUsage)[intIndex]); + + return (usage & (1UL << bitIndex)) > 0; + } + + private void MarkAsUsed(int bufferIndex) + { + var intIndex = bufferIndex / k_ULongSizeInBits; + var bitIndex = bufferIndex % k_ULongSizeInBits; + do + { + var usage = ((ulong*)m_BuffersUsage)[intIndex]; + if ((usage & (1UL << bitIndex)) > 0) + { + // This should never happen. If we hit this exception it might indicate + // a failure in the UnsafeAtomicFreeList implementation or bad memory + throw new InvalidOperationException($"Trying to acquire buffer with index {bufferIndex} but it was already marked as used"); + } + var newUsage = usage | (1UL << bitIndex); + if (Interlocked.CompareExchange(ref ((long*)m_BuffersUsage)[intIndex], (long)newUsage, (long)usage) == (long)usage) + break; + } + while (true); + } + + private void MarkAsNotUsed(int bufferIndex) + { + var intIndex = bufferIndex / k_ULongSizeInBits; + var bitIndex = bufferIndex % k_ULongSizeInBits; + do + { + var usage = ((ulong*)m_BuffersUsage)[intIndex]; + if ((usage & (1UL << bitIndex)) == 0) + { + throw new InvalidOperationException($"Trying to release a buffer with index {bufferIndex} but it was already marked as not used"); + } + var newUsage = usage & ~(1UL << bitIndex); + if (Interlocked.CompareExchange(ref ((long*)m_BuffersUsage)[intIndex], (long)newUsage, (long)usage) == (long)usage) + break; + } + while (true); + } + +#endif + } +} diff --git a/Runtime/PacketsQueue.cs.meta b/Runtime/PacketsQueue.cs.meta new file mode 100644 index 0000000..64d3a5c --- /dev/null +++ b/Runtime/PacketsQueue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21f2ece8bdfe66843a2aef4e6452d200 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Pipelines/FragmentationPipelineStage.cs b/Runtime/Pipelines/FragmentationPipelineStage.cs index 45cca3e..b22cb02 100644 --- a/Runtime/Pipelines/FragmentationPipelineStage.cs +++ b/Runtime/Pipelines/FragmentationPipelineStage.cs @@ -8,33 +8,19 @@ namespace Unity.Networking.Transport { - /// - /// The fragmentation pipeline stage allows for packets to be broken up into smaller packets. - /// - /// - /// The current implementation of this pipeline stage does not handle reassembly of out-of-order - /// fragments. Thus if it is expected that multiple fragmented messages will be in flight at the - /// same time, and/or if sending on networks with a lot of jitter, it is recommended to pair - /// this pipeline stage with . - /// [BurstCompile] public unsafe struct FragmentationPipelineStage : INetworkPipelineStage { - /// The fragmentation stage's internal context. - public struct FragContext + private struct FragContext { - /// Starting index. public int startIndex; - /// Ending index. public int endIndex; - /// Sequence number. public int sequence; - /// Whether there's an error with a packet. public bool packetError; } [Flags] - enum FragFlags + private enum FragFlags { First = 1 << 15, Last = 1 << 14, @@ -46,7 +32,6 @@ enum FragFlags #else const int FragHeaderCapacity = 2; // 2 bits for First/Last flags, 14 bits sequence number #endif - [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) @@ -58,7 +43,7 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in FragFlags flags = FragFlags.First; int headerCapacity = ctx.header.Capacity; - var systemHeaderCapacity = systemHeaderSize + 1 + SessionIdToken.k_Length; // Extra 1 byte is for pipeline id, SessionIdToken.k_Length bytes for footer + var systemHeaderCapacity = systemHeaderSize + 1; // Extra 1 byte is for pipeline id var maxBlockLength = NetworkParameterConstants.MTU - systemHeaderCapacity - inboundBuffer.headerPadding; var maxBlockLengthFirstPacket = maxBlockLength - ctx.accumulatedHeaderCapacity; // The first packet has the headers for all pipeline stages before this one @@ -219,14 +204,7 @@ private static void InitializeConnection(byte* staticInstanceBuffer, int staticI static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - /// - /// Statics the initialize using the specified static instance buffer - /// - /// The static instance buffer - /// The static instance buffer length - /// The NetworkSettings - /// Please specify a FragmentationUtility.Parameters with a PayloadCapacity greater than MTU, which is {NetworkParameterConstants.MTU} - /// The network pipeline stage + public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { FragmentationUtility.Parameters param = settings.GetFragmentationStageParameters(); @@ -245,9 +223,6 @@ public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int sta ); } - /// - /// Gets the value of the static size - /// public int StaticSize => UnsafeUtility.SizeOf(); } } diff --git a/Runtime/Pipelines/FragmentationUtility.cs b/Runtime/Pipelines/FragmentationUtility.cs index d19cf53..93c48f1 100644 --- a/Runtime/Pipelines/FragmentationUtility.cs +++ b/Runtime/Pipelines/FragmentationUtility.cs @@ -1,11 +1,9 @@ +using Unity.Collections; + namespace Unity.Networking.Transport.Utilities { public static class FragmentationStageParameterExtensions { - /// - /// Sets the values for the - /// - /// public static ref NetworkSettings WithFragmentationStageParameters( ref this NetworkSettings settings, int payloadCapacity = FragmentationUtility.Parameters.k_DefaultPayloadCapacity @@ -21,10 +19,6 @@ public static ref NetworkSettings WithFragmentationStageParameters( return ref settings; } - /// - /// Gets the - /// - /// Returns the values for the public static FragmentationUtility.Parameters GetFragmentationStageParameters(ref this NetworkSettings settings) { if (!settings.TryGet(out var parameters)) @@ -38,12 +32,10 @@ public static FragmentationUtility.Parameters GetFragmentationStageParameters(re public struct FragmentationUtility { - /// Configuration parameters for . public struct Parameters : INetworkParameter { internal const int k_DefaultPayloadCapacity = 4 * 1024; - /// Maximum payload size that can be fragmented. public int PayloadCapacity; public bool Validate() diff --git a/Runtime/Pipelines/NullPipelineStage.cs b/Runtime/Pipelines/NullPipelineStage.cs index 7e9417f..9d77c2e 100644 --- a/Runtime/Pipelines/NullPipelineStage.cs +++ b/Runtime/Pipelines/NullPipelineStage.cs @@ -3,9 +3,6 @@ namespace Unity.Networking.Transport { - /// - /// The NullPipelineStage is the default pipeline stage and used to send packets unreliably - /// [BurstCompile] public unsafe struct NullPipelineStage : INetworkPipelineStage { @@ -31,9 +28,7 @@ private static void InitializeConnection(byte* staticInstanceBuffer, int staticI } static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings netParams) diff --git a/Runtime/Pipelines/ReliableSequencedPipelineStage.cs b/Runtime/Pipelines/ReliableSequencedPipelineStage.cs index f6820b4..88b4d0b 100644 --- a/Runtime/Pipelines/ReliableSequencedPipelineStage.cs +++ b/Runtime/Pipelines/ReliableSequencedPipelineStage.cs @@ -1,3 +1,4 @@ +using System; using AOT; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -6,21 +7,16 @@ namespace Unity.Networking.Transport { - /// - /// The ReliableSequencedPipelineStage is used to send packets reliably and retain the order in which they are sent. - /// This PipelineStage has a hardcoded WindowSize of 32 inflight packets and will drop packets if its unable to - /// track them. - /// [BurstCompile] public unsafe struct ReliableSequencedPipelineStage : INetworkPipelineStage { static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); + public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { ReliableUtility.Parameters param = settings.GetReliableStageParameters(); - UnsafeUtility.MemCpy(staticInstanceBuffer, ¶m, UnsafeUtility.SizeOf()); return new NetworkPipelineStage( Receive: ReceiveFunctionPointer, @@ -61,8 +57,7 @@ private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffe NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref inboundArray, safetyHandle); #endif var reader = new DataStreamReader(inboundArray); - reader.ReadBytes((byte*)&header, UnsafeUtility.SizeOf()); - + reader.ReadBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf()); if (header.Type == (ushort)ReliableUtility.PacketType.Ack) { ReliableUtility.ReadAckPacket(ctx, header); @@ -127,7 +122,7 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in requests |= NetworkPipelineStage.Requests.Error; return (int)Error.StatusCode.NetworkSendQueueFull; } - ctx.header.WriteBytes((byte*)&header, UnsafeUtility.SizeOf()); + ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf()); if (reliable->Resume != ReliableUtility.NullEntry) requests |= NetworkPipelineStage.Requests.Resume; @@ -142,7 +137,8 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in if (needsResume) requests |= NetworkPipelineStage.Requests.Resume; ctx.header.Clear(); - ctx.header.WriteBytes((byte*)&header, UnsafeUtility.SizeOf()); + + ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf()); reliable->PreviousTimestamp = ctx.timestamp; return (int)Error.StatusCode.Success; } @@ -152,13 +148,14 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in reliable->LastSentTime = ctx.timestamp; ReliableUtility.WriteAckPacket(ctx, ref header); - ctx.header.WriteBytes((byte*)&header, UnsafeUtility.SizeOf()); + + ctx.header.WriteBytesUnsafe((byte*)&header, UnsafeUtility.SizeOf()); reliable->PreviousTimestamp = ctx.timestamp; // TODO: Sending dummy byte over since the pipeline won't send an empty payload (ignored on receive) inboundBuffer.bufferWithHeadersLength = inboundBuffer.headerPadding + 1; inboundBuffer.bufferWithHeaders = (byte*)UnsafeUtility.Malloc(inboundBuffer.bufferWithHeadersLength, 8, Allocator.Temp); - inboundBuffer.SetBufferFrombufferWithHeaders(); + inboundBuffer.SetBufferFromBufferWithHeaders(); return (int)Error.StatusCode.Success; } reliable->PreviousTimestamp = ctx.timestamp; @@ -173,11 +170,26 @@ private static void InitializeConnection(byte* staticInstanceBuffer, int staticI { ReliableUtility.Parameters param; UnsafeUtility.MemCpy(¶m, staticInstanceBuffer, UnsafeUtility.SizeOf()); - if (sharedProcessBufferLength >= ReliableUtility.SharedCapacityNeeded(param) && - (sendProcessBufferLength + recvProcessBufferLength) >= ReliableUtility.ProcessCapacityNeeded(param) * 2) + + if (sharedProcessBufferLength != ReliableUtility.SharedCapacityNeeded(param)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("sharedProcessBufferLength is wrong length for ReliableUtility.Parameters!"); +#else + return; +#endif + } + + if (sendProcessBufferLength + recvProcessBufferLength < ReliableUtility.ProcessCapacityNeeded(param) * 2) { - ReliableUtility.InitializeContext(sharedProcessBuffer, sharedProcessBufferLength, sendProcessBuffer, sendProcessBufferLength, recvProcessBuffer, recvProcessBufferLength, param); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("sendProcessBufferLength + recvProcessBufferLength is wrong length for ReliableUtility.ProcessCapacityNeeded!"); +#else + return; +#endif } + + ReliableUtility.InitializeContext(sharedProcessBuffer, sharedProcessBufferLength, sendProcessBuffer, sendProcessBufferLength, recvProcessBuffer, recvProcessBufferLength, param); } } } diff --git a/Runtime/Pipelines/ReliableUtility.cs b/Runtime/Pipelines/ReliableUtility.cs index 9bd95f9..85211c9 100644 --- a/Runtime/Pipelines/ReliableUtility.cs +++ b/Runtime/Pipelines/ReliableUtility.cs @@ -16,13 +16,11 @@ public struct SequenceBufferContext public static class ReliableStageParameterExtensions { - /// - /// Sets the values for the - /// - /// + private const int k_DefaultWindowSize = 32; + public static ref NetworkSettings WithReliableStageParameters( ref this NetworkSettings settings, - int windowSize = ReliableUtility.ParameterConstants.WindowSize + int windowSize = k_DefaultWindowSize ) { var parameter = new ReliableUtility.Parameters @@ -35,15 +33,11 @@ public static ref NetworkSettings WithReliableStageParameters( return ref settings; } - /// - /// Gets the - /// - /// Returns the values for the public static ReliableUtility.Parameters GetReliableStageParameters(ref this NetworkSettings settings) { if (!settings.TryGet(out var parameters)) { - parameters.WindowSize = ReliableUtility.ParameterConstants.WindowSize; + parameters.WindowSize = k_DefaultWindowSize; } return parameters; @@ -54,10 +48,19 @@ public struct ReliableUtility { public struct Statistics { + /// + /// The Total. Thus, includes out of order, stale, and duplicate packets. + /// public int PacketsReceived; public int PacketsSent; public int PacketsDropped; public int PacketsOutOfOrder; + /// + /// Note that the reliability pipeline does not (and cannot) make the distinction between a packet 'actually duplicated by the network', + /// and a packet 'resent by the remote connection as a form of reliability'. + /// To do so would require increasing the to include unique packet ID's. + /// Thus, be aware that this field records both. + /// public int PacketsDuplicated; public int PacketsStale; public int PacketsResent; @@ -71,7 +74,7 @@ public struct RTTInfo public int ResendTimeout; } - public const int NullEntry = -1; + internal const int NullEntry = -1; // The least amount of time we'll wait until a packet resend is performed // This is 4x16ms (assumes a 60hz update rate) public const int DefaultMinimumResendTime = 64; @@ -86,7 +89,7 @@ public enum ErrorCodes InsufficientMemory = -8 } - public enum PacketType : ushort + internal enum PacketType : ushort { Payload = 0, Ack = 1 @@ -150,13 +153,8 @@ public bool Validate() } } - public struct ParameterConstants - { - public const int WindowSize = 32; - } - [StructLayout(LayoutKind.Sequential)] - public struct PacketHeader + internal struct PacketHeader { public ushort Type; public ushort ProcessingTime; @@ -176,7 +174,7 @@ public struct PacketInformation // Header is inside the total packet length (Buffer size) [StructLayout(LayoutKind.Explicit)] - public unsafe struct Packet + internal unsafe struct Packet { internal const int Length = NetworkParameterConstants.MTU; [FieldOffset(0)] public PacketHeader Header; @@ -198,7 +196,7 @@ private static int AlignedSizeOf() where T : struct return (UnsafeUtility.SizeOf() + NetworkPipelineProcessor.AlignmentMinusOne) & (~NetworkPipelineProcessor.AlignmentMinusOne); } - public static int SharedCapacityNeeded(Parameters param) + internal static int SharedCapacityNeeded(Parameters param) { // Timers are stored for both remote packets (processing time) and local packets (round trip time) // The amount of timestamps needed in the queues is the same as the window size capacity @@ -208,7 +206,7 @@ public static int SharedCapacityNeeded(Parameters param) return capacityNeeded; } - public static int ProcessCapacityNeeded(Parameters param) + internal static int ProcessCapacityNeeded(Parameters param) { var infoSize = AlignedSizeOf(); var dataSize = (Packet.Length + UnsafeUtility.SizeOf() + NetworkPipelineProcessor.AlignmentMinusOne) & (~NetworkPipelineProcessor.AlignmentMinusOne); @@ -220,7 +218,7 @@ public static int ProcessCapacityNeeded(Parameters param) return capacityNeeded; } - public static unsafe SharedContext InitializeContext(byte* sharedBuffer, int sharedBufferLength, + internal static unsafe SharedContext InitializeContext(byte* sharedBuffer, int sharedBufferLength, byte* sendBuffer, int sendBufferLength, byte* recvBuffer, int recvBufferLength, Parameters param) { InitializeProcessContext(sendBuffer, sendBufferLength, param); @@ -230,9 +228,9 @@ public static unsafe SharedContext InitializeContext(byte* sharedBuffer, int sha *notifier = new SharedContext { WindowSize = param.WindowSize, - SentPackets = new SequenceBufferContext { Acked = NullEntry }, + SentPackets = new SequenceBufferContext { Acked = NullEntry, AckMask = ~0u, LastAckMask = ~0u }, MinimumResendTime = DefaultMinimumResendTime, - ReceivedPackets = new SequenceBufferContext { Sequence = NullEntry }, + ReceivedPackets = new SequenceBufferContext { Sequence = NullEntry, AckMask = ~0u, LastAckMask = ~0u}, RttInfo = new RTTInfo { SmoothedVariance = 5, SmoothedRtt = 50, ResendTimeout = 50, LastRtt = 50}, TimerDataOffset = AlignedSizeOf(), TimerDataStride = AlignedSizeOf(), @@ -242,7 +240,7 @@ public static unsafe SharedContext InitializeContext(byte* sharedBuffer, int sha return *notifier; } - public static unsafe int InitializeProcessContext(byte* buffer, int bufferLength, Parameters param) + internal static unsafe int InitializeProcessContext(byte* buffer, int bufferLength, Parameters param) { int totalCapacity = ProcessCapacityNeeded(param); if (bufferLength != totalCapacity) @@ -263,21 +261,23 @@ public static unsafe int InitializeProcessContext(byte* buffer, int bufferLength return 0; } - public static unsafe void SetPacket(byte* self, int sequence, InboundRecvBuffer data) + internal static unsafe void SetPacket(byte* self, int sequence, InboundRecvBuffer data) { SetPacket(self, sequence, data.buffer, data.bufferLength); } - public static unsafe void SetPacket(byte* self, int sequence, void* data, int length) + internal static unsafe void SetPacket(byte* self, int sequence, void* data, int length) { Context* ctx = (Context*)self; if (length > ctx->DataStride) + { #if ENABLE_UNITY_COLLECTIONS_CHECKS throw new OverflowException(); #else return; #endif + } var index = sequence % ctx->Capacity; @@ -303,17 +303,20 @@ public static unsafe void SetPacket(byte* self, int sequence, void* data, int le /// The packet header which we'll store with the packet payload. /// The packet data which we're storing. /// - public static unsafe void SetHeaderAndPacket(byte* self, int sequence, PacketHeader header, InboundSendBuffer data, long timestamp) + internal static unsafe void SetHeaderAndPacket(byte* self, int sequence, PacketHeader header, InboundSendBuffer data, long timestamp) { Context* ctx = (Context*)self; int totalSize = data.bufferLength + data.headerPadding; if (totalSize + UnsafeUtility.SizeOf() > ctx->DataStride) + { #if ENABLE_UNITY_COLLECTIONS_CHECKS throw new OverflowException(); #else return; #endif + } + var index = sequence % ctx->Capacity; PacketInformation* info = GetPacketInformation(self, sequence); @@ -339,7 +342,7 @@ public static unsafe void SetHeaderAndPacket(byte* self, int sequence, PacketHea return (PacketInformation*)((self + ctx->IndexPtrOffset) + (index * ctx->IndexStride)); } - public static unsafe Packet* GetPacket(byte* self, int sequence) + internal static unsafe Packet* GetPacket(byte* self, int sequence) { Context* ctx = (Context*)self; var index = sequence % ctx->Capacity; @@ -348,7 +351,7 @@ public static unsafe void SetHeaderAndPacket(byte* self, int sequence, PacketHea return (Packet*)(self + offset); } - public static unsafe bool TryAquire(byte* self, int sequence) + internal static unsafe bool TryAquire(byte* self, int sequence) { Context* ctx = (Context*)self; @@ -363,12 +366,12 @@ public static unsafe bool TryAquire(byte* self, int sequence) return false; } - public static unsafe void Release(byte* self, int sequence) + internal static unsafe void Release(byte* self, int sequence) { Release(self, sequence, 1); } - public static unsafe void Release(byte* self, int start_sequence, int count) + internal static unsafe void Release(byte* self, int start_sequence, int count) { Context* ctx = (Context*)self; for (int i = 0; i < count; i++) @@ -377,7 +380,7 @@ public static unsafe void Release(byte* self, int start_sequence, int count) } } - static unsafe void SetIndex(byte* self, int index, int sequence) + private static unsafe void SetIndex(byte* self, int index, int sequence) { Context* ctx = (Context*)self; @@ -385,7 +388,7 @@ static unsafe void SetIndex(byte* self, int index, int sequence) *value = sequence; } - static unsafe int GetIndex(byte* self, int index) + private static unsafe int GetIndex(byte* self, int index) { Context* ctx = (Context*)self; @@ -403,7 +406,7 @@ static unsafe int GetIndex(byte* self, int index) /// /// Pipeline context, contains the buffer slices this pipeline connection owns. /// - public static unsafe bool ReleaseOrResumePackets(NetworkPipelineContext context) + internal static unsafe bool ReleaseOrResumePackets(NetworkPipelineContext context) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; Context* ctx = (Context*)context.internalProcessBuffer; @@ -458,7 +461,7 @@ public static unsafe bool ReleaseOrResumePackets(NetworkPipelineContext context) /// The first packet which we need to retrieve now, there could be more after that. /// Indicates if we need the pipeline to resume again. /// - public static unsafe InboundRecvBuffer ResumeReceive(NetworkPipelineContext context, int startSequence, ref bool needsResume) + internal static unsafe InboundRecvBuffer ResumeReceive(NetworkPipelineContext context, int startSequence, ref bool needsResume) { if (startSequence == NullEntry) return default; @@ -497,7 +500,7 @@ public static unsafe InboundRecvBuffer ResumeReceive(NetworkPipelineContext cont /// Indicates if a pipeline resume is needed again. /// Buffer slice to packet payload. /// - public static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context, out PacketHeader header, ref bool needsResume) + internal static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context, out PacketHeader header, ref bool needsResume) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; Context* ctx = (Context*)context.internalProcessBuffer; @@ -527,11 +530,11 @@ public static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context inbound.bufferWithHeaders = context.internalProcessBuffer + offset; inbound.bufferWithHeadersLength = information->Size; inbound.headerPadding = information->HeaderPadding; - inbound.SetBufferFrombufferWithHeaders(); + inbound.SetBufferFromBufferWithHeaders(); reliable->stats.PacketsResent++; needsResume = false; - ctx->Resume = -1; + 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++) @@ -555,7 +558,7 @@ public static unsafe InboundSendBuffer ResumeSend(NetworkPipelineContext context /// Buffer with packet data. /// Packet header which will be populated. /// Sequence ID assigned to this packet. - public static unsafe int Write(NetworkPipelineContext context, InboundSendBuffer inboundBuffer, ref PacketHeader header) + internal static unsafe int Write(NetworkPipelineContext context, InboundSendBuffer inboundBuffer, ref PacketHeader header) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; @@ -594,7 +597,7 @@ public static unsafe int Write(NetworkPipelineContext context, InboundSendBuffer /// Pipeline context, the reliability shared state is used here. /// Packet header which will be populated. /// - public static unsafe void WriteAckPacket(NetworkPipelineContext context, ref PacketHeader header) + internal static unsafe void WriteAckPacket(NetworkPipelineContext context, ref PacketHeader header) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; @@ -607,7 +610,7 @@ public static unsafe void WriteAckPacket(NetworkPipelineContext context, ref Pac reliable->ReceivedPackets.LastAckMask = header.AckMask; } - public static unsafe void StoreTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp) + internal static unsafe void StoreTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp) { var timerData = GetLocalPacketTimer(sharedBuffer, sequenceId); timerData->SequenceId = sequenceId; @@ -616,7 +619,7 @@ public static unsafe void StoreTimestamp(byte* sharedBuffer, ushort sequenceId, timerData->ReceiveTime = 0; } - public static unsafe void StoreReceiveTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp, ushort processingTime) + internal static unsafe void StoreReceiveTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp, ushort processingTime) { var sharedCtx = (SharedContext*)sharedBuffer; var rttInfo = sharedCtx->RttInfo; @@ -638,14 +641,14 @@ public static unsafe void StoreReceiveTimestamp(byte* sharedBuffer, ushort seque } } - public static unsafe void StoreRemoteReceiveTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp) + internal static unsafe void StoreRemoteReceiveTimestamp(byte* sharedBuffer, ushort sequenceId, long timestamp) { var timerData = GetRemotePacketTimer(sharedBuffer, sequenceId); timerData->SequenceId = sequenceId; timerData->ReceiveTime = timestamp; } - static unsafe int CurrentResendTime(byte* sharedBuffer) + private static unsafe int CurrentResendTime(byte* sharedBuffer) { var sharedCtx = (SharedContext*)sharedBuffer; if (sharedCtx->RttInfo.ResendTimeout > MaximumResendTime) @@ -653,7 +656,7 @@ static unsafe int CurrentResendTime(byte* sharedBuffer) return Math.Max(sharedCtx->RttInfo.ResendTimeout, sharedCtx->MinimumResendTime); } - public static unsafe ushort CalculateProcessingTime(byte* sharedBuffer, ushort sequenceId, long timestamp) + internal static unsafe ushort CalculateProcessingTime(byte* sharedBuffer, ushort sequenceId, long timestamp) { // Look up previously recorded receive timestamp, subtract that from current timestamp and return as processing time var timerData = GetRemotePacketTimer(sharedBuffer, sequenceId); @@ -686,7 +689,7 @@ public static unsafe ushort CalculateProcessingTime(byte* sharedBuffer, ushort s /// Pipeline context, the reliability shared state is used here. /// Packet header of a new received packet. /// Sequence ID of the received packet. - public static unsafe int Read(NetworkPipelineContext context, PacketHeader header) + internal static unsafe int Read(NetworkPipelineContext context, PacketHeader header) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; @@ -701,7 +704,7 @@ public static unsafe int Read(NetworkPipelineContext context, PacketHeader heade } var window = reliable->WindowSize - 1; - if (SequenceHelpers.GreaterThan16((ushort)(header.SequenceId + 1), (ushort)reliable->ReceivedPackets.Sequence)) + if (SequenceHelpers.GreaterThan16((ushort)(header.SequenceId), (ushort)reliable->ReceivedPackets.Sequence)) { int distance = SequenceHelpers.AbsDistance(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence); @@ -726,7 +729,7 @@ public static unsafe int Read(NetworkPipelineContext context, PacketHeader heade reliable->ReceivedPackets.Sequence = header.SequenceId; } - else if (SequenceHelpers.LessThan16(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence)) + else { int distance = SequenceHelpers.AbsDistance(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence); // If this is a resent packet the distance will seem very big and needs to be calculated again with adjustment for wrapping @@ -752,7 +755,7 @@ public static unsafe int Read(NetworkPipelineContext context, PacketHeader heade return header.SequenceId; } - public static unsafe void ReadAckPacket(NetworkPipelineContext context, PacketHeader header) + internal static unsafe void ReadAckPacket(NetworkPipelineContext context, PacketHeader header) { SharedContext* reliable = (SharedContext*)context.internalSharedProcessBuffer; @@ -779,7 +782,7 @@ public static unsafe void ReadAckPacket(NetworkPipelineContext context, PacketHe } } - public static unsafe bool ShouldSendAck(NetworkPipelineContext ctx) + internal static unsafe bool ShouldSendAck(NetworkPipelineContext ctx) { var reliable = (Context*)ctx.internalProcessBuffer; var shared = (SharedContext*)ctx.internalSharedProcessBuffer; @@ -797,8 +800,8 @@ public static unsafe bool ShouldSendAck(NetworkPipelineContext ctx) public static unsafe void SetMinimumResendTime(int value, NetworkDriver driver, NetworkPipeline pipeline, NetworkConnection con) { - driver.GetPipelineBuffers(pipeline, NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)), con, out var receiveBuffer, out var sendBuffer, out var sharedBuffer); - var sharedCtx = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); + driver.GetPipelineBuffers(pipeline, NetworkPipelineStageId.Get(), con, out var receiveBuffer, out var sendBuffer, out var sharedBuffer); + var sharedCtx = (SharedContext*)sharedBuffer.GetUnsafePtr(); sharedCtx->MinimumResendTime = value; } } diff --git a/Runtime/Pipelines/SimulatorPipelineStage.cs b/Runtime/Pipelines/SimulatorPipelineStage.cs index 904a328..376701f 100644 --- a/Runtime/Pipelines/SimulatorPipelineStage.cs +++ b/Runtime/Pipelines/SimulatorPipelineStage.cs @@ -1,3 +1,4 @@ +using System; using AOT; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -6,11 +7,6 @@ namespace Unity.Networking.Transport { - /// - /// The SimulatorPipelineStage could be added on either the client or server to simulate bad network conditions. - /// It's best to add it as the last stage in the pipeline, then it will either drop the packet or add a delay right - /// before it would go on the wire. - /// [BurstCompile] public unsafe struct SimulatorPipelineStage : INetworkPipelineStage { @@ -18,25 +14,19 @@ public unsafe struct SimulatorPipelineStage : INetworkPipelineStage static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - /// - /// Statics the initialize using the specified static instance buffer - /// - /// The static instance buffer - /// The static instance buffer length - /// The net params - /// The network pipeline stage public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { SimulatorUtility.Parameters param = settings.GetSimulatorStageParameters(); - - UnsafeUtility.MemCpy(staticInstanceBuffer, ¶m, UnsafeUtility.SizeOf()); + var simulatorParamsSizeOf = UnsafeUtility.SizeOf(); + if (simulatorParamsSizeOf != staticInstanceBufferLength) throw new InvalidOperationException($"simulatorParamsSizeOf {simulatorParamsSizeOf}"); + UnsafeUtility.MemCpy(staticInstanceBuffer, ¶m, simulatorParamsSizeOf); return new NetworkPipelineStage( Receive: ReceiveFunctionPointer, Send: SendFunctionPointer, InitializeConnection: InitializeConnectionFunctionPointer, ReceiveCapacity: param.MaxPacketCount * (param.MaxPacketSize + UnsafeUtility.SizeOf()), - SendCapacity: 0, + SendCapacity: param.MaxPacketCount * (param.MaxPacketSize + UnsafeUtility.SizeOf()), HeaderCapacity: 0, SharedStateCapacity: UnsafeUtility.SizeOf() ); @@ -44,38 +34,108 @@ public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int sta [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, + static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, byte* sharedProcessBuffer, int sharedProcessBufferLength) { SimulatorUtility.Parameters param = default; - UnsafeUtility.MemCpy(¶m, staticInstanceBuffer, UnsafeUtility.SizeOf()); - if (sharedProcessBufferLength >= UnsafeUtility.SizeOf()) + + if (staticInstanceBufferLength != UnsafeUtility.SizeOf()) { - SimulatorUtility.InitializeContext(param, sharedProcessBuffer); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("staticInstanceBufferLength is wrong length for SimulatorUtility.Parameters!"); +#else + return; +#endif + } + + if (sharedProcessBufferLength != UnsafeUtility.SizeOf()) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("sharedProcessBufferLength is wrong length for SimulatorUtility.Context!"); +#else + return; +#endif } + + SimulatorUtility.InitializeContext(param, sharedProcessBuffer); } [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) + static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) { + var context = (SimulatorUtility.Context*)ctx.internalSharedProcessBuffer; + var param = *(SimulatorUtility.Parameters*)ctx.staticInstanceBuffer; + + if (param.Mode == ApplyMode.ReceivedPacketsOnly || param.Mode == ApplyMode.Off) + return (int)Error.StatusCode.Success; + + var inboundPacketSize = inboundBuffer.headerPadding + inboundBuffer.bufferLength; + if (inboundPacketSize > param.MaxPacketSize) + { + UnityEngine.Debug.LogWarning($"Incoming packet too large for SimulatorPipeline internal storage buffer. Passing through. [buffer={(inboundBuffer.headerPadding + inboundBuffer.bufferLength)} MaxPacketSize={param.MaxPacketSize}]"); + return (int)Error.StatusCode.NetworkPacketOverflow; + } + + var timestamp = ctx.timestamp; + + if (inboundBuffer.bufferLength > 0) + { + context->PacketCount++; + + if (SimulatorUtility.ShouldDropPacket(context, param, timestamp)) + { + context->PacketDropCount++; + inboundBuffer = default; + return (int)Error.StatusCode.Success; + } + + if (param.FuzzFactor > 0) + { + SimulatorUtility.FuzzPacket(context, ref param, ref inboundBuffer); + } + + if (SimulatorUtility.ShouldDuplicatePacket(context, ref param)) + { + if (SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref inboundBuffer, ref requests, timestamp)) + { + context->PacketCount++; + context->PacketDuplicatedCount++; + } + } + + if (SimulatorUtility.TrySkipDelayingPacket(ref param, ref requests, context) || !SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref inboundBuffer, ref requests, timestamp)) + { + return (int)Error.StatusCode.Success; + } + } + + InboundSendBuffer returnPacket = default; + if (SimulatorUtility.GetDelayedPacket(ref ctx, ref returnPacket, ref requests, timestamp)) + { + inboundBuffer = returnPacket; + return (int)Error.StatusCode.Success; + } + + inboundBuffer = default; return (int)Error.StatusCode.Success; } [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) + static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) { var context = (SimulatorUtility.Context*)ctx.internalSharedProcessBuffer; var param = *(SimulatorUtility.Parameters*)ctx.staticInstanceBuffer; - var simulator = new SimulatorUtility(param.MaxPacketCount, param.MaxPacketSize, param.PacketDelayMs, param.PacketJitterMs); + + if (param.Mode == ApplyMode.SentPacketsOnly || param.Mode == ApplyMode.Off) + return; if (inboundBuffer.bufferLength > param.MaxPacketSize) { - //UnityEngine.Debug.LogWarning("Incoming packet too large for internal storage buffer. Passing through. [buffer=" + inboundBuffer.Length + " packet=" + param->MaxPacketSize + "]"); - // TODO: Add error code for this + UnityEngine.Debug.LogWarning(FixedString.Format("Incoming packet too large for internal storage buffer. Passing through. [buffer={0} packet={1}]", inboundBuffer.bufferLength, param.MaxPacketSize)); return; } @@ -86,7 +146,7 @@ private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffe { context->PacketCount++; - if (simulator.ShouldDropPacket(context, param, timestamp)) + if (SimulatorUtility.ShouldDropPacket(context, param, timestamp)) { context->PacketDropCount++; inboundBuffer = default; @@ -99,15 +159,25 @@ private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffe bufferVec.buffer = inboundBuffer.buffer; bufferVec.bufferLength = inboundBuffer.bufferLength; bufferVec.headerPadding = 0; - if (context->PacketDelayMs == 0 || - !simulator.DelayPacket(ref ctx, bufferVec, ref requests, timestamp)) + + + if (SimulatorUtility.ShouldDuplicatePacket(context, ref param)) + { + if (SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref bufferVec, ref requests, timestamp)) + { + context->PacketCount++; + context->PacketDuplicatedCount++; + } + } + + if (SimulatorUtility.TrySkipDelayingPacket(ref param, ref requests, context) || !SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref bufferVec, ref requests, timestamp)) { return; } } InboundSendBuffer returnPacket = default; - if (simulator.GetDelayedPacket(ref ctx, ref returnPacket, ref requests, timestamp)) + if (SimulatorUtility.GetDelayedPacket(ref ctx, ref returnPacket, ref requests, timestamp)) { inboundBuffer.buffer = returnPacket.bufferWithHeaders; inboundBuffer.bufferLength = returnPacket.bufferWithHeadersLength; @@ -120,22 +190,13 @@ private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffe public int StaticSize => UnsafeUtility.SizeOf(); } - /// - /// The simulator pipeline stage in send - /// [BurstCompile] + [Obsolete("SimulatorPipelineStage now supports handling both sending and receiving via ApplyMode.AllPackets. You can safely remove this stage from your pipelines. (RemovedAfter 2022-03-01)")] public unsafe struct SimulatorPipelineStageInSend : INetworkPipelineStage { static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - /// - /// Statics the initialize using the specified static instance buffer - /// - /// The static instance buffer - /// The static instance buffer length - /// The net params - /// The network pipeline stage public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { SimulatorUtility.Parameters param = settings.GetSimulatorStageParameters(); @@ -155,30 +216,57 @@ public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int sta [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, + static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, byte* sharedProcessBuffer, int sharedProcessBufferLength) { SimulatorUtility.Parameters param = default; - UnsafeUtility.MemCpy(¶m, staticInstanceBuffer, UnsafeUtility.SizeOf()); - if (sharedProcessBufferLength >= UnsafeUtility.SizeOf()) + + if (staticInstanceBufferLength != UnsafeUtility.SizeOf()) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("staticInstanceBufferLength is wrong length for SimulatorUtility.Parameters!"); +#else + return; +#endif + } + + if (sharedProcessBufferLength != UnsafeUtility.SizeOf()) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("sharedProcessBufferLength is wrong length for SimulatorUtility.Context!"); +#else + return; +#endif + } + + if (param.Mode == ApplyMode.AllPackets) { - SimulatorUtility.InitializeContext(param, sharedProcessBuffer); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException("SimulatorPipelineStageInSend applies to sent packets only, and is deprecated. Please use SimulatorPipleineStage with SimulatorUtility.Parameters.Mode = ApplyMode.AllPackets."); +#else + return; +#endif } + + SimulatorUtility.InitializeContext(param, sharedProcessBuffer); } + // This is a copy/paste duplication of SimulatorPipelineStage.Send as this class is now deprecated. [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) + static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) { var context = (SimulatorUtility.Context*)ctx.internalSharedProcessBuffer; var param = *(SimulatorUtility.Parameters*)ctx.staticInstanceBuffer; + if (param.Mode == ApplyMode.ReceivedPacketsOnly || param.Mode == ApplyMode.Off) + return (int)Error.StatusCode.Success; - var simulator = new SimulatorUtility(param.MaxPacketCount, param.MaxPacketSize, param.PacketDelayMs, param.PacketJitterMs); - if (inboundBuffer.headerPadding + inboundBuffer.bufferLength > param.MaxPacketSize) + var inboundPacketSize = inboundBuffer.headerPadding + inboundBuffer.bufferLength; + if (inboundPacketSize > param.MaxPacketSize) { - //UnityEngine.Debug.LogWarning("Incoming packet too large for internal storage buffer. Passing through. [buffer=" + (inboundBuffer.headerPadding+inboundBuffer.buffer.Length) + " packet=" + param.MaxPacketSize + "]"); + UnityEngine.Debug.LogWarning($"Incoming packet too large for SimulatorPipeline internal storage buffer. Passing through. [buffer={(inboundBuffer.headerPadding + inboundBuffer.bufferLength)} MaxPacketSize={param.MaxPacketSize}]"); return (int)Error.StatusCode.NetworkPacketOverflow; } @@ -188,27 +276,35 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in { context->PacketCount++; - if (simulator.ShouldDropPacket(context, param, timestamp)) + if (SimulatorUtility.ShouldDropPacket(context, param, timestamp)) { context->PacketDropCount++; inboundBuffer = default; return (int)Error.StatusCode.Success; } - if (context->FuzzFactor > 0) + if (param.FuzzFactor > 0) + { + SimulatorUtility.FuzzPacket(context, ref param, ref inboundBuffer); + } + + if (SimulatorUtility.ShouldDuplicatePacket(context, ref param)) { - simulator.FuzzPacket(context, ref inboundBuffer); + if (SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref inboundBuffer, ref requests, timestamp)) + { + context->PacketCount++; + context->PacketDuplicatedCount++; + } } - if (context->PacketDelayMs == 0 || - !simulator.DelayPacket(ref ctx, inboundBuffer, ref requests, timestamp)) + if (SimulatorUtility.TrySkipDelayingPacket(ref param, ref requests, context) || !SimulatorUtility.TryDelayPacket(ref ctx, ref param, ref inboundBuffer, ref requests, timestamp)) { return (int)Error.StatusCode.Success; } } InboundSendBuffer returnPacket = default; - if (simulator.GetDelayedPacket(ref ctx, ref returnPacket, ref requests, timestamp)) + if (SimulatorUtility.GetDelayedPacket(ref ctx, ref returnPacket, ref requests, timestamp)) { inboundBuffer = returnPacket; return (int)Error.StatusCode.Success; @@ -219,14 +315,11 @@ private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer in [BurstCompile(DisableDirectCall = true)] [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, + static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, int systemHeaderSize) { } - /// - /// Gets the value of the static size - /// public int StaticSize => UnsafeUtility.SizeOf(); } } diff --git a/Runtime/Pipelines/SimulatorUtility.cs b/Runtime/Pipelines/SimulatorUtility.cs index 0091cc6..47d28e1 100644 --- a/Runtime/Pipelines/SimulatorUtility.cs +++ b/Runtime/Pipelines/SimulatorUtility.cs @@ -1,31 +1,24 @@ +using System; using System.Runtime.InteropServices; +using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; using Random = Unity.Mathematics.Random; namespace Unity.Networking.Transport.Utilities { public static class SimulatorStageParameterExtensions { - /// - /// Sets the values for the - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// public static ref NetworkSettings WithSimulatorStageParameters( ref this NetworkSettings settings, int maxPacketCount, int maxPacketSize, + ApplyMode mode, int packetDelayMs = 0, int packetJitterMs = 0, int packetDropInterval = 0, int packetDropPercentage = 0, + int packetDuplicationPercentage = 0, int fuzzFactor = 0, int fuzzOffset = 0, uint randomSeed = 0 @@ -35,10 +28,12 @@ public static ref NetworkSettings WithSimulatorStageParameters( { MaxPacketCount = maxPacketCount, MaxPacketSize = maxPacketSize, + Mode = mode, PacketDelayMs = packetDelayMs, PacketJitterMs = packetJitterMs, PacketDropInterval = packetDropInterval, PacketDropPercentage = packetDropPercentage, + PacketDuplicationPercentage = packetDuplicationPercentage, FuzzFactor = fuzzFactor, FuzzOffset = fuzzOffset, RandomSeed = randomSeed, @@ -49,10 +44,6 @@ public static ref NetworkSettings WithSimulatorStageParameters( return ref settings; } - /// - /// Gets the - /// - /// Returns the values for the public static SimulatorUtility.Parameters GetSimulatorStageParameters(ref this NetworkSettings settings) { // TODO: Pipelines need to store always all possible pipeline parameters, even when they are not used. @@ -60,15 +51,59 @@ public static SimulatorUtility.Parameters GetSimulatorStageParameters(ref this N settings.TryGet(out var parameters); return parameters; } + + // TODO This ModifySimulatorStageParameters() extension method is NOT a pattern we want + // repeated throughout the code. At some point we'll want to deprecate it and replace + // it with a proper general mechanism to modify settings at runtime (see MTT-4161). + + /// Modify the parameters of the simulator pipeline stage. + /// + /// Some parameters (e.g. max packet count and size) are not modifiable. These need to be + /// passed unmodified to this function (can't just leave them at 0 for example). The current + /// parameters can be obtained using . + /// + /// New parameters for the simulator stage. + public static unsafe void ModifySimulatorStageParameters(this NetworkDriver driver, SimulatorUtility.Parameters newParams) + { + var stageId = NetworkPipelineStageId.Get(); + var currentParams = driver.GetWriteablePipelineParameter(default, stageId); + + if (currentParams->MaxPacketCount != newParams.MaxPacketCount) + { + UnityEngine.Debug.LogError("Simulator stage maximum packet count can't be modified."); + return; + } + + if (currentParams->MaxPacketSize != newParams.MaxPacketSize) + { + UnityEngine.Debug.LogError("Simulator stage maximum packet size can't be modified."); + return; + } + + *currentParams = newParams; + driver.m_NetworkSettings.AddRawParameterStruct(ref newParams); + } } - public struct SimulatorUtility + /// + /// Denotes whether or not the should apply to sent or received packets (or both). + /// Default is . + /// As is deprecated, please change this to . + /// Note: Not a flag enum as the default value should never be "off". + /// + public enum ApplyMode : byte { - private int m_PacketCount; - private int m_MaxPacketSize; - private int m_PacketDelayMs; - private int m_PacketJitterMs; + /// Default to ensure no breaking changes with deprecation. + ReceivedPacketsOnly, + SentPacketsOnly, + /// Apply simulation (delay, jitter, packet loss, duplication, fuzz etc) to both sent and received packets. Recommended mode. + AllPackets, + /// For runtime toggling. + Off, + } + public static class SimulatorUtility + { /// /// Configuration parameters for the simulator pipeline stage. /// @@ -81,11 +116,22 @@ public struct Parameters : INetworkParameter /// be later brought back. /// public int MaxPacketCount; + /// /// The maximum size of a packet which the simulator stores. If a packet exceeds this size it will /// bypass the simulator. /// 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. + /// + public uint RandomSeed; + + /// + public ApplyMode Mode; + /// /// Fixed delay to apply to all packets which pass through. /// @@ -101,12 +147,19 @@ public struct Parameters : INetworkParameter /// public int PacketDropInterval; /// - /// Use a drop percentage when deciding when to drop packet. For every packet - /// a random number generator is used to determine if the packet should be dropped or not. - /// A percentage of 5 means approximately every 20th packet will be dropped. + /// 0 - 100, denotes the percentage of packets that will be dropped (i.e. deleted unprocessed). + /// E.g. "5" means approximately every 20th packet will be dropped. + /// to change random seed values. /// public int PacketDropPercentage; /// + /// 0 - 100, denotes the percentage of packets that will be duplicated once. + /// E.g. "5" means approximately every 20th packet will be duplicated once. + /// to change random seed values. + /// Note: Skipped if the packet is dropped. + /// + public int PacketDuplicationPercentage; + /// /// Use the fuzz factor when you want to fuzz a packet. For every packet /// a random number generator is used to determine if the packet should have the internal bits flipped. /// A percentage of 5 means approximately every 20th packet will be fuzzed, and that each bit in the packet @@ -118,11 +171,6 @@ public struct Parameters : INetworkParameter /// flipping bits. This is useful if you want to only fuzz a part of the packet. /// public int FuzzOffset; - /// - /// 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. - /// - public uint RandomSeed; public bool Validate() => true; } @@ -130,20 +178,12 @@ public struct Parameters : INetworkParameter [StructLayout(LayoutKind.Sequential)] public struct Context { - public int MaxPacketCount; - public int MaxPacketSize; - public int PacketDelayMs; - public int PacketJitterMs; - public int PacketDrop; - public int FuzzOffset; - public int FuzzFactor; - - public uint RandomSeed; public Random Random; // Statistics public int PacketCount; public int PacketDropCount; + public int PacketDuplicatedCount; public int ReadyPackets; public int WaitingPackets; public long NextPacketTime; @@ -151,7 +191,7 @@ public struct Context } [StructLayout(LayoutKind.Sequential)] - public struct DelayedPacket + internal struct DelayedPacket { public int processBufferOffset; public ushort packetSize; @@ -159,45 +199,29 @@ public struct DelayedPacket public long delayUntil; } - public SimulatorUtility(int packetCount, int maxPacketSize, int packetDelayMs, int packetJitterMs) - { - m_PacketCount = packetCount; - m_MaxPacketSize = maxPacketSize; - m_PacketDelayMs = packetDelayMs; - m_PacketJitterMs = packetJitterMs; - } - - public static unsafe void InitializeContext(Parameters param, byte* sharedProcessBuffer) + internal static unsafe void InitializeContext(Parameters param, byte* sharedProcessBuffer) { // Store parameters in the shared buffer space Context* ctx = (Context*)sharedProcessBuffer; - ctx->MaxPacketCount = param.MaxPacketCount; - ctx->MaxPacketSize = param.MaxPacketSize; - ctx->PacketDelayMs = param.PacketDelayMs; - ctx->PacketJitterMs = param.PacketJitterMs; - ctx->PacketDrop = param.PacketDropInterval; - ctx->FuzzFactor = param.FuzzFactor; - ctx->FuzzOffset = param.FuzzOffset; - ctx->PacketCount = 0; ctx->PacketDropCount = 0; ctx->Random = new Random(); if (param.RandomSeed > 0) { ctx->Random.InitState(param.RandomSeed); - ctx->RandomSeed = param.RandomSeed; } else - ctx->Random.InitState(); + ctx->Random.InitState((uint)TimerHelpers.GetTicks()); } - public unsafe bool GetEmptyDataSlot(byte* processBufferPtr, ref int packetPayloadOffset, + private static unsafe bool GetEmptyDataSlot(NetworkPipelineContext ctx, byte* processBufferPtr, ref int packetPayloadOffset, ref int packetDataOffset) { + var param = *(Parameters*)ctx.staticInstanceBuffer; var dataSize = UnsafeUtility.SizeOf(); - var packetPayloadStartOffset = m_PacketCount * dataSize; + var packetPayloadStartOffset = param.MaxPacketCount * dataSize; bool foundSlot = false; - for (int i = 0; i < m_PacketCount; i++) + for (int i = 0; i < param.MaxPacketCount; i++) { packetDataOffset = dataSize * i; DelayedPacket* packetData = (DelayedPacket*)(processBufferPtr + packetDataOffset); @@ -206,7 +230,7 @@ public unsafe bool GetEmptyDataSlot(byte* processBufferPtr, ref int packetPayloa if (packetData->delayUntil == 0) { foundSlot = true; - packetPayloadOffset = packetPayloadStartOffset + m_MaxPacketSize * i; + packetPayloadOffset = packetPayloadStartOffset + param.MaxPacketSize * i; break; } } @@ -214,10 +238,11 @@ public unsafe bool GetEmptyDataSlot(byte* processBufferPtr, ref int packetPayloa return foundSlot; } - public unsafe bool GetDelayedPacket(ref NetworkPipelineContext ctx, ref InboundSendBuffer delayedPacket, + internal static unsafe bool GetDelayedPacket(ref NetworkPipelineContext ctx, ref InboundSendBuffer delayedPacket, ref NetworkPipelineStage.Requests requests, long currentTimestamp) { requests = NetworkPipelineStage.Requests.None; + var param = *(Parameters*)ctx.staticInstanceBuffer; var dataSize = UnsafeUtility.SizeOf(); byte* processBufferPtr = (byte*)ctx.internalProcessBuffer; @@ -226,7 +251,7 @@ public unsafe bool GetDelayedPacket(ref NetworkPipelineContext ctx, ref InboundS long oldestTime = long.MaxValue; int readyPackets = 0; int packetsInQueue = 0; - for (int i = 0; i < m_PacketCount; i++) + for (int i = 0; i < param.MaxPacketCount; i++) { DelayedPacket* packet = (DelayedPacket*)(processBufferPtr + dataSize * i); if ((int)packet->delayUntil == 0) continue; @@ -265,17 +290,17 @@ public unsafe bool GetDelayedPacket(ref NetworkPipelineContext ctx, ref InboundS delayedPacket.bufferWithHeaders = ctx.internalProcessBuffer + packet->processBufferOffset; delayedPacket.bufferWithHeadersLength = packet->packetSize; delayedPacket.headerPadding = packet->packetHeaderPadding; - delayedPacket.SetBufferFrombufferWithHeaders(); + delayedPacket.SetBufferFromBufferWithHeaders(); return true; } return false; } - public unsafe void FuzzPacket(Context *ctx, ref InboundSendBuffer inboundBuffer) + internal static unsafe void FuzzPacket(Context *ctx, ref Parameters param, ref InboundSendBuffer inboundBuffer) { - int fuzzFactor = ctx->FuzzFactor; - int fuzzOffset = ctx->FuzzOffset; + int fuzzFactor = param.FuzzFactor; + int fuzzOffset = param.FuzzOffset; int rand = ctx->Random.NextInt(0, 100); if (rand > fuzzFactor) return; @@ -293,28 +318,33 @@ public unsafe void FuzzPacket(Context *ctx, ref InboundSendBuffer inboundBuffer) } } - public unsafe bool DelayPacket(ref NetworkPipelineContext ctx, InboundSendBuffer inboundBuffer, + /// Storing it twice will trigger a resend. + internal static unsafe bool TryDelayPacket(ref NetworkPipelineContext ctx, ref Parameters param, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests requests, long timestamp) { + var simCtx = (Context*)ctx.internalSharedProcessBuffer; + // Find empty slot in bookkeeping data space to track this packet int packetPayloadOffset = 0; int packetDataOffset = 0; var processBufferPtr = (byte*)ctx.internalProcessBuffer; - bool foundSlot = GetEmptyDataSlot(processBufferPtr, ref packetPayloadOffset, ref packetDataOffset); + bool foundSlot = GetEmptyDataSlot(ctx, processBufferPtr, ref packetPayloadOffset, ref packetDataOffset); if (!foundSlot) { - //UnityEngine.Debug.LogWarning("No space left for delaying packet (" + m_PacketCount + " packets in queue)"); + 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; return false; } UnsafeUtility.MemCpy(ctx.internalProcessBuffer + packetPayloadOffset + inboundBuffer.headerPadding, inboundBuffer.buffer, inboundBuffer.bufferLength); - var param = (SimulatorUtility.Context*)ctx.internalSharedProcessBuffer; // Add tracking for this packet so we can resurrect later DelayedPacket packet; - packet.delayUntil = timestamp + m_PacketDelayMs + param->Random.NextInt(m_PacketJitterMs * 2) - m_PacketJitterMs; + var addedDelay = math.max(0, param.PacketDelayMs + simCtx->Random.NextInt(param.PacketJitterMs * 2) - param.PacketJitterMs); + packet.delayUntil = timestamp + addedDelay; packet.processBufferOffset = packetPayloadOffset; packet.packetSize = (ushort)(inboundBuffer.headerPadding + inboundBuffer.bufferLength); packet.packetHeaderPadding = (ushort)inboundBuffer.headerPadding; @@ -326,15 +356,43 @@ public unsafe bool DelayPacket(ref NetworkPipelineContext ctx, InboundSendBuffer return true; } - public unsafe bool ShouldDropPacket(Context* ctx, Parameters param, long timestamp) + /// + /// Optimization. + /// We want to skip in the case where we have no delay to avoid mem-copies. + /// Also ensures requests are updated if there are other packets in the store. + /// + /// True if we can skip delaying this packet. + internal static unsafe bool TrySkipDelayingPacket(ref Parameters param, ref NetworkPipelineStage.Requests requests, Context* simCtx) + { + if (param.PacketDelayMs == 0 && param.PacketJitterMs == 0) + { + if (simCtx->WaitingPackets > 0) + requests |= NetworkPipelineStage.Requests.Update; + return true; + } + return false; + } + + internal static unsafe bool ShouldDropPacket(Context* ctx, Parameters param, long timestamp) { if (param.PacketDropInterval > 0 && ((ctx->PacketCount - 1) % param.PacketDropInterval) == 0) return true; if (param.PacketDropPercentage > 0) { - //var packetLoss = new System.Random().NextDouble() * 100; - var packetLoss = ctx->Random.NextInt(0, 100); - if (packetLoss < param.PacketDropPercentage) + var chance = ctx->Random.NextInt(0, 100); + if (chance < param.PacketDropPercentage) + return true; + } + + return false; + } + + internal static unsafe bool ShouldDuplicatePacket(Context* ctx, ref Parameters param) + { + if (param.PacketDuplicationPercentage > 0) + { + var chance = ctx->Random.NextInt(0, 100); + if (chance < param.PacketDuplicationPercentage) return true; } diff --git a/Runtime/Pipelines/UnreliableSequencedPipelineStage.cs b/Runtime/Pipelines/UnreliableSequencedPipelineStage.cs index d71e722..4c7dbf7 100644 --- a/Runtime/Pipelines/UnreliableSequencedPipelineStage.cs +++ b/Runtime/Pipelines/UnreliableSequencedPipelineStage.cs @@ -6,22 +6,13 @@ namespace Unity.Networking.Transport { - /// - /// The UnreliableSequencedPipelineStage is used to send unreliable packets in order. - /// [BurstCompile] public unsafe struct UnreliableSequencedPipelineStage : INetworkPipelineStage { static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - /// - /// Statics the initialize using the specified static instance buffer - /// - /// The static instance buffer - /// The static instance buffer length - /// The net params - /// The network pipeline stage + public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) { return new NetworkPipelineStage( @@ -35,9 +26,6 @@ public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int sta ); } - /// - /// Gets the value of the static size - /// public int StaticSize => 0; [BurstCompile(DisableDirectCall = true)] diff --git a/Runtime/Relay/Messages/RelayMessageAccepted.cs b/Runtime/Relay/Messages/RelayMessageAccepted.cs index 8359d8e..24ef0c6 100644 --- a/Runtime/Relay/Messages/RelayMessageAccepted.cs +++ b/Runtime/Relay/Messages/RelayMessageAccepted.cs @@ -5,21 +5,11 @@ namespace Unity.Networking.Transport.Relay [StructLayout(LayoutKind.Sequential)] internal struct RelayMessageAccepted { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length * 2; // Header + FromAllocationId + ToAllocationId + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length * 2; // Header + FromAllocationId + ToAllocationId public RelayMessageHeader Header; public RelayAllocationId FromAllocationId; public RelayAllocationId ToAllocationId; - - internal static RelayMessageAccepted Create(RelayAllocationId fromAllocationId, RelayAllocationId toAllocationId) - { - return new RelayMessageAccepted - { - Header = RelayMessageHeader.Create(RelayMessageType.Accepted), - FromAllocationId = fromAllocationId, - ToAllocationId = toAllocationId - }; - } } } diff --git a/Runtime/Relay/Messages/RelayMessageBind.cs b/Runtime/Relay/Messages/RelayMessageBind.cs index b6b6466..85541f5 100644 --- a/Runtime/Relay/Messages/RelayMessageBind.cs +++ b/Runtime/Relay/Messages/RelayMessageBind.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace Unity.Networking.Transport.Relay @@ -7,7 +7,7 @@ internal static class RelayMessageBind { private const byte k_ConnectionDataLength = 255; private const byte k_HMACLength = 32; - public const int Length = RelayMessageHeader.Length + 1 + 2 + 1 + k_ConnectionDataLength + k_HMACLength; // Header + AcceptMode + Nonce + ConnectionDataLength + ConnectionData + HMAC; + public const int Length = RelayMessageHeader.k_Length + 1 + 2 + 1 + k_ConnectionDataLength + k_HMACLength; // Header + AcceptMode + Nonce + ConnectionDataLength + ConnectionData + HMAC; // public RelayMessageHeader Header; // public byte AcceptMode; @@ -16,16 +16,29 @@ internal static class RelayMessageBind // public fixed byte ConnectionData[k_ConnectionDataLength]; // public fixed byte HMAC[k_HMACLength]; - internal static unsafe void Write(DataStreamWriter writer, byte acceptMode, ushort nonce, byte* connectionDataPtr, byte* hmac) + public static unsafe void Write(DataStreamWriter writer, byte acceptMode, ushort nonce, byte* connectionDataPtr, byte* hmac) { var header = RelayMessageHeader.Create(RelayMessageType.Bind); - writer.WriteBytes((byte*)&header, RelayMessageHeader.Length); + + writer.WriteBytesUnsafe((byte*)&header, RelayMessageHeader.k_Length); writer.WriteByte(acceptMode); writer.WriteUShort(nonce); writer.WriteByte(k_ConnectionDataLength); - writer.WriteBytes(connectionDataPtr, k_ConnectionDataLength); - writer.WriteBytes(hmac, k_HMACLength); + writer.WriteBytesUnsafe(connectionDataPtr, k_ConnectionDataLength); + writer.WriteBytesUnsafe(hmac, k_HMACLength); + } + + public static unsafe void Write(ref PacketProcessor packetProcessor, ref RelayServerData serverData) + { + RelayMessageHeader.Write(ref packetProcessor, RelayMessageType.Bind); + packetProcessor.AppendToPayload(0); + packetProcessor.AppendToPayload(serverData.Nonce); + packetProcessor.AppendToPayload(k_ConnectionDataLength); + packetProcessor.AppendToPayload(serverData.ConnectionData); + + fixed(byte* hmacPtr = serverData.HMAC) + packetProcessor.AppendToPayload(hmacPtr, k_HMACLength); } } } diff --git a/Runtime/Relay/Messages/RelayMessageConnectRequest.cs b/Runtime/Relay/Messages/RelayMessageConnectRequest.cs index e8c14f5..a746df5 100644 --- a/Runtime/Relay/Messages/RelayMessageConnectRequest.cs +++ b/Runtime/Relay/Messages/RelayMessageConnectRequest.cs @@ -5,7 +5,7 @@ namespace Unity.Networking.Transport.Relay [StructLayout(LayoutKind.Sequential)] internal struct RelayMessageConnectRequest { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length + 1 + RelayConnectionData.k_Length; // Header + AllocationId + ToConnectionDataLength + ToConnectionData; + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length + 1 + RelayConnectionData.k_Length; // Header + AllocationId + ToConnectionDataLength + ToConnectionData; public RelayMessageHeader Header; @@ -13,7 +13,7 @@ internal struct RelayMessageConnectRequest public byte ToConnectionDataLength; public RelayConnectionData ToConnectionData; - internal static RelayMessageConnectRequest Create(RelayAllocationId allocationId, RelayConnectionData toConnectionData) + public static RelayMessageConnectRequest Create(RelayAllocationId allocationId, RelayConnectionData toConnectionData) { return new RelayMessageConnectRequest { @@ -23,5 +23,13 @@ internal static RelayMessageConnectRequest Create(RelayAllocationId allocationId ToConnectionData = toConnectionData, }; } + + public static void Write(ref PacketProcessor packetProcessor, ref RelayAllocationId allocationId, ref RelayConnectionData toConnectionData) + { + RelayMessageHeader.Write(ref packetProcessor, RelayMessageType.ConnectRequest); + packetProcessor.AppendToPayload(allocationId); + packetProcessor.AppendToPayload(255); + packetProcessor.AppendToPayload(toConnectionData); + } } } diff --git a/Runtime/Relay/Messages/RelayMessageDisconnect.cs b/Runtime/Relay/Messages/RelayMessageDisconnect.cs index 96847e1..877fafa 100644 --- a/Runtime/Relay/Messages/RelayMessageDisconnect.cs +++ b/Runtime/Relay/Messages/RelayMessageDisconnect.cs @@ -5,14 +5,14 @@ namespace Unity.Networking.Transport.Relay [StructLayout(LayoutKind.Sequential)] internal struct RelayMessageDisconnect { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length * 2; // Header + FromAllocationId + ToAllocationId + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length * 2; // Header + FromAllocationId + ToAllocationId public RelayMessageHeader Header; public RelayAllocationId FromAllocationId; public RelayAllocationId ToAllocationId; - internal static RelayMessageDisconnect Create(RelayAllocationId fromAllocationId, RelayAllocationId toAllocationId) + public static RelayMessageDisconnect Create(RelayAllocationId fromAllocationId, RelayAllocationId toAllocationId) { return new RelayMessageDisconnect { @@ -21,5 +21,12 @@ internal static RelayMessageDisconnect Create(RelayAllocationId fromAllocationId ToAllocationId = toAllocationId, }; } + + public static void Write(ref PacketProcessor packetProcessor, ref RelayAllocationId fromAllocationId, ref RelayAllocationId toAllocationId) + { + RelayMessageHeader.Write(ref packetProcessor, RelayMessageType.Disconnect); + packetProcessor.AppendToPayload(fromAllocationId); + packetProcessor.AppendToPayload(toAllocationId); + } } } diff --git a/Runtime/Relay/Messages/RelayMessageError.cs b/Runtime/Relay/Messages/RelayMessageError.cs index 8c69368..690b069 100644 --- a/Runtime/Relay/Messages/RelayMessageError.cs +++ b/Runtime/Relay/Messages/RelayMessageError.cs @@ -2,24 +2,52 @@ namespace Unity.Networking.Transport.Relay { - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct RelayMessageError { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length + sizeof(byte); // Header + AllocationId + ErrorCode + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length + sizeof(byte); // Header + AllocationId + ErrorCode public RelayMessageHeader Header; public RelayAllocationId AllocationId; public byte ErrorCode; - internal static RelayMessageError Create(RelayAllocationId allocationId, byte errorCode) + public void LogError() { - return new RelayMessageError + switch (ErrorCode) { - Header = RelayMessageHeader.Create(RelayMessageType.Error), - AllocationId = allocationId, - ErrorCode = errorCode - }; + case 0: + UnityEngine.Debug.LogError("Received error message from Relay: invalid protocol version. " + + "Make sure your Unity Transport package is up to date."); + break; + case 1: + UnityEngine.Debug.LogError("Received error message from Relay: player timed out due to inactivity."); + break; + case 2: + UnityEngine.Debug.LogError("Received error message from Relay: unauthorized."); + break; + case 3: + UnityEngine.Debug.LogError("Received error message from Relay: allocation ID client mismatch."); + break; + case 4: + UnityEngine.Debug.LogError("Received error message from Relay: allocation ID not found."); + break; + case 5: + UnityEngine.Debug.LogError("Received error message from Relay: not connected."); + break; + case 6: + UnityEngine.Debug.LogError("Received error message from Relay: self-connect not allowed."); + break; + default: + UnityEngine.Debug.LogError($"Received error message from Relay with unknown error code {ErrorCode}"); + break; + } + + if (ErrorCode == 1 || ErrorCode == 4) + { + UnityEngine.Debug.LogError("Relay allocation is invalid. See NetworkDriver.GetRelayConnectionStatus and " + + "RelayConnectionStatus.AllocationInvalid for details on how to handle this situation."); + } } } } diff --git a/Runtime/Relay/Messages/RelayMessageHeader.cs b/Runtime/Relay/Messages/RelayMessageHeader.cs index a8240fc..5db1d72 100644 --- a/Runtime/Relay/Messages/RelayMessageHeader.cs +++ b/Runtime/Relay/Messages/RelayMessageHeader.cs @@ -5,7 +5,9 @@ namespace Unity.Networking.Transport.Relay [StructLayout(LayoutKind.Sequential)] internal struct RelayMessageHeader { - public const int Length = 4; + public const int k_Length = 4; + public const ushort k_Signature = 0x72DA; + public const byte k_Version = 0; public ushort Signature; public byte Version; @@ -16,18 +18,25 @@ public bool IsValid() return Signature == 0x72DA && Version == 0; } - internal static RelayMessageHeader Create(RelayMessageType type) + public static RelayMessageHeader Create(RelayMessageType type) { return new RelayMessageHeader { - Signature = 0x72DA, - Version = 0, + Signature = k_Signature, + Version = k_Version, Type = type, }; } + + public static void Write(ref PacketProcessor packetProcessor, RelayMessageType type) + { + packetProcessor.AppendToPayload(k_Signature); + packetProcessor.AppendToPayload(k_Version); + packetProcessor.AppendToPayload(type); + } } - internal enum RelayMessageType : byte + public enum RelayMessageType : byte { Bind = 0, BindReceived = 1, diff --git a/Runtime/Relay/Messages/RelayMessagePing.cs b/Runtime/Relay/Messages/RelayMessagePing.cs index e590fa0..4f45d9d 100644 --- a/Runtime/Relay/Messages/RelayMessagePing.cs +++ b/Runtime/Relay/Messages/RelayMessagePing.cs @@ -5,13 +5,13 @@ namespace Unity.Networking.Transport.Relay [StructLayout(LayoutKind.Sequential)] internal struct RelayMessagePing { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length + 2; // Header + FromAllocationId + SequenceNumber + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length + 2; // Header + FromAllocationId + SequenceNumber public RelayMessageHeader Header; public RelayAllocationId FromAllocationId; public ushort SequenceNumber; - internal static RelayMessagePing Create(RelayAllocationId fromAllocationId, ushort dataLength) + public static RelayMessagePing Create(RelayAllocationId fromAllocationId) { return new RelayMessagePing { @@ -20,5 +20,12 @@ internal static RelayMessagePing Create(RelayAllocationId fromAllocationId, usho SequenceNumber = 1 }; } + + public static void Write(ref PacketProcessor packetProcessor, ref RelayAllocationId fromAllocationId) + { + RelayMessageHeader.Write(ref packetProcessor, RelayMessageType.Ping); + packetProcessor.AppendToPayload(fromAllocationId); + packetProcessor.AppendToPayload(1); + } } } diff --git a/Runtime/Relay/Messages/RelayMessageRelay.cs b/Runtime/Relay/Messages/RelayMessageRelay.cs index 0c79eb6..1c14988 100644 --- a/Runtime/Relay/Messages/RelayMessageRelay.cs +++ b/Runtime/Relay/Messages/RelayMessageRelay.cs @@ -1,27 +1,50 @@ using System.Runtime.InteropServices; +using Unity.Collections; namespace Unity.Networking.Transport.Relay { [StructLayout(LayoutKind.Sequential)] internal struct RelayMessageRelay { - public const int Length = RelayMessageHeader.Length + RelayAllocationId.k_Length * 2 + 2; // Header + FromAllocationId + ToAllocationId + DataLength + public const int k_Length = RelayMessageHeader.k_Length + RelayAllocationId.k_Length * 2 + 2; // Header + FromAllocationId + ToAllocationId + DataLength public RelayMessageHeader Header; public RelayAllocationId FromAllocationId; public RelayAllocationId ToAllocationId; - public ushort DataLength; + private ushort m_DataLength; - internal static RelayMessageRelay Create(RelayAllocationId fromAllocationId, RelayAllocationId toAllocationId, ushort dataLength) + public ushort DataLength + { + get => SwitchEndianness(m_DataLength); + set => m_DataLength = SwitchEndianness(value); + } + + internal static ushort SwitchEndianness(ushort value) + { + if (DataStreamWriter.IsLittleEndian) + return (ushort)((value << 8) | (value >> 8)); + + return value; + } + + public static RelayMessageRelay Create(RelayAllocationId fromAllocationId, RelayAllocationId toAllocationId, ushort dataLength) { return new RelayMessageRelay { Header = RelayMessageHeader.Create(RelayMessageType.Relay), FromAllocationId = fromAllocationId, ToAllocationId = toAllocationId, - DataLength = RelayNetworkProtocol.SwitchEndianness(dataLength), + DataLength = dataLength, }; } + + public static void Write(ref PacketProcessor packetProcessor, ref RelayAllocationId fromAllocationId, ref RelayAllocationId toAllocationId, ushort dataLength) + { + packetProcessor.PrependToPayload(SwitchEndianness(dataLength)); + packetProcessor.PrependToPayload(toAllocationId); + packetProcessor.PrependToPayload(fromAllocationId); + packetProcessor.PrependToPayload(RelayMessageHeader.Create(RelayMessageType.Relay)); + } } } diff --git a/Runtime/Relay/RelayAllocationId.cs b/Runtime/Relay/RelayAllocationId.cs index e575970..4c1af5e 100644 --- a/Runtime/Relay/RelayAllocationId.cs +++ b/Runtime/Relay/RelayAllocationId.cs @@ -9,23 +9,10 @@ namespace Unity.Networking.Transport.Relay /// public unsafe struct RelayAllocationId : IEquatable, IComparable { - /// - /// The length in bytes of the Allocation Id. - /// public const int k_Length = 16; - /// - /// The raw data of the Allocation Id. - /// public fixed byte Value[k_Length]; // Used by Relay SDK - /// - /// Converts a byte pointer to a RelayAllocationId. - /// - /// The pointer to the data of the Allocation Id. - /// The length of the data. - /// Provided byte array length is invalid, must be {k_Length} but got {length}. - /// Returns a RelayAllocationId constructed from the provided data. public static RelayAllocationId FromBytePointer(byte* dataPtr, int length) { if (length != k_Length) @@ -43,6 +30,17 @@ public static RelayAllocationId FromBytePointer(byte* dataPtr, int length) return allocationId; } + internal NetworkEndpoint ToNetworkEndpoint() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (UnsafeUtility.SizeOf() < UnsafeUtility.SizeOf()) + throw new InvalidOperationException($"RellayAllocationId ({UnsafeUtility.SizeOf()} bytes) does not fit into a NetworkEndpoint ({UnsafeUtility.SizeOf()} bytes)"); +#endif + var endpoint = default(NetworkEndpoint); + *(RelayAllocationId*)&endpoint = this; + return endpoint; + } + public static bool operator==(RelayAllocationId lhs, RelayAllocationId rhs) { return lhs.Compare(rhs) == 0; @@ -92,4 +90,19 @@ int Compare(RelayAllocationId other) } } } + + internal static class RellayAllocationIdExtensions + { + public static unsafe ref RelayAllocationId AsRelayAllocationId(this ref NetworkEndpoint address) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (UnsafeUtility.SizeOf() < UnsafeUtility.SizeOf()) + throw new InvalidOperationException($"RellayAllocationId ({UnsafeUtility.SizeOf()} bytes) does not fit into a NetworkEndpoint ({UnsafeUtility.SizeOf()} bytes)"); +#endif + fixed(NetworkEndpoint* addressPtr = &address) + { + return ref *(RelayAllocationId*)addressPtr; + } + } + } } diff --git a/Runtime/Relay/RelayConnectionData.cs b/Runtime/Relay/RelayConnectionData.cs index 3202320..712e8c8 100644 --- a/Runtime/Relay/RelayConnectionData.cs +++ b/Runtime/Relay/RelayConnectionData.cs @@ -5,27 +5,14 @@ namespace Unity.Networking.Transport.Relay { /// /// This is the encrypted data that the Relay server uses for describing a connection. - /// Used mainly in the connection establishing process (Binding). + /// Used mainly in the connection stablishing process (Binding) /// public unsafe struct RelayConnectionData { - /// - /// The length in bytes of the Connection Data. - /// public const int k_Length = 255; - /// - /// The raw data of the Connection Data - /// public fixed byte Value[k_Length]; // Used by Relay SDK - /// - /// Converts a byte pointer to a RelayConnectionData. - /// - /// The pointer to the data of the Connection Data. - /// The length of the data. - /// Provided byte array length is invalid, must be {k_Length} but got {length}. - /// Returns a RelayConnectionData constructed from the provided data. public static RelayConnectionData FromBytePointer(byte* dataPtr, int length) { if (length != k_Length) diff --git a/Runtime/Relay/RelayConnectionStatus.cs b/Runtime/Relay/RelayConnectionStatus.cs index df06f71..efc9ffa 100644 --- a/Runtime/Relay/RelayConnectionStatus.cs +++ b/Runtime/Relay/RelayConnectionStatus.cs @@ -27,7 +27,9 @@ public enum RelayConnectionStatus /// /// This status indicates that the allocation used to connect to the relay server is invalid, /// either because an invalid allocation was provided in - /// or because the allocation timed out due to inactivity. + /// or because the allocation timed out due to inactivity (the latter can happen if the value + /// of relayConnectionTimeMS provided in + /// is too high or if is not called often enough). /// /// In both cases, this is an unrecoverable error. A new allocation needs to be created through /// the relay service, and a new needs to be created with that @@ -41,9 +43,9 @@ public static class NetworkDriverRelayExtensions /// Get the current status of the connection to the relay server. public static RelayConnectionStatus GetRelayConnectionStatus(this NetworkDriver driver) { - if (driver.NetworkProtocol is RelayNetworkProtocol) + if (driver.m_NetworkStack.TryGetLayer(out var layer)) { - return (RelayConnectionStatus)driver.ProtocolStatus; + return layer.ConnectionStatus; } else { @@ -56,4 +58,4 @@ public static RelayConnectionStatus GetRelayConnectionStatus(this NetworkDriver } } } -} \ No newline at end of file +} diff --git a/Runtime/Relay/RelayHMACKey.cs b/Runtime/Relay/RelayHMACKey.cs index 46281d2..66f421f 100644 --- a/Runtime/Relay/RelayHMACKey.cs +++ b/Runtime/Relay/RelayHMACKey.cs @@ -3,29 +3,13 @@ namespace Unity.Networking.Transport.Relay { - /// - /// Used to represent the HMACKey for the Relay Service - /// public unsafe struct RelayHMACKey { - /// - /// The length in bytes of the RelayHMACKey. - /// public const int k_Length = 64; - /// - /// The raw data of the HMAC key. - /// public fixed byte Value[k_Length]; // Used by Relay SDK - /// - /// Converts a byte pointer to a RelayHMACKey. - /// - /// The pointer to the data of the Allocation Id. - /// The length of the data. - /// Provided byte array length is invalid, must be {k_Length} but got {length}. - /// Returns a RelayHMACKey constructed from the provided data. public static RelayHMACKey FromBytePointer(byte* data, int length) { if (length != k_Length) diff --git a/Runtime/Relay/RelayNetworkParameter.cs b/Runtime/Relay/RelayNetworkParameter.cs new file mode 100644 index 0000000..b55f712 --- /dev/null +++ b/Runtime/Relay/RelayNetworkParameter.cs @@ -0,0 +1,71 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Networking.Transport.Relay +{ + public static class RelayParameterExtensions + { + public static ref NetworkSettings WithRelayParameters( + ref this NetworkSettings settings, + ref RelayServerData serverData, + int relayConnectionTimeMS = RelayNetworkParameter.k_DefaultConnectionTimeMS + ) + { + var parameter = new RelayNetworkParameter + { + ServerData = serverData, + RelayConnectionTimeMS = relayConnectionTimeMS, + }; + + settings.AddRawParameterStruct(ref parameter); + + return ref settings; + } + + public static RelayNetworkParameter GetRelayParameters(ref this NetworkSettings settings) + { + if (!settings.TryGet(out var parameters)) + { + throw new System.InvalidOperationException($"Can't extract Relay parameters: {nameof(RelayNetworkParameter)} must be provided to the {nameof(NetworkSettings)}"); + } + + return parameters; + } + } + + public struct RelayNetworkParameter : INetworkParameter + { + internal const int k_DefaultConnectionTimeMS = 3000; + + public RelayServerData ServerData; + public int RelayConnectionTimeMS; + + public unsafe bool Validate() + { + var valid = true; + + if (ServerData.Endpoint == default) + { + 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; + UnityEngine.Debug.LogError($"{nameof(ServerData.AllocationId)} value ({ServerData.AllocationId}) must be a valid value"); + } + if (RelayConnectionTimeMS < 0) + { + valid = false; + UnityEngine.Debug.LogError($"{nameof(RelayConnectionTimeMS)} value({RelayConnectionTimeMS}) must be greater or equal to 0"); + } + + return valid; + } + } +} diff --git a/Runtime/Relay/RelayNetworkProtocol.cs.meta b/Runtime/Relay/RelayNetworkParameter.cs.meta similarity index 100% rename from Runtime/Relay/RelayNetworkProtocol.cs.meta rename to Runtime/Relay/RelayNetworkParameter.cs.meta diff --git a/Runtime/Relay/RelayNetworkProtocol.cs b/Runtime/Relay/RelayNetworkProtocol.cs deleted file mode 100644 index 4c56306..0000000 --- a/Runtime/Relay/RelayNetworkProtocol.cs +++ /dev/null @@ -1,1079 +0,0 @@ -using System; -using System.Threading; -using AOT; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Protocols; -#if ENABLE_MANAGED_UNITYTLS -using Unity.Networking.Transport.TLS; -using Unity.TLS.LowLevel; -#endif -using UnityEngine; -using UnityEngine.Assertions; - -namespace Unity.Networking.Transport.Relay -{ - internal static class ConnectionAddressExtensions - { - /// - /// Converts the relay allocation id using the specified address - /// - /// The address - /// The ref relay allocation id - public static unsafe ref RelayAllocationId AsRelayAllocationId(this ref NetworkInterfaceEndPoint address) - { - fixed(byte* addressPtr = address.data) - { - return ref *(RelayAllocationId*)addressPtr; - } - } - } - - public static class RelayParameterExtensions - { - /// - /// Sets the values for the - /// - /// - /// - public static ref NetworkSettings WithRelayParameters( - ref this NetworkSettings settings, - ref RelayServerData serverData, - int relayConnectionTimeMS = RelayNetworkParameter.k_DefaultConnectionTimeMS - ) - { - var parameter = new RelayNetworkParameter - { - ServerData = serverData, - RelayConnectionTimeMS = relayConnectionTimeMS, - }; - - settings.AddRawParameterStruct(ref parameter); - - return ref settings; - } - - /// - /// Gets the - /// - /// Returns the values for the - public static RelayNetworkParameter GetRelayParameters(ref this NetworkSettings settings) - { - if (!settings.TryGet(out var parameters)) - { - throw new System.InvalidOperationException($"Can't extract Relay parameters: {nameof(RelayNetworkParameter)} must be provided to the {nameof(NetworkSettings)}"); - } - - return parameters; - } - } - - /// - /// Relay protocol network parementers used to connect to the Unity Relay service. This data must be provided to - /// the function in order to be able to use connect to Relay. - /// - public struct RelayNetworkParameter : INetworkParameter - { - internal const int k_DefaultConnectionTimeMS = 3000; - - /// - /// The data that is used to describe the connection to the Relay Server. - /// - public RelayServerData ServerData; - /// - /// The timeout in milliseconds after which a ping message is sent to the Relay Server - /// to keep the connection alive. - /// - public int RelayConnectionTimeMS; - - public unsafe bool Validate() - { - var valid = true; - - if (ServerData.Endpoint == default) - { - 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; - UnityEngine.Debug.LogError($"{nameof(ServerData.AllocationId)} value ({ServerData.AllocationId}) must be a valid value"); - } - if (RelayConnectionTimeMS < 0) - { - valid = false; - UnityEngine.Debug.LogError($"{nameof(RelayConnectionTimeMS)} value({RelayConnectionTimeMS}) must be greater or equal to 0"); - } - - return valid; - } - } - - [BurstCompile] - internal struct RelayNetworkProtocol : INetworkProtocol - { - public static ushort SwitchEndianness(ushort value) - { - if (DataStreamWriter.IsLittleEndian) - return (ushort)((value << 8) | (value >> 8)); - - return value; - } - - private enum RelayConnectionState : byte - { - Unbound = 0, - Handshake = 1, - Binding = 2, - Bound = 3, - Connected = 4, - } - - private enum SecuredRelayConnectionState : byte - { - Unsecure = 0, - Secured = 1 - } - - private struct RelayProtocolData - { - public RelayConnectionState ConnectionState; - public SecuredRelayConnectionState SecureState; - public SessionIdToken ConnectionReceiveToken; - public long LastConnectAttempt; - public long LastUpdateTime; - public long LastSentTime; - public int ConnectTimeoutMS; - public int RelayConnectionTimeMS; - public RelayAllocationId HostAllocationId; - public NetworkInterfaceEndPoint ServerEndpoint; - public RelayServerData ServerData; -#if ENABLE_MANAGED_UNITYTLS - public SecureClientState SecureClientState; -#endif - // Used by clients to indicate that we should attempt to connect as soon as we're bound. - // We can't just connect unconditionnally on bind since the user might not have called - // Connect yet at that point. - public bool ConnectOnBind; - } - - public IntPtr UserData; - - public void Initialize(NetworkSettings settings) - { - var relayConfig = settings.GetRelayParameters(); - var config = settings.GetNetworkConfigParameters(); - -#if ENABLE_MANAGED_UNITYTLS - if (relayConfig.ServerData.IsSecure == 1) - { - ManagedSecureFunctions.Initialize(); - } -#endif - unsafe - { - UserData = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), Allocator.Persistent); - *(RelayProtocolData*)UserData = new RelayProtocolData - { - ServerData = relayConfig.ServerData, - ConnectionState = RelayConnectionState.Unbound, - ConnectTimeoutMS = config.connectTimeoutMS, - RelayConnectionTimeMS = relayConfig.RelayConnectionTimeMS, - SecureState = SecuredRelayConnectionState.Unsecure - }; - } - } - - public void Dispose() - { - unsafe - { -#if ENABLE_MANAGED_UNITYTLS - // cleanup the SecureData - var protocolData = (RelayProtocolData*)UserData; - if (protocolData->SecureClientState.ClientPtr != null) - SecureNetworkProtocol.DisposeSecureClient(ref protocolData->SecureClientState); -#endif - if (UserData != default) - UnsafeUtility.Free(UserData.ToPointer(), Allocator.Persistent); - - UserData = default; - } - } - - bool TryExtractParameters(out T config, params INetworkParameter[] param) - { - for (var i = 0; i < param.Length; ++i) - { - if (param[i] is T) - { - config = (T)param[i]; - return true; - } - } - - config = default; - return false; - } - - public int Bind(INetworkInterface networkInterface, ref NetworkInterfaceEndPoint localEndPoint) - { - if (networkInterface.Bind(localEndPoint) != 0) - return -1; - - unsafe - { - var protocolData = (RelayProtocolData*)UserData; - // Relay protocol will stablish only one physical connection using the interface (to Relay server). - // All client connections are virtual. Here we initialize that connection. - networkInterface.CreateInterfaceEndPoint(protocolData->ServerData.Endpoint, out protocolData->ServerEndpoint); - - if (protocolData->ServerData.IsSecure == 1) - { - protocolData->ConnectionState = RelayConnectionState.Handshake; - } - else - { - // The Relay protocol binding process requires to exchange some messages to stablish the connection - // with the Relay server, so we set the state to "binding" until the connection with server is confirm. - protocolData->ConnectionState = RelayConnectionState.Binding; - } - - return 0; - } - } - - public int CreateConnectionAddress(INetworkInterface networkInterface, NetworkEndPoint endPoint, out NetworkInterfaceEndPoint address) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (UnsafeUtility.SizeOf() < UnsafeUtility.SizeOf()) - throw new InvalidOperationException("RelayAllocationId does not fit in NetworkInterfaceEndPoint"); -#endif - unsafe - { - var protocolData = (RelayProtocolData*)UserData; - - // The remote endpoint for clients is always the host allocation ID. - // Note that there's a good chance the host allocation ID is not known yet. That's - // fine; we update the connection's address (allocation ID) once connected. - address = default; - fixed(byte* addressPtr = address.data) - { - *(RelayAllocationId*)addressPtr = protocolData->HostAllocationId; - } - - return 0; - } - } - - public NetworkEndPoint GetRemoteEndPoint(INetworkInterface networkInterface, NetworkInterfaceEndPoint address) - { - unsafe - { - var protocolData = (RelayProtocolData*)UserData; - return networkInterface.GetGenericEndPoint(protocolData->ServerEndpoint); - } - } - - public NetworkProtocol CreateProtocolInterface() - { - return new NetworkProtocol( - computePacketOverhead: new TransportFunctionPointer(ComputePacketOverhead), - processReceive: new TransportFunctionPointer(ProcessReceive), - processSend: new TransportFunctionPointer(ProcessSend), - processSendConnectionAccept: new TransportFunctionPointer(ProcessSendConnectionAccept), - connect: new TransportFunctionPointer(Connect), - disconnect: new TransportFunctionPointer(Disconnect), - processSendPing: new TransportFunctionPointer(ProcessSendPing), - processSendPong: new TransportFunctionPointer(ProcessSendPong), - update: new TransportFunctionPointer(Update), - needsUpdate: true, - userData: UserData, - maxHeaderSize: RelayMessageRelay.Length + UdpCHeader.Length, - maxFooterSize: SessionIdToken.k_Length - ); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ComputePacketOverheadDelegate))] - public static int ComputePacketOverhead(ref NetworkDriver.Connection connection, out int dataOffset) - { - var utpOverhead = UnityTransportProtocol.ComputePacketOverhead(ref connection, out dataOffset); - dataOffset += RelayMessageRelay.Length; - return utpOverhead + RelayMessageRelay.Length; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessReceiveDelegate))] - public static void ProcessReceive(IntPtr stream, ref NetworkInterfaceEndPoint endpoint, int size, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData, ref ProcessPacketCommand command) - { - unsafe - { - var protocolData = (RelayProtocolData*)userData; - - if (endpoint != protocolData->ServerEndpoint) - { - command.Type = ProcessPacketCommandType.Drop; - return; - } - -#if ENABLE_MANAGED_UNITYTLS - if (protocolData->ConnectionState == RelayConnectionState.Handshake) - { - var secureUserData = (SecureUserData*)protocolData->SecureClientState.ClientConfig->transportUserData; - - SecureNetworkProtocol.SetSecureUserData(stream, size, ref endpoint, ref sendInterface, ref queueHandle, secureUserData); - - var clientState = Binding.unitytls_client_get_state(protocolData->SecureClientState.ClientPtr); - uint handshakeResult = Binding.UNITYTLS_SUCCESS; - - // check and see if we are still in the handshake :D - if (clientState == Binding.UnityTLSClientState_Handshake - || clientState == Binding.UnityTLSClientState_Init) - { - bool shouldRunAgain = false; - do - { - handshakeResult = SecureNetworkProtocol.SecureHandshakeStep(ref protocolData->SecureClientState); - clientState = Binding.unitytls_client_get_state(protocolData->SecureClientState.ClientPtr); - shouldRunAgain = (size != 0 && secureUserData->BytesProcessed == 0 && clientState == Binding.UnityTLSClientState_Handshake); - } - while (shouldRunAgain); - } - - if (clientState == Binding.UnityTLSClientState_Messaging) - { - // we are moving to the binding state - protocolData->ConnectionState = RelayConnectionState.Binding; - protocolData->SecureState = SecuredRelayConnectionState.Secured; - } - - command.Type = ProcessPacketCommandType.Drop; - return; - } - - // Are we setup for a secure state? and if so - if (protocolData->ServerData.IsSecure == 1 && - (protocolData->SecureState != SecuredRelayConnectionState.Secured)) - { - // we should not get here ? - command.Type = ProcessPacketCommandType.Drop; - return; - } - - if (protocolData->ServerData.IsSecure == 1 && - (protocolData->SecureState == SecuredRelayConnectionState.Secured)) - { - var secureUserData = (SecureUserData*)protocolData->SecureClientState.ClientConfig->transportUserData; - - SecureNetworkProtocol.SetSecureUserData(stream, size, ref endpoint, ref sendInterface, ref queueHandle, secureUserData); - - var buffer = new NativeArray(NetworkParameterConstants.MTU, Allocator.Temp); - var bytesRead = new UIntPtr(); - var result = Binding.unitytls_client_read_data(protocolData->SecureClientState.ClientPtr, - (byte*)buffer.GetUnsafePtr(), new UIntPtr(NetworkParameterConstants.MTU), - &bytesRead); - - if (result == Binding.UNITYTLS_SUCCESS) - { - // when we have a proper read we need to copy that data into the stream. It should be noted - // that this copy does change data we don't technically own. - UnsafeUtility.MemCpy((void*)stream, buffer.GetUnsafePtr(), bytesRead.ToUInt32()); - - if (ProcessRelayData(stream, ref endpoint, (int)bytesRead.ToUInt32(), ref sendInterface, ref queueHandle, ref command, protocolData)) - return; - } - - command.Type = ProcessPacketCommandType.Drop; - return; - } -#endif - if (ProcessRelayData(stream, ref endpoint, size, ref sendInterface, ref queueHandle, ref command, protocolData)) - return; - - command.Type = ProcessPacketCommandType.Drop; - } - } - - private static unsafe bool ProcessRelayData(IntPtr stream, ref NetworkInterfaceEndPoint endpoint, int size, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, ref ProcessPacketCommand command, - RelayProtocolData* protocolData) - { - var data = (byte*)stream; - var header = *(RelayMessageHeader*)data; - - if (size < RelayMessageHeader.Length || !header.IsValid()) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - UnityEngine.Debug.LogError("Received an invalid Relay message header"); -#endif - command.Type = ProcessPacketCommandType.Drop; - return true; - } -#if ENABLE_MANAGED_UNITYTLS - if (protocolData->ServerData.IsSecure == 1 && - (protocolData->SecureState == SecuredRelayConnectionState.Secured)) - { - var secureUserData = (SecureUserData*)protocolData->SecureClientState.ClientConfig->transportUserData; - SecureNetworkProtocol.SetSecureUserData(stream, size, ref endpoint, ref sendInterface, ref queueHandle, secureUserData); - } -#endif - switch (header.Type) - { - case RelayMessageType.BindReceived: - command.Type = ProcessPacketCommandType.Drop; - - if (size != RelayMessageHeader.Length) - { - UnityEngine.Debug.LogError("Received an invalid Relay Bind Received message: Wrong length"); - return true; - } - - protocolData->ConnectionState = RelayConnectionState.Bound; - - if (protocolData->ConnectOnBind) - { - SendConnectionRequestToRelay(protocolData, ref sendInterface, ref queueHandle); - } - - command.Type = ProcessPacketCommandType.ProtocolStatusUpdate; - command.As.ProtocolStatusUpdate.Status = (int)RelayConnectionStatus.Established; - - return true; - - case RelayMessageType.Accepted: - command.Type = ProcessPacketCommandType.Drop; - - if (size != RelayMessageAccepted.Length) - { - UnityEngine.Debug.LogError("Received an invalid Relay Accepted message: Wrong length"); - return true; - } - - if (protocolData->HostAllocationId != default) - return true; - - var acceptedMessage = *(RelayMessageAccepted*)data; - protocolData->HostAllocationId = acceptedMessage.FromAllocationId; - - command.Type = ProcessPacketCommandType.AddressUpdate; - command.Address = default; - command.SessionId = protocolData->ConnectionReceiveToken; - command.As.AddressUpdate.NewAddress = default; - - fixed(byte* addressPtr = command.As.AddressUpdate.NewAddress.data) - { - *(RelayAllocationId*)addressPtr = acceptedMessage.FromAllocationId; - } - - var type = UdpCProtocol.ConnectionRequest; - var token = protocolData->ConnectionReceiveToken; - var result = SendHeaderOnlyHostMessage( - type, token, protocolData, ref acceptedMessage.FromAllocationId, ref sendInterface, ref queueHandle); - if (result < 0) - { - Debug.LogError("Failed to send Connection Request message to host."); - return false; - } - - return true; - - case RelayMessageType.Relay: - var relayMessage = *(RelayMessageRelay*)data; - relayMessage.DataLength = RelayNetworkProtocol.SwitchEndianness(relayMessage.DataLength); - if (size < RelayMessageRelay.Length || size != RelayMessageRelay.Length + relayMessage.DataLength) - { - UnityEngine.Debug.LogError($"Received an invalid Relay Received message: Wrong length"); - command.Type = ProcessPacketCommandType.Drop; - return true; - } - - // TODO: Make sure UTP protocol is not sending any message back here as it wouldn't be using Relay - UnityTransportProtocol.ProcessReceive(stream + RelayMessageRelay.Length, ref endpoint, - size - RelayMessageRelay.Length, ref sendInterface, ref queueHandle, IntPtr.Zero, ref command); - - switch (command.Type) - { - case ProcessPacketCommandType.ConnectionAccept: - protocolData->ConnectionState = RelayConnectionState.Connected; - break; - - case ProcessPacketCommandType.Data: - command.As.Data.Offset += RelayMessageRelay.Length; - break; - - case ProcessPacketCommandType.DataWithImplicitConnectionAccept: - command.As.DataWithImplicitConnectionAccept.Offset += RelayMessageRelay.Length; - break; - - case ProcessPacketCommandType.Disconnect: - SendRelayDisconnect( - protocolData, ref relayMessage.FromAllocationId, ref sendInterface, ref queueHandle); - break; - } - - command.Address = default; - fixed(byte* addressPtr = command.Address.data) - { - *(RelayAllocationId*)addressPtr = relayMessage.FromAllocationId; - } - - return true; - - case RelayMessageType.Error: - command.Type = ProcessPacketCommandType.Drop; - ProcessRelayError(data, size, ref command); - return true; - } - - command.Type = ProcessPacketCommandType.Drop; - return true; - } - - private static unsafe void ProcessRelayError(byte* data, int size, ref ProcessPacketCommand command) - { - if (size != RelayMessageError.Length) - { - Debug.LogError("Received an invalid Relay Error message (wrong length)."); - return; - } - - var errorMessage = *(RelayMessageError*)data; - - switch (errorMessage.ErrorCode) - { - case 0: - Debug.LogError("Received error message from Relay: invalid protocol version. " + - "Make sure your Unity Transport package is up to date."); - break; - case 1: - Debug.LogError("Received error message from Relay: player timed out due to inactivity."); - break; - case 2: - Debug.LogError("Received error message from Relay: unauthorized."); - break; - case 3: - Debug.LogError("Received error message from Relay: allocation ID client mismatch."); - break; - case 4: - Debug.LogError("Received error message from Relay: allocation ID not found."); - break; - case 5: - Debug.LogError("Received error message from Relay: not connected."); - break; - case 6: - Debug.LogError("Received error message from Relay: self-connect not allowed."); - break; - default: - Debug.LogError($"Received error message from Relay with unknown error code {errorMessage.ErrorCode}"); - break; - } - - // Allocation time outs and failure to find the allocation indicate that the allocation - // is not valid anymore, and that users will need to recreate a new one. - if (errorMessage.ErrorCode == 1 || errorMessage.ErrorCode == 4) - { - Debug.LogError("Relay allocation is invalid. See NetworkDriver.GetRelayConnectionStatus and " + - "RelayConnectionStatus.AllocationInvalid for details on how to handle this situation."); - command.Type = ProcessPacketCommandType.ProtocolStatusUpdate; - command.As.ProtocolStatusUpdate.Status = (int)RelayConnectionStatus.AllocationInvalid; - } - } - - private static unsafe int SendMessage(RelayProtocolData* protocolData, ref NetworkSendInterface sendInterface, - ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle) - { -#if ENABLE_MANAGED_UNITYTLS - if (protocolData->ServerData.IsSecure == 1 && protocolData->SecureState == SecuredRelayConnectionState.Secured) - { - var secureUserData = (SecureUserData*)protocolData->SecureClientState.ClientConfig->transportUserData; - SecureNetworkProtocol.SetSecureUserData( - IntPtr.Zero, 0, ref protocolData->ServerEndpoint, ref sendInterface, ref queueHandle, secureUserData); - - // we need to free up the current handle before we call send because we could be at capacity and thus - // when we go and try to get a new handle it will fail. - var buffer = new NativeArray(sendHandle.size, Allocator.Temp); - UnsafeUtility.MemCpy(buffer.GetUnsafePtr(), (void*)sendHandle.data, sendHandle.size); - - // We end up having to abort this handle so we can free it up as DTLS will generate a - // new one based on the encrypted buffer size. - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - - var result = Binding.unitytls_client_send_data( - protocolData->SecureClientState.ClientPtr, (byte*)buffer.GetUnsafePtr(), new UIntPtr((uint)buffer.Length)); - - if (result != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Secure send failed with result: {result}."); - // Error is likely caused by a connection that's closed or not established yet. - return (int)Error.StatusCode.NetworkStateMismatch; - } - - return buffer.Length; - } - else -#endif - { - return sendInterface.EndSendMessage.Ptr.Invoke( - ref sendHandle, ref protocolData->ServerEndpoint, sendInterface.UserData, ref queueHandle); - } - } - - private static unsafe void SendRelayDisconnect(RelayProtocolData* protocolData, ref RelayAllocationId hostAllocationId, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - var result = sendInterface.BeginSendMessage.Ptr.Invoke( - out var sendHandle, sendInterface.UserData, RelayMessageDisconnect.Length); - if (result != 0) - { - UnityEngine.Debug.LogError("Failed to send Disconnect message to relay."); - return; - } - - var packet = (byte*)sendHandle.data; - sendHandle.size = RelayMessageDisconnect.Length; - if (sendHandle.size > sendHandle.capacity) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - UnityEngine.Debug.LogError("Failed to send Disconnect message to relay."); - return; - } - - var disconnectMessage = (RelayMessageDisconnect*)packet; - *disconnectMessage = RelayMessageDisconnect.Create(protocolData->ServerData.AllocationId, hostAllocationId); - - if (SendMessage(protocolData, ref sendInterface, ref sendHandle, ref queueHandle) < 0) - { - Debug.LogError("Failed to send Disconnect message to relay."); - return; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendDelegate))] - public static unsafe int ProcessSend(ref NetworkDriver.Connection connection, bool hasPipeline, ref NetworkSendInterface sendInterface, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var relayProtocolData = (RelayProtocolData*)userData; - - var dataLength = (ushort)UnityTransportProtocol.WriteSendMessageHeader(ref connection, hasPipeline, ref sendHandle, RelayMessageRelay.Length); - - var relayMessage = (RelayMessageRelay*)sendHandle.data; - fixed(byte* addressPtr = connection.Address.data) - { - *relayMessage = RelayMessageRelay.Create(relayProtocolData->ServerData.AllocationId, *(RelayAllocationId*)addressPtr, dataLength); - } - - Interlocked.Exchange(ref relayProtocolData->LastSentTime, relayProtocolData->LastUpdateTime); - - return SendMessage(relayProtocolData, ref sendInterface, ref sendHandle, ref queueHandle); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendConnectionAcceptDelegate))] - public static void ProcessSendConnectionAccept(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var relayProtocolData = (RelayProtocolData*)userData; - - var toAllocationId = default(RelayAllocationId); - - fixed(byte* addrPtr = connection.Address.data) - toAllocationId = *(RelayAllocationId*)addrPtr; - - var maxLengthNeeded = RelayMessageRelay.Length + UnityTransportProtocol.GetConnectionAcceptMessageMaxLength(); - if (sendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, sendInterface.UserData, maxLengthNeeded) != 0) - { - UnityEngine.Debug.LogError("Failed to send a ConnectionRequest packet"); - return; - } - - if (sendHandle.capacity < maxLengthNeeded) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet: size exceeds capacity"); - return; - } - - var packet = (byte*)sendHandle.data; - var size = UnityTransportProtocol.WriteConnectionAcceptMessage(ref connection, packet + RelayMessageRelay.Length, sendHandle.capacity - RelayMessageRelay.Length); - - if (size < 0) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet"); - return; - } - - sendHandle.size = RelayMessageRelay.Length + size; - - var relayMessage = (RelayMessageRelay*)packet; - *relayMessage = RelayMessageRelay.Create(relayProtocolData->ServerData.AllocationId, toAllocationId, (ushort)size); - Assert.IsTrue(sendHandle.size <= sendHandle.capacity); - - if (SendMessage(relayProtocolData, ref sendInterface, ref sendHandle, ref queueHandle) < 0) - { - Debug.LogError("Failed to send Connection Accept message to host."); - return; - } - } - } - - private static unsafe int SendHeaderOnlyHostMessage(UdpCProtocol type, SessionIdToken token, RelayProtocolData* relayProtocolData, - ref RelayAllocationId hostAllocationId, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - var result = sendInterface.BeginSendMessage.Ptr.Invoke( - out var sendHandle, sendInterface.UserData, RelayMessageRelay.Length + UdpCHeader.Length); - if (result != 0) - { - return -1; - } - - var packet = (byte*)sendHandle.data; - sendHandle.size = RelayMessageRelay.Length + UdpCHeader.Length; - if (sendHandle.size > sendHandle.capacity) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - return -1; - } - - var relayMessage = (RelayMessageRelay*)packet; - *relayMessage = RelayMessageRelay.Create( - relayProtocolData->ServerData.AllocationId, hostAllocationId, UdpCHeader.Length); - - var header = (UdpCHeader*)(((byte*)relayMessage) + RelayMessageRelay.Length); - *header = new UdpCHeader - { - Type = (byte)type, - SessionToken = token, - Flags = 0 - }; - - return SendMessage(relayProtocolData, ref sendInterface, ref sendHandle, ref queueHandle); - } - - private static unsafe void SendConnectionRequestToRelay(RelayProtocolData* relayProtocolData, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - var result = sendInterface.BeginSendMessage.Ptr.Invoke( - out var sendHandle, sendInterface.UserData, RelayMessageConnectRequest.Length); - if (result != 0) - { - Debug.LogError("Failed to send ConnectRequest to relay."); - return; - } - - var packet = (byte*)sendHandle.data; - sendHandle.size = RelayMessageConnectRequest.Length; - if (sendHandle.size > sendHandle.capacity) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - Debug.LogError("Failed to send ConnectRequest to relay."); - return; - } - - var message = (RelayMessageConnectRequest*)packet; - *message = RelayMessageConnectRequest.Create( - relayProtocolData->ServerData.AllocationId, relayProtocolData->ServerData.HostConnectionData); - - if (SendMessage(relayProtocolData, ref sendInterface, ref sendHandle, ref queueHandle) < 0) - { - Debug.LogError("Failed to send ConnectRequest to relay."); - return; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ConnectDelegate))] - public static void Connect(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var relayProtocolData = (RelayProtocolData*)userData; - relayProtocolData->ConnectionReceiveToken = connection.ReceiveToken; - - // If we're not bound, either we're still binding (and we can't attempt to connect - // yet), or we're connected (and connecting is thus useless). - if (relayProtocolData->ConnectionState != RelayConnectionState.Bound) - { - relayProtocolData->ConnectOnBind = true; - return; - } - - if (relayProtocolData->HostAllocationId == default) - { - SendConnectionRequestToRelay(relayProtocolData, ref sendInterface, ref queueHandle); - } - else - { - var type = UdpCProtocol.ConnectionRequest; - var token = relayProtocolData->ConnectionReceiveToken; - var result = SendHeaderOnlyHostMessage( - type, token, relayProtocolData, ref relayProtocolData->HostAllocationId, ref sendInterface, ref queueHandle); - if (result < 0) - { - Debug.LogError("Failed to send Connection Request message to host."); - return; - } - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.DisconnectDelegate))] - public static unsafe void Disconnect(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var relayProtocolData = (RelayProtocolData*)userData; - - var type = UdpCProtocol.Disconnect; - var token = connection.SendToken; - var result = SendHeaderOnlyHostMessage( - type, token, relayProtocolData, ref connection.Address.AsRelayAllocationId(), ref sendInterface, ref queueHandle); - if (result < 0) - { - Debug.LogError("Failed to send Disconnect message to host."); - return; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPingDelegate))] - public static unsafe void ProcessSendPing(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var relayProtocolData = (RelayProtocolData*)userData; - - var type = UdpCProtocol.Ping; - var token = connection.SendToken; - var result = SendHeaderOnlyHostMessage( - type, token, relayProtocolData, ref connection.Address.AsRelayAllocationId(), ref sendInterface, ref queueHandle); - if (result < 0) - { - Debug.LogError("Failed to send Ping message to host."); - return; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPingDelegate))] - public static unsafe void ProcessSendPong(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var relayProtocolData = (RelayProtocolData*)userData; - - var type = UdpCProtocol.Pong; - var token = connection.SendToken; - var result = SendHeaderOnlyHostMessage( - type, token, relayProtocolData, ref connection.Address.AsRelayAllocationId(), ref sendInterface, ref queueHandle); - if (result < 0) - { - Debug.LogError("Failed to send Pong message to host."); - return; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.UpdateDelegate))] - public static void Update(long updateTime, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (RelayProtocolData*)userData; - - switch (protocolData->ConnectionState) - { -#if ENABLE_MANAGED_UNITYTLS - case RelayConnectionState.Handshake: - { - if (protocolData->SecureClientState.ClientPtr == null) - { - // we need to create a config/client - var config = (Binding.unitytls_client_config*)UnsafeUtility.Malloc( - UnsafeUtility.SizeOf(), - UnsafeUtility.AlignOf(), Allocator.Persistent); - - *config = new Binding.unitytls_client_config(); - - Binding.unitytls_client_init_config(config); - - config->dataSendCB = ManagedSecureFunctions.s_SendCallback.Data.Value; - config->dataReceiveCB = ManagedSecureFunctions.s_RecvMethod.Data.Value; - - config->clientAuth = Binding.UnityTLSRole_Client; - config->transportProtocol = Binding.UnityTLSTransportProtocol_Datagram; - config->clientAuth = Binding.UnityTLSClientAuth_Optional; - - config->ssl_read_timeout_ms = SecureNetworkProtocol.DefaultParameters.SSLReadTimeoutMs; - config->ssl_handshake_timeout_min = - SecureNetworkProtocol.DefaultParameters.SSLHandshakeTimeoutMin; - config->ssl_handshake_timeout_max = - SecureNetworkProtocol.DefaultParameters.SSLHandshakeTimeoutMax; - - FixedString32Bytes hostname = "relay"; - config->hostname = hostname.GetUnsafePtr(); - - config->psk = new Binding.unitytls_dataRef() - { - dataPtr = protocolData->ServerData.HMACKey.Value, - dataLen = new UIntPtr(64) - }; - - config->pskIdentity = new Binding.unitytls_dataRef() - { - dataPtr = protocolData->ServerData.AllocationId.Value, - dataLen = new UIntPtr((uint)16) - }; - - protocolData->SecureClientState.ClientConfig = config; - - protocolData->SecureClientState.ClientPtr = Binding.unitytls_client_create(Binding.UnityTLSRole_Client, protocolData->SecureClientState.ClientConfig); - - IntPtr secureUserData = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), - UnsafeUtility.AlignOf(), Allocator.Persistent); - - *(SecureUserData*)secureUserData = new SecureUserData - { - Interface = default, - Remote = default, - QueueHandle = default, - StreamData = IntPtr.Zero, - Size = 0, - BytesProcessed = 0 - }; - - protocolData->SecureClientState.ClientConfig->transportUserData = secureUserData; - - Binding.unitytls_client_init(protocolData->SecureClientState.ClientPtr); - } - - var currentState = Binding.unitytls_client_get_state(protocolData->SecureClientState.ClientPtr); - if (currentState == Binding.UnityTLSClientState_Handshake) - return; - - var data = (SecureUserData*)protocolData->SecureClientState.ClientConfig->transportUserData; - - SecureNetworkProtocol.SetSecureUserData(IntPtr.Zero, 0, ref protocolData->ServerEndpoint, ref sendInterface, ref queueHandle, data); - var handshakeResult = SecureNetworkProtocol.SecureHandshakeStep(ref protocolData->SecureClientState); - } - break; -#endif - case RelayConnectionState.Binding: - if (updateTime - protocolData->LastConnectAttempt > protocolData->ConnectTimeoutMS || protocolData->LastUpdateTime == 0) - { - protocolData->LastConnectAttempt = updateTime; - protocolData->LastSentTime = updateTime; - - const int requirePayloadSize = RelayMessageBind.Length; - - if (sendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, sendInterface.UserData, requirePayloadSize) != 0) - { - UnityEngine.Debug.LogError("Failed to send a ConnectionRequest packet"); - return; - } - - var writeResult = WriteBindMessage(ref protocolData->ServerEndpoint, ref sendHandle, ref queueHandle, userData); - - if (!writeResult) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - return; - } - - if (SendMessage(protocolData, ref sendInterface, ref sendHandle, ref queueHandle) < 0) - { - Debug.LogError("Failed to send Bind message to relay."); - return; - } - } - break; - case RelayConnectionState.Bound: - case RelayConnectionState.Connected: - { - if (updateTime - protocolData->LastSentTime >= protocolData->RelayConnectionTimeMS) - { - if (sendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, sendInterface.UserData, RelayMessagePing.Length) != 0) - { - UnityEngine.Debug.LogError("Failed to send a RelayPingMessage packet"); - return; - } - - var writeResult = WriteRelayPingMessage(ref protocolData->ServerEndpoint, ref sendHandle, ref queueHandle, userData); - - if (!writeResult) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - return; - } - - if (SendMessage(protocolData, ref sendInterface, ref sendHandle, ref queueHandle) < 0) - { - Debug.LogError("Failed to send Ping message to relay."); - return; - } - - protocolData->LastSentTime = updateTime; - } - } - break; - } - - protocolData->LastUpdateTime = updateTime; - } - } - - [BurstCompatible] - private static unsafe bool WriteRelayPingMessage(ref NetworkInterfaceEndPoint serverEndpoint, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var protocolData = (RelayProtocolData*)userData; - - var packet = (byte*)sendHandle.data; - sendHandle.size = RelayMessagePing.Length; - if (sendHandle.size > sendHandle.capacity) - { - UnityEngine.Debug.LogError("Failed to send a RelayPingMessage packet"); - return false; - } - - var message = (RelayMessagePing*)packet; - *message = RelayMessagePing.Create(protocolData->ServerData.AllocationId, 0); - - return true; - } - - [BurstCompatible] - private static unsafe bool WriteBindMessage(ref NetworkInterfaceEndPoint serverEndpoint, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var writer = WriterForSendBuffer(RelayMessageBind.Length, ref sendHandle); - if (writer.IsCreated == false) - { - UnityEngine.Debug.LogError("Failed to send a RelayBindMessage packet"); - return false; - } - - // RelayMessageBind contains unaligned ushort, so we don't want to 'blit' the structure, instead we're using DataStreamWriter - var protocolData = (RelayProtocolData*)userData; - RelayMessageBind.Write(writer, 0, protocolData->ServerData.Nonce, protocolData->ServerData.ConnectionData.Value, protocolData->ServerData.HMAC); - - return true; - } - - static DataStreamWriter WriterForSendBuffer(int requestSize, ref NetworkInterfaceSendHandle sendHandle) - { - unsafe - { - if (requestSize <= sendHandle.capacity) - { - sendHandle.size = requestSize; - return new DataStreamWriter((byte*)sendHandle.data, sendHandle.size); - } - } - - return default; - } - } -} diff --git a/Runtime/Relay/RelayServerData.cs b/Runtime/Relay/RelayServerData.cs index 56d832b..67f83f9 100644 --- a/Runtime/Relay/RelayServerData.cs +++ b/Runtime/Relay/RelayServerData.cs @@ -1,59 +1,22 @@ using System; -using System.Diagnostics; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using Unity.Networking.Transport.Utilities; namespace Unity.Networking.Transport.Relay { - /// - /// Used by the Relay Protocol to describe how to connect to the Relay Service. - /// public unsafe struct RelayServerData { - /// - /// The endpoint of the Relay Server. - /// - public NetworkEndPoint Endpoint; - /// - /// The Nonce value used to stablish the connection with the Relay Server. - /// + public NetworkEndpoint Endpoint; public ushort Nonce; - /// - /// The data that describes the client presence on the Relay Server. - /// public RelayConnectionData ConnectionData; - /// - /// The connection data of the host client on the Relay Server. - /// public RelayConnectionData HostConnectionData; - /// - /// The unique identifier of the client on the Relay Server. - /// public RelayAllocationId AllocationId; - /// - /// The HMAC key for the connection. - /// public RelayHMACKey HMACKey; - /// - /// The computed HMAC. - /// 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. - /// - /// A byte that identifies the connection as secured. - /// public readonly byte IsSecure; - /// - /// Initializes a new instance of the class - /// - /// The endpoint - /// The nonce - /// The allocation id - /// The connection data - /// The host connection data - /// The key - /// The is secure - public RelayServerData(ref NetworkEndPoint endpoint, ushort nonce, RelayAllocationId allocationId, string connectionData, string hostConnectionData, string key, bool isSecure) + public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, RelayAllocationId allocationId, string connectionData, string hostConnectionData, string key, bool isSecure) { Endpoint = endpoint; AllocationId = allocationId; @@ -76,17 +39,7 @@ public RelayServerData(ref NetworkEndPoint endpoint, ushort nonce, RelayAllocati } } - /// - /// Initializes a new instance of the class - /// - /// The endpoint - /// The nonce - /// The allocation id - /// The connection data - /// The host connection data - /// The key - /// The is secure - public RelayServerData(ref NetworkEndPoint endpoint, ushort nonce, ref RelayAllocationId allocationId, + public RelayServerData(ref NetworkEndpoint endpoint, ushort nonce, ref RelayAllocationId allocationId, ref RelayConnectionData connectionData, ref RelayConnectionData hostConnectionData, ref RelayHMACKey key, bool isSecure) { Endpoint = endpoint; @@ -104,12 +57,9 @@ public RelayServerData(ref NetworkEndPoint endpoint, ushort nonce, ref RelayAllo } } - /// - /// Computes the new nonce, this must be called one time! - /// public void ComputeNewNonce() { - Nonce = (ushort)(new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp())).NextUInt(1, 0xefff); + Nonce = (ushort)(new Unity.Mathematics.Random((uint)TimerHelpers.GetTicks())).NextUInt(1, 0xefff); fixed(byte* hmacPtr = HMAC) { @@ -117,13 +67,6 @@ public void ComputeNewNonce() } } - /// - /// Computes the bind hmac using the specified result - /// - /// The result - /// The nonce - /// The connection data - /// The key private static void ComputeBindHMAC(byte* result, ushort nonce, ref RelayConnectionData connectionData, ref RelayHMACKey key) { var keyArray = new byte[64]; diff --git a/Runtime/SHA256.cs b/Runtime/SHA256.cs index 8ec280f..b685afd 100644 --- a/Runtime/SHA256.cs +++ b/Runtime/SHA256.cs @@ -2,9 +2,6 @@ namespace Unity.Networking.Transport { - /// - /// Utility class that provides the ability to generate a SHA256 Hash - /// internal static class SHA256 { internal unsafe struct SHA256State @@ -27,11 +24,6 @@ public static SHA256State Create() return result; } - /// - /// Updates data - /// - /// The data - /// The length public void Update(byte* data, int length) { var curBufferPos = count & 0x3F; @@ -48,10 +40,6 @@ public void Update(byte* data, int length) } } - /// - /// Finalize data and copy into dest - /// - /// The dest public void Final(byte* dest) { var lenInBits = count << 3; @@ -83,7 +71,7 @@ public void Final(byte* dest) *dest++ = (byte)state[i]; } } - + private void WriteByteBlock() { var data32 = stackalloc uint[16]; diff --git a/Runtime/SecureProtocol/SecureNetworkProtocol.cs b/Runtime/SecureProtocol/SecureNetworkProtocol.cs deleted file mode 100644 index 0321e03..0000000 --- a/Runtime/SecureProtocol/SecureNetworkProtocol.cs +++ /dev/null @@ -1,947 +0,0 @@ -#if ENABLE_MANAGED_UNITYTLS - -using System; -using System.Runtime.InteropServices; -using AOT; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Protocols; - -using Unity.TLS.LowLevel; -using UnityEngine; -using size_t = System.UIntPtr; - -namespace Unity.Networking.Transport.TLS -{ - internal struct SecureClientState - { - public unsafe Binding.unitytls_client* ClientPtr; - public unsafe Binding.unitytls_client_config* ClientConfig; - - // Only set (and used) by the client when sending the Connection Request message after - // successfully completing the handshake. - public SessionIdToken ReceiveToken; - - // During the handshake, tracks the last time UpdateSecureHandshakeState was called. - // Used to prune old half-open connections (handshake not completed). - public long LastHandshakeUpdate; - } - - struct SecureNetworkProtocolData - { - public UnsafeHashMap SecureClients; - public FixedString4096Bytes Pem; - public FixedString4096Bytes Rsa; - public FixedString4096Bytes RsaKey; - public FixedString32Bytes Hostname; - public uint Protocol; - public uint SSLReadTimeoutMs; - public uint SSLHandshakeTimeoutMax; - public uint SSLHandshakeTimeoutMin; - public uint ClientAuth; - public long LastUpdate; - public long LastHalfOpenPrune; - } - - internal struct SecureUserData - { - public IntPtr StreamData; - public NetworkSendInterface Interface; - public NetworkInterfaceEndPoint Remote; - public NetworkSendQueueHandle QueueHandle; - public int Size; - public int BytesProcessed; - } - - internal static class ManagedSecureFunctions - { - private const int UNITYTLS_ERR_SSL_WANT_READ = -0x6900; - private const int UNITYTLS_ERR_SSL_WANT_WRITE = -0x6880; - - private static Binding.unitytls_client_data_send_callback s_sendCallback; - private static Binding.unitytls_client_data_receive_callback s_recvCallback; - - private static bool IsInitialized; - - private struct ManagedSecureFunctionsKey {} - - internal static readonly SharedStatic> - s_SendCallback = SharedStatic> - .GetOrCreate, ManagedSecureFunctionsKey>(); - - internal static readonly SharedStatic> - s_RecvMethod = - SharedStatic> - .GetOrCreate, ManagedSecureFunctionsKey>(); - - internal static void Initialize() - { - if (IsInitialized) return; - IsInitialized = true; - - unsafe - { - s_sendCallback = SecureDataSendCallback; - s_recvCallback = SecureDataReceiveCallback; - - s_SendCallback.Data = new FunctionPointer(Marshal.GetFunctionPointerForDelegate(s_sendCallback)); - s_RecvMethod.Data = new FunctionPointer(Marshal.GetFunctionPointerForDelegate(s_recvCallback)); - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(Binding.unitytls_client_data_send_callback))] - static unsafe int SecureDataSendCallback( - IntPtr userData, - byte* data, - UIntPtr dataLen, - uint status) - { - var protocolData = (SecureUserData*)userData; - if (protocolData->Interface.BeginSendMessage.Ptr.Invoke(out var sendHandle, - protocolData->Interface.UserData, (int)dataLen.ToUInt32()) != 0) - { - return UNITYTLS_ERR_SSL_WANT_WRITE; - } - - sendHandle.size = (int)dataLen.ToUInt32(); - byte* packet = (byte*)sendHandle.data; - UnsafeUtility.MemCpy(packet, data, (long)dataLen.ToUInt64()); - - return protocolData->Interface.EndSendMessage.Ptr.Invoke(ref sendHandle, ref protocolData->Remote, - protocolData->Interface.UserData, ref protocolData->QueueHandle); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(Binding.unitytls_client_data_receive_callback))] - static unsafe int SecureDataReceiveCallback( - IntPtr userData, - byte* data, - UIntPtr dataLen, - uint status) - { - var protocolData = (SecureUserData*)userData; - var packet = (byte*)protocolData->StreamData; - if (packet == null || protocolData->Size <= 0) - { - return UNITYTLS_ERR_SSL_WANT_READ; - } - - // This is a case where we process an invalid record - // and the internal statemachine trys to read the next record - // and we don't have any data. Eventually one side will timeout - // and resend - if (protocolData->BytesProcessed != 0) - { - return UNITYTLS_ERR_SSL_WANT_READ; - } - - UnsafeUtility.MemCpy(data, packet, protocolData->Size); - protocolData->BytesProcessed = protocolData->Size; - return protocolData->Size; - } - } - - [BurstCompile] - internal unsafe struct SecureNetworkProtocol : INetworkProtocol - { - public IntPtr UserData; - - public static readonly SecureNetworkProtocolParameter DefaultParameters = new SecureNetworkProtocolParameter - { - Protocol = SecureTransportProtocol.DTLS, - SSLReadTimeoutMs = 0, - SSLHandshakeTimeoutMin = 1000, - SSLHandshakeTimeoutMax = 60000, - ClientAuthenticationPolicy = SecureClientAuthPolicy.Optional - }; - - private static void CreateSecureClient(uint role, SecureClientState* state) - { - var client = Binding.unitytls_client_create(role, state->ClientConfig); - state->ClientPtr = client; - } - - private static Binding.unitytls_client_config* GetSecureClientConfig(SecureNetworkProtocolData * protocolData) - { - var config = (Binding.unitytls_client_config*)UnsafeUtility.Malloc( - UnsafeUtility.SizeOf(), - UnsafeUtility.AlignOf(), Allocator.Persistent); - - *config = new Binding.unitytls_client_config(); - - Binding.unitytls_client_init_config(config); - - config->dataSendCB = ManagedSecureFunctions.s_SendCallback.Data.Value; - config->dataReceiveCB = ManagedSecureFunctions.s_RecvMethod.Data.Value; - config->logCallback = IntPtr.Zero; - - // Going to set this for None for now - config->clientAuth = Binding.UnityTLSRole_None; - - config->transportProtocol = protocolData->Protocol; - config->clientAuth = protocolData->ClientAuth; - - config->ssl_read_timeout_ms = protocolData->SSLReadTimeoutMs; - config->ssl_handshake_timeout_min = protocolData->SSLHandshakeTimeoutMin; - config->ssl_handshake_timeout_max = protocolData->SSLHandshakeTimeoutMax; - - return config; - } - - public void Initialize(NetworkSettings settings) - { - unsafe - { - ManagedSecureFunctions.Initialize(); - - // we need Secure Transport related configs because we need the user to pass int he keys? - var secureConfig = settings.GetSecureParameters(); - - //TODO: We need to validate that you have a config that makes sense for what you are trying to do - // should this be something we allow for expressing in the config? like which role you are? - -#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_WEBGL - // If we have baselib configs we need to make sure they are of proper size - if (settings.TryGet(out var baselibConfig)) - { - // TODO: We do not support fragmented messages at the moment :( - // and the largest packet that mbedTLS sends is 1800 which is the key - // exchange.. - if (baselibConfig.maximumPayloadSize < 2000) - { - UnityEngine.Debug.LogWarning( - "Secure Protocol Requires the payload size for the Baselib Interface to be at least 2000KB"); - } - } -#endif - - UserData = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), - UnsafeUtility.AlignOf(), Allocator.Persistent); - *(SecureNetworkProtocolData*)UserData = new SecureNetworkProtocolData - { - SecureClients = new UnsafeHashMap(1, Allocator.Persistent), - Rsa = secureConfig.Rsa, - RsaKey = secureConfig.RsaKey, - Pem = secureConfig.Pem, - Hostname = secureConfig.Hostname, - Protocol = (uint)secureConfig.Protocol, - SSLReadTimeoutMs = secureConfig.SSLReadTimeoutMs, - SSLHandshakeTimeoutMin = secureConfig.SSLHandshakeTimeoutMin, - SSLHandshakeTimeoutMax = secureConfig.SSLHandshakeTimeoutMax, - ClientAuth = (uint)secureConfig.ClientAuthenticationPolicy - }; - } - } - - public static void DisposeSecureClient(ref SecureClientState state) - { - if (state.ClientConfig->transportUserData.ToPointer() != null) - UnsafeUtility.Free(state.ClientConfig->transportUserData.ToPointer(), Allocator.Persistent); - - if (state.ClientConfig != null) - UnsafeUtility.Free((void*)state.ClientConfig, Allocator.Persistent); - - state.ClientConfig = null; - - if (state.ClientPtr != null) - Binding.unitytls_client_destroy(state.ClientPtr); - } - - public void Dispose() - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)UserData; - var keys = protocolData->SecureClients.GetKeyArray(Allocator.Temp); - for (int connectionIndex = 0; connectionIndex < keys.Length; ++connectionIndex) - { - var connection = protocolData->SecureClients[keys[connectionIndex]]; - - DisposeSecureClient(ref connection); - - protocolData->SecureClients.Remove(keys[connectionIndex]); - } - - if (UserData != default) - UnsafeUtility.Free(UserData.ToPointer(), Allocator.Persistent); - - UserData = default; - } - } - - bool TryExtractParameters(out T config, params INetworkParameter[] param) - { - for (var i = 0; i < param.Length; ++i) - { - if (param[i] is T) - { - config = (T)param[i]; - return true; - } - } - - config = default; - return false; - } - - public int Bind(INetworkInterface networkInterface, ref NetworkInterfaceEndPoint localEndPoint) - { - if (networkInterface.Bind(localEndPoint) != 0) - return -1; - - return 0; - } - - public int CreateConnectionAddress(INetworkInterface networkInterface, NetworkEndPoint remoteEndpoint, out NetworkInterfaceEndPoint remoteAddress) - { - remoteAddress = default; - return networkInterface.CreateInterfaceEndPoint(remoteEndpoint, out remoteAddress); - } - - public NetworkEndPoint GetRemoteEndPoint(INetworkInterface networkInterface, NetworkInterfaceEndPoint address) - { - return networkInterface.GetGenericEndPoint(address); - } - - public int Listen(INetworkInterface networkInterface) - { - return networkInterface.Listen(); - } - - public NetworkProtocol CreateProtocolInterface() - { - return new NetworkProtocol( - computePacketOverhead: new TransportFunctionPointer(ComputePacketOverhead), - processReceive: new TransportFunctionPointer(ProcessReceive), - processSend: new TransportFunctionPointer(ProcessSend), - processSendConnectionAccept: new TransportFunctionPointer(ProcessSendConnectionAccept), - connect: new TransportFunctionPointer(Connect), - disconnect: new TransportFunctionPointer(Disconnect), - processSendPing: new TransportFunctionPointer(ProcessSendPing), - processSendPong: new TransportFunctionPointer(ProcessSendPong), - update: new TransportFunctionPointer(Update), - needsUpdate: true, - userData: UserData, - maxHeaderSize: UdpCHeader.Length, - maxFooterSize: SessionIdToken.k_Length - ); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ComputePacketOverheadDelegate))] - public static int ComputePacketOverhead(ref NetworkDriver.Connection connection, out int dataOffset) - { - return UnityTransportProtocol.ComputePacketOverhead(ref connection, out dataOffset); - } - - public static bool ServerShouldStep(uint currentState) - { - // these are the initial states from the server ? - switch (currentState) - { - case Binding.UNITYTLS_SSL_HANDSHAKE_HELLO_REQUEST: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_HELLO: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_HELLO: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_CERTIFICATE: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_KEY_EXCHANGE: - case Binding.UNITYTLS_SSL_HANDSHAKE_CERTIFICATE_REQUEST: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_HELLO_DONE: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_CHANGE_CIPHER_SPEC: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_FINISHED: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_WRAPUP: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_OVER: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_FLUSH_BUFFERS: - return true; - } - - return false; - } - - private static bool ClientShouldStep(uint currentState) - { - // these are the initial states from the server ? - switch (currentState) - { - case Binding.UNITYTLS_SSL_HANDSHAKE_HELLO_REQUEST: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_HELLO: - return true; - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_HELLO: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_CERTIFICATE: - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_KEY_EXCHANGE: - case Binding.UNITYTLS_SSL_HANDSHAKE_CERTIFICATE_REQUEST: - return false; - case Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_HELLO_DONE: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_CERTIFICATE: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_KEY_EXCHANGE: - case Binding.UNITYTLS_SSL_HANDSHAKE_CERTIFICATE_VERIFY: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_CHANGE_CIPHER_SPEC: - case Binding.UNITYTLS_SSL_HANDSHAKE_CLIENT_FINISHED: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_WRAPUP: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_OVER: - case Binding.UNITYTLS_SSL_HANDSHAKE_HANDSHAKE_FLUSH_BUFFERS: - return true; - } - - return false; - } - - internal static void SetSecureUserData( - IntPtr inStream, - int size, - ref NetworkInterfaceEndPoint remote, - ref NetworkSendInterface networkSendInterface, - ref NetworkSendQueueHandle queueHandle, - SecureUserData* secureUserData) - { - secureUserData->Interface = networkSendInterface; - secureUserData->Remote = remote; - secureUserData->QueueHandle = queueHandle; - secureUserData->Size = size; - secureUserData->StreamData = inStream; - secureUserData->BytesProcessed = 0; - } - - private static bool CreateNewSecureClientState( - ref NetworkInterfaceEndPoint endpoint, - uint tlsRole, - SecureNetworkProtocolData* protocolData, - SessionIdToken receiveToken = default) - { - if (protocolData->SecureClients.TryAdd(endpoint, new SecureClientState())) - { - var secureClient = protocolData->SecureClients[endpoint]; - secureClient.ClientConfig = GetSecureClientConfig(protocolData); - secureClient.ReceiveToken = receiveToken; - - CreateSecureClient(tlsRole, &secureClient); - - IntPtr secureUserData = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), - UnsafeUtility.AlignOf(), Allocator.Persistent); - - *(SecureUserData*)secureUserData = new SecureUserData - { - Interface = default, - Remote = default, - QueueHandle = default, - StreamData = IntPtr.Zero, - Size = 0, - BytesProcessed = 0 - }; - - secureClient.ClientConfig->transportUserData = secureUserData; - - if (protocolData->Hostname != default) - { - secureClient.ClientConfig->hostname = protocolData->Hostname.GetUnsafePtr(); - } - else - { - secureClient.ClientConfig->hostname = null; - } - - if (protocolData->Pem != default) - { - secureClient.ClientConfig->caPEM = new Binding.unitytls_dataRef() - { - dataPtr = protocolData->Pem.GetUnsafePtr(), - dataLen = new UIntPtr((uint)protocolData->Pem.Length) - }; - } - else - { - secureClient.ClientConfig->caPEM = new Binding.unitytls_dataRef() - { - dataPtr = null, - dataLen = new UIntPtr(0) - }; - } - - if (protocolData->Rsa != default && protocolData->RsaKey != default) - { - secureClient.ClientConfig->serverPEM = new Binding.unitytls_dataRef() - { - dataPtr = protocolData->Rsa.GetUnsafePtr(), - dataLen = new UIntPtr((uint)protocolData->Rsa.Length) - }; - - secureClient.ClientConfig->privateKeyPEM = new Binding.unitytls_dataRef() - { - dataPtr = protocolData->RsaKey.GetUnsafePtr(), - dataLen = new UIntPtr((uint)protocolData->RsaKey.Length) - }; - } - else - { - secureClient.ClientConfig->serverPEM = new Binding.unitytls_dataRef() - { - dataPtr = null, - dataLen = new UIntPtr(0) - }; - - secureClient.ClientConfig->privateKeyPEM = new Binding.unitytls_dataRef() - { - dataPtr = null, - dataLen = new UIntPtr(0) - }; - } - - Binding.unitytls_client_init(secureClient.ClientPtr); - - protocolData->SecureClients[endpoint] = secureClient; - } - - return false; - } - - internal static uint SecureHandshakeStep(ref SecureClientState clientAgent) - { - // So now we need to check which role we are ? - var isServer = Binding.unitytls_client_get_role(clientAgent.ClientPtr) == Binding.UnityTLSRole_Server; - // we should do server things - bool shouldStep = true; - uint result = Binding.UNITYTLS_HANDSHAKE_STEP; - do - { - shouldStep = false; - result = Binding.unitytls_client_handshake( - clientAgent.ClientPtr); - - // this was a case where properly stepped handshake - if (result == Binding.UNITYTLS_HANDSHAKE_STEP) - { - uint currentState = Binding.unitytls_client_get_handshake_state(clientAgent.ClientPtr); - shouldStep = isServer ? ServerShouldStep(currentState) : ClientShouldStep(currentState); - } - } - while (shouldStep); - - return result; - } - - private unsafe static uint UpdateSecureHandshakeState( - SecureNetworkProtocolData* protocolData, ref NetworkInterfaceEndPoint endpoint) - { - var secureClient = protocolData->SecureClients[endpoint]; - - secureClient.LastHandshakeUpdate = protocolData->LastUpdate; - protocolData->SecureClients[endpoint] = secureClient; - - return SecureHandshakeStep(ref secureClient); - } - - private unsafe static void PruneHalfOpenConnections(SecureNetworkProtocolData* protocolData) - { - var endpoints = protocolData->SecureClients.GetKeyArray(Allocator.Temp); - bool pruned = false; - - for (int i = 0; i < endpoints.Length; i++) - { - var secureClient = protocolData->SecureClients[endpoints[i]]; - var state = Binding.unitytls_client_get_state(secureClient.ClientPtr); - - // After the maximum handshake timeout is a good time to prune half-open connections - // since at that point there is no progress possible on the handshake. By default - // it's also a very generous timeout (a minute). - // - // The check on LastHandshakeUpdate being greater than 0 is required in the case - // where a client hello is received before the very first update of the protocol. - // Unlikely to occur in practice (the client hello would have to come in right - // between the server's bind and its first update), but common in automated tests. - if (state == Binding.UnityTLSClientState_Handshake && - secureClient.LastHandshakeUpdate > 0 && - protocolData->LastUpdate - secureClient.LastHandshakeUpdate > protocolData->SSLHandshakeTimeoutMax) - { - DisposeSecureClient(ref secureClient); - protocolData->SecureClients.Remove(endpoints[i]); - - pruned = true; - } - } - - // It would be more useful to log each client that has been pruned (we could show the - // details of the client's endpoint), but a malicious actor could abuse that to spam our - // logs with many half-open connections. At worst here we'll only log once every second - // (the default value of SSLHandshakeTimeoutMin). - if (pruned) - { - Debug.LogError("Had to prune half-open connections (clients with unfinished TLS handshakes)."); - } - - endpoints.Dispose(); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessReceiveDelegate))] - public static void ProcessReceive(IntPtr stream, ref NetworkInterfaceEndPoint endpoint, int size, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData, - ref ProcessPacketCommand command) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - - // We assume this is a server if we need to create a new SecureClientState and the reason - // for this is because the client always sends the Connection Request message and we process that - // and then we check if we have heard from this client before and if not then we need to create one - // and its assume that client is in the server role and would be validating all incoming connections - CreateNewSecureClientState(ref endpoint, Binding.UnityTLSRole_Server, protocolData); - - var secureClient = protocolData->SecureClients[endpoint]; - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - - SetSecureUserData(stream, size, ref endpoint, ref sendInterface, ref queueHandle, secureUserData); - var clientState = Binding.unitytls_client_get_state(secureClient.ClientPtr); - uint handshakeResult = Binding.UNITYTLS_SUCCESS; - - // check and see if we are still in the handshake :D - if (clientState == Binding.UnityTLSClientState_Handshake - || clientState == Binding.UnityTLSClientState_Init) - { - bool shouldRunAgain = false; - do - { - handshakeResult = UpdateSecureHandshakeState(protocolData, ref endpoint); - clientState = Binding.unitytls_client_get_state(secureClient.ClientPtr); - shouldRunAgain = (size != 0 && secureUserData->BytesProcessed == 0 && clientState == Binding.UnityTLSClientState_Handshake); - } - while (shouldRunAgain); - - // If the handshake has completed and we're a client, immediately send the - // Connection Request message. This avoids having to wait for the driver to - // timeout and resend it (which could be a while). - var role = Binding.unitytls_client_get_role(secureClient.ClientPtr); - if (role == Binding.UnityTLSRole_Client && clientState == Binding.UnityTLSClientState_Messaging) - { - SendConnectionRequest( - secureClient.ReceiveToken, secureClient, ref endpoint, ref sendInterface, ref queueHandle); - } - - command.Type = ProcessPacketCommandType.Drop; - } - else if (clientState == Binding.UnityTLSClientState_Messaging) - { - var buffer = new NativeArray(NetworkParameterConstants.MTU, Allocator.Temp); - var bytesRead = new UIntPtr(); - var result = Binding.unitytls_client_read_data(secureClient.ClientPtr, - (byte*)buffer.GetUnsafePtr(), new UIntPtr(NetworkParameterConstants.MTU), - &bytesRead); - - if (result != Binding.UNITYTLS_SUCCESS) - { - // Don't log an error here. There are normal situations where we hit this - // situation. For example if we get a retransmitted handshake message after - // the handshake is completed (if the handshake minimum timeout is set too - // low for the network conditions, this can occur frequently). - command.Type = ProcessPacketCommandType.Drop; - return; - } - - // when we have a proper read we need to copy that data into the stream. It should be noted - // that this copy does change data we don't technically own. - UnsafeUtility.MemCpy((void*)stream, buffer.GetUnsafePtr(), bytesRead.ToUInt32()); - - UnityTransportProtocol.ProcessReceive(stream, - ref endpoint, - (int)bytesRead.ToUInt32(), - ref sendInterface, - ref queueHandle, - IntPtr.Zero, - ref command); - - if (command.Type == ProcessPacketCommandType.Disconnect) - { - // So we got a disconnect message we need to clean up the agent - DisposeSecureClient(ref secureClient); - protocolData->SecureClients.Remove(endpoint); - return; - } - } - - clientState = Binding.unitytls_client_get_state(secureClient.ClientPtr); - if (clientState == Binding.UnityTLSClientState_Fail) - { - // Failed client on server finish state means the handshake has failed, likely - // due to a certificate validation failure. - if (handshakeResult == Binding.UNITYTLS_SSL_HANDSHAKE_SERVER_FINISHED) - { - UnityEngine.Debug.LogError("Secure handshake failure (likely caused by certificate validation failure)."); - } - - command.Type = ProcessPacketCommandType.Drop; - - // we should cleanup the connection state ? - DisposeSecureClient(ref secureClient); - - protocolData->SecureClients.Remove(endpoint); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendDelegate))] - public static int ProcessSend(ref NetworkDriver.Connection connection, bool hasPipeline, ref NetworkSendInterface sendInterface, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - var protocolData = (SecureNetworkProtocolData*)userData; - - CreateNewSecureClientState(ref connection.Address, Binding.UnityTLSRole_Server, protocolData); - - var secureClient = protocolData->SecureClients[connection.Address]; - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - - SetSecureUserData(IntPtr.Zero, 0, ref connection.Address, ref sendInterface, ref queueHandle, secureUserData); - - UnityTransportProtocol.WriteSendMessageHeader(ref connection, hasPipeline, ref sendHandle, 0); - - // we need to free up the current handle before we call send because we could be at capacity and thus - // when we go and try to get a new handle it will fail. - var buffer = new NativeArray(sendHandle.size, Allocator.Temp); - UnsafeUtility.MemCpy(buffer.GetUnsafePtr(), (void*)sendHandle.data, sendHandle.size); - - // We end up having to abort this handle so we can free it up as DTLS will generate a - // new one based on the encrypted buffer size. - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - - var result = Binding.unitytls_client_send_data(secureClient.ClientPtr, (byte*)buffer.GetUnsafePtr(), new UIntPtr((uint)buffer.Length)); - - if (result != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Secure Send failed with result {result}"); - // Error is likely caused by a connection that's closed or not established yet. - return (int)Error.StatusCode.NetworkStateMismatch; - } - - return buffer.Length; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendConnectionAcceptDelegate))] - public static void ProcessSendConnectionAccept(ref NetworkDriver.Connection connection, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - var secureClient = protocolData->SecureClients[connection.Address]; - - var packet = new NativeArray(UdpCHeader.Length + SessionIdToken.k_Length, Allocator.Temp); - var size = WriteConnectionAcceptMessage(ref connection, (byte*)packet.GetUnsafePtr(), packet.Length); - - if (size < 0) - { - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet"); - return; - } - - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - SetSecureUserData(IntPtr.Zero, 0, ref connection.Address, ref sendInterface, ref queueHandle, secureUserData); - - var result = Binding.unitytls_client_send_data(secureClient.ClientPtr, (byte*)packet.GetUnsafePtr(), new UIntPtr((uint)packet.Length)); - if (result != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Secure Send failed with result {result}"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - internal static unsafe int WriteConnectionAcceptMessage(ref NetworkDriver.Connection connection, byte* packet, int capacity) - { - var size = UdpCHeader.Length; - - if (connection.DidReceiveData == 0) - size += SessionIdToken.k_Length; - - if (size > capacity) - { - UnityEngine.Debug.LogError("Failed to create a ConnectionAccept packet: size exceeds capacity"); - return -1; - } - - var header = (UdpCHeader*)packet; - //Avoid use of 'new' keyword for UdpCHeader now until Mono fix backport gets in; it ignores the StructLayout.Size parameter - //and allocates 16 bytes instead of 10 - header->Type = (byte)UdpCProtocol.ConnectionAccept; - header->SessionToken = connection.SendToken; - header->Flags = 0; - - if (connection.DidReceiveData == 0) - { - header->Flags |= UdpCHeader.HeaderFlags.HasConnectToken; - *(SessionIdToken*)(packet + UdpCHeader.Length) = connection.ReceiveToken; - } - - return size; - } - - private static unsafe void SendConnectionRequest(SessionIdToken token, SecureClientState secureClient, - ref NetworkInterfaceEndPoint address, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - var packet = new NativeArray(UdpCHeader.Length, Allocator.Temp); - var header = (UdpCHeader*)packet.GetUnsafePtr(); - header->Type = (byte)UdpCProtocol.ConnectionRequest; - header->SessionToken = token; - header->Flags = 0; - - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - SetSecureUserData(IntPtr.Zero, 0, ref address, ref sendInterface, ref queueHandle, secureUserData); - - var result = Binding.unitytls_client_send_data(secureClient.ClientPtr, - (byte*)packet.GetUnsafePtr(), new UIntPtr((uint)packet.Length)); - if (result != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError("We have failed to Send Encrypted SendConnectionRequest"); - } - } - - private static unsafe uint SendHeaderOnlyMessage(UdpCProtocol type, SessionIdToken token, SecureClientState secureClient, - ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - var packet = new NativeArray(UdpCHeader.Length, Allocator.Temp); - - var header = (UdpCHeader*)packet.GetUnsafePtr(); - header->Type = (byte)type; - header->SessionToken = token; - header->Flags = 0; - - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - SetSecureUserData(IntPtr.Zero, 0, ref connection.Address, ref sendInterface, ref queueHandle, secureUserData); - - return Binding.unitytls_client_send_data(secureClient.ClientPtr, (byte*)packet.GetUnsafePtr(), new UIntPtr((uint)packet.Length)); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ConnectDelegate))] - public static void Connect(ref NetworkDriver.Connection connection, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - CreateNewSecureClientState( - ref connection.Address, Binding.UnityTLSRole_Client, protocolData, connection.ReceiveToken); - - var secureClient = protocolData->SecureClients[connection.Address]; - - var secureUserData = (SecureUserData*)secureClient.ClientConfig->transportUserData; - SetSecureUserData(IntPtr.Zero, 0, ref connection.Address, ref sendInterface, ref queueHandle, secureUserData); - - var currentState = Binding.unitytls_client_get_state(secureClient.ClientPtr); - - if (currentState == Binding.UnityTLSClientState_Messaging) - { - // this is the case we are now with a proper handshake! - // we now need to send the proper connection request! - // FIXME: If using DTLS we should just make that handshake accept the connection - SendConnectionRequest( - connection.ReceiveToken, secureClient, ref connection.Address, ref sendInterface, ref queueHandle); - - return; - } - - var handshakeResult = UpdateSecureHandshakeState(protocolData, ref connection.Address); - currentState = Binding.unitytls_client_get_state(secureClient.ClientPtr); - if (currentState == Binding.UnityTLSClientState_Fail) - { - Debug.LogError($"Handshake failed with result {handshakeResult}"); - - // so we are in an error state which likely means the handshake failed in some - // way. We dispose of the connection state so when we attempt to connect again - // we can try again - DisposeSecureClient(ref secureClient); - - protocolData->SecureClients.Remove(connection.Address); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.DisconnectDelegate))] - public static void Disconnect(ref NetworkDriver.Connection connection, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - var secureClient = protocolData->SecureClients[connection.Address]; - - if (connection.State == NetworkConnection.State.Connected) - { - var type = UdpCProtocol.Disconnect; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, secureClient, ref connection, ref sendInterface, ref queueHandle); - if (res != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Failed to send secure Disconnect message (result: {res})"); - } - } - - // we should cleanup the connection state ? - DisposeSecureClient(ref secureClient); - - protocolData->SecureClients.Remove(connection.Address); - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPingDelegate))] - public static void ProcessSendPing(ref NetworkDriver.Connection connection, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - var secureClient = protocolData->SecureClients[connection.Address]; - - var type = UdpCProtocol.Ping; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, secureClient, ref connection, ref sendInterface, ref queueHandle); - if (res != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Failed to send secure Ping message (result: {res})"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPongDelegate))] - public static void ProcessSendPong(ref NetworkDriver.Connection connection, - ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - var secureClient = protocolData->SecureClients[connection.Address]; - - var type = UdpCProtocol.Pong; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, secureClient, ref connection, ref sendInterface, ref queueHandle); - if (res != Binding.UNITYTLS_SUCCESS) - { - Debug.LogError($"Failed to send secure Pong message (result: {res})"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.UpdateDelegate))] - public static void Update(long updateTime, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var protocolData = (SecureNetworkProtocolData*)userData; - protocolData->LastUpdate = updateTime; - - // No use pruning half-open connections more often than the handshake retry timeout. - if (updateTime - protocolData->LastHalfOpenPrune > protocolData->SSLHandshakeTimeoutMin) - { - PruneHalfOpenConnections(protocolData); - protocolData->LastHalfOpenPrune = updateTime; - } - } - } - } -} - -#endif diff --git a/Runtime/SessionIdToken.cs.meta b/Runtime/SessionIdToken.cs.meta deleted file mode 100644 index 3f47698..0000000 --- a/Runtime/SessionIdToken.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 898991870dd870b438ff83e21d321b6a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/TCPNetworkInterface.cs b/Runtime/TCPNetworkInterface.cs new file mode 100644 index 0000000..e40e8cc --- /dev/null +++ b/Runtime/TCPNetworkInterface.cs @@ -0,0 +1,600 @@ +#if !UNITY_WEBGL || UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Unity.Baselib.LowLevel; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; + +using ErrorState = Unity.Baselib.LowLevel.Binding.Baselib_ErrorState; +using ErrorCode = Unity.Baselib.LowLevel.Binding.Baselib_ErrorCode; + +namespace Unity.Networking.Transport +{ + using NetworkSocket = Binding.Baselib_Socket_Handle; + + /// + /// A TCP based network interface. + /// + /// + /// Different than this interface keeps a connection list and does not + /// provide message segmentation. It doesn't event have a concept of messages and simply packs incoming chunks of + /// data into one or more MTU-sized packets. This means packets put in the receive queue for the upper layer may + /// contain either multiple "messages" or even an incomplete "message". It still offers the same guarantees of TCP + /// in that data delivery is reliable and sequenced or else (in scenarios of unrecoverable data loss) the + /// connection will be closed. + /// + [BurstCompile] + internal struct TCPNetworkInterface : INetworkInterface + { + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void Warn(string msg) => UnityEngine.Debug.LogWarning(msg); + + static readonly NetworkSocket InvalidSocket = Binding.Baselib_Socket_Handle_Invalid; + + static bool IsValid(NetworkSocket socket) => socket.handle != default && socket.handle != InvalidSocket.handle; + + /// + /// This class tracks all baselib socket handles created so that they can be closed on domain reload. + /// Supposedly, this is cheaper than implementing the full Disposable Pattern in the Network Interface class (?) + /// + class AllSockets + { + private AllSockets() {} + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + private struct Socket + { + public NetworkSocket NetworkSocket; + } + + public static void Add(NetworkSocket socket) => instance.sockets.Add(new Socket { NetworkSocket = socket }); + public static void Remove(NetworkSocket socket) => instance.sockets.Remove(new Socket { NetworkSocket = socket }); + + private static readonly AllSockets instance = new(); + private readonly List sockets = new(); + + ~AllSockets() + { + foreach (var socket in sockets) + Binding.Baselib_Socket_Close(socket.NetworkSocket); + } + +#else + public static void Add(NetworkSocket socket) {} + public static void Remove(NetworkSocket socket) {} +#endif + } + + static class TCPSocket + { + public static unsafe NetworkSocket Listen(ref NetworkEndpoint localEndpoint, out ErrorState errorState) + { + var error = default(ErrorState); + var endpoint = localEndpoint; + var address = &endpoint.rawNetworkAddress; + var socket = Binding.Baselib_Socket_Create((Binding.Baselib_NetworkAddress_Family)address->family, Binding.Baselib_Socket_Protocol.TCP, &error); + if (error.code == ErrorCode.Success) + { + Binding.Baselib_Socket_Bind(socket, address, Binding.Baselib_NetworkAddress_AddressReuse.Allow, &error); + if (error.code == ErrorCode.Success) + { + // Get the end point bound not so much for the address (a wildcard address should remain unchanged) but + // for the port, although it's pretty rare to have a server binding with ANY_PORT (0) for pratical reasons. + Binding.Baselib_Socket_GetAddress(socket, address, &error); + Binding.Baselib_Socket_TCP_Listen(socket, &error); + } + + if (error.code != ErrorCode.Success) + { + Binding.Baselib_Socket_Close(socket); + socket = InvalidSocket; + } + } + + localEndpoint = endpoint; + errorState = error; + return socket; + } + + public static unsafe NetworkSocket Accept(NetworkSocket listenSocket, out NetworkEndpoint localEndpoint) + { + var error = default(ErrorState); + var acceptedSocket = Binding.Baselib_Socket_TCP_Accept(listenSocket, &error); + localEndpoint = default; + if (IsValid(acceptedSocket) && error.code == ErrorCode.Success) + { + var address = default(NetworkEndpoint); + Binding.Baselib_Socket_GetAddress(acceptedSocket, &address.rawNetworkAddress, &error); + localEndpoint = address; + if (error.code != ErrorCode.Success) + { + Warn($"Failed to get local endpoint for incoming connection: {error.code}"); + Binding.Baselib_Socket_Close(acceptedSocket); + acceptedSocket = InvalidSocket; + } + } + + return acceptedSocket; + } + + // Note that connection in baselib is async. You have to check the completion using IsConnected. + public static unsafe NetworkSocket Connect(NetworkEndpoint remoteEndoint) + { + var address = &remoteEndoint.rawNetworkAddress; + var error = default(ErrorState); + var socket = Binding.Baselib_Socket_Create((Binding.Baselib_NetworkAddress_Family)address->family, Binding.Baselib_Socket_Protocol.TCP, &error); + if (error.code == ErrorCode.Success) + { + Binding.Baselib_Socket_TCP_Connect(socket, address, Binding.Baselib_NetworkAddress_AddressReuse.Allow, &error); + } + + if (error.code != ErrorCode.Success) + { + Binding.Baselib_Socket_Close(socket); + socket = InvalidSocket; + } + + return socket; + } + + public static unsafe bool IsConnectionReady(NetworkSocket socket, out ErrorState errorState) + { + var error = default(ErrorState); + var sockError = default(ErrorState); + var sockFd = new Binding.Baselib_Socket_PollFd + { + handle = socket, + requestedEvents = Binding.Baselib_Socket_PollEvents.Connected, + errorState = &sockError + }; + + Binding.Baselib_Socket_Poll(&sockFd, 1, 0, &error); + errorState = error; + if (error.code == ErrorCode.Success) + { + if (sockFd.errorState->code != ErrorCode.Success) + { + errorState = *sockFd.errorState; + return false; + } + + return (sockFd.resultEvents & Binding.Baselib_Socket_PollEvents.Connected) != 0; + } + + return false; + } + + public static void Close(NetworkSocket socket) + { + Binding.Baselib_Socket_Close(socket); + } + + public static unsafe int Send(NetworkSocket socket, byte* data, int length, out ErrorState errorState) + { + var nbytes = 0; + var error = default(ErrorState); + + if (length > 0) + nbytes = (int)Binding.Baselib_Socket_TCP_Send(socket, (IntPtr)data, (uint)length, &error); + + errorState = error; + return nbytes; + } + + public static unsafe int Receive(NetworkSocket socket, byte* data, int capacity, out ErrorState errorState) + { + var nbytes = 0; + var error = default(ErrorState); + + if (capacity > 0) + nbytes = (int)Binding.Baselib_Socket_TCP_Recv(socket, (IntPtr)data, (uint)capacity, &error); + + errorState = error; + return nbytes; + } + } + + unsafe struct InternalData + { + public NetworkSocket ListenSocket; // the listen socket for servers (not used by clients) + public NetworkEndpoint ListenEndpoint; // endpoint bound by the listen socket + public int ConnectTimeoutMS; // maximum time to wait for a connection to complete + public int MaxConnectAttempts; // maximum number of connect retries + } + + unsafe struct ConnectionData + { + public NetworkSocket Socket; + + public long ConnectTime; // Connect start time + public long LastConnectAttemptTime; // Time of the last attempt + public int LastConnectAttempt; // Number of attempts so far + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private NativeList m_SocketsPendingAdd; + private NativeList m_SocketsPendingRemove; +#endif + + private NativeReference m_InternalData; + + // Maps a connection id from the connection list to its connection data. + private ConnectionDataMap m_ConnectionMap; + + // List of connection information carried over to the layer above + private ConnectionList m_ConnectionList; + + internal ConnectionList CreateConnectionList() + { + m_ConnectionList = ConnectionList.Create(); + return m_ConnectionList; + } + + /// + /// Returns the local endpoint bound to the listen socket. + /// + /// NetworkEndpoint + public unsafe NetworkEndpoint LocalEndpoint => m_InternalData.Value.ListenEndpoint; + + public bool IsCreated => m_InternalData.IsCreated; + + /// + /// Initializes a instance of the UDPNetworkInterface struct. + /// + public unsafe int Initialize(ref NetworkSettings settings, ref int packetPadding) + { + // TODO: We might at some point want to apply receiveQueueCapacity to SO_RECVBUF and sendQueueCapacity to SO_SENDBUF + var networkConfiguration = settings.GetNetworkConfigParameters(); + + var state = new InternalData + { + ListenEndpoint = NetworkEndpoint.AnyIpv4, + ListenSocket = InvalidSocket, + ConnectTimeoutMS = Math.Max(0, networkConfiguration.connectTimeoutMS), + MaxConnectAttempts = Math.Max(1, networkConfiguration.maxConnectAttempts), + }; + + m_InternalData = new NativeReference(state, Allocator.Persistent); + m_ConnectionMap = new ConnectionDataMap(1, default, Allocator.Persistent); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_SocketsPendingAdd = new NativeList(Allocator.Persistent); + m_SocketsPendingRemove = new NativeList(Allocator.Persistent); +#endif + return 0; + } + + // TODO: We may want to allow binding clients as well in the future. Granted, it's normally pretty useless, but + // it would harmonize the behavior with the UDP interface, where we allow binding clients. + + /// + /// Binds to the local endpoint passed. This is only applicable for a listening server. Outgoing connections + /// do not have to bind. + /// + /// A valid ipv4 or ipv6 address + /// int + public unsafe int Bind(NetworkEndpoint endpoint) + { + var state = m_InternalData.Value; + state.ListenEndpoint = endpoint; + m_InternalData.Value = state; + + return 0; + } + + public unsafe int Listen() + { + var state = m_InternalData.Value; + state.ListenSocket = TCPSocket.Listen(ref state.ListenEndpoint, out var error); + if (error.code != ErrorCode.Success) + return (int)error.code == -1 ? -1 : -(int)error.code; + + AllSockets.Add(state.ListenSocket); + m_InternalData.Value = state; + return 0; + } + + public void Dispose() + { + var socket = m_InternalData.Value.ListenSocket; + if (IsValid(socket)) + { + TCPSocket.Close(socket); + AllSockets.Remove(socket); + } + + m_InternalData.Dispose(); + + for (int i = 0; i < m_ConnectionMap.Length; ++i) + { + socket = m_ConnectionMap.DataAt(i).Socket; + if (IsValid(socket)) + TCPSocket.Close(socket); + + AllSockets.Remove(socket); + } + + + m_ConnectionMap.Dispose(); + m_ConnectionList.Dispose(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_SocketsPendingAdd.Dispose(); + m_SocketsPendingRemove.Dispose(); +#endif + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Remove closed sockets from the list of sockets to be closed in a domain reload. + // This is here because we can't use managed AllSockets to track sockets from within burst compiled jobs. + for (int i = 0; i < m_SocketsPendingRemove.Length; i++) + AllSockets.Remove(m_SocketsPendingRemove[i]); + m_SocketsPendingRemove.Clear(); + + // Add connecting sockets to the list of sockets to be closed in a domain reload. + // This is here because we can't use managed AllSockets to track sockets from within burst compiled jobs. + for (int i = 0; i < m_SocketsPendingAdd.Length; i++) + AllSockets.Add(m_SocketsPendingAdd[i]); + m_SocketsPendingAdd.Clear(); +#endif + + return new ReceiveJob + { + ReceiveQueue = arguments.ReceiveQueue, + InternalData = m_InternalData, + ConnectionList = m_ConnectionList, + ConnectionMap = m_ConnectionMap, +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SocketsPendingAdd = m_SocketsPendingAdd, + SocketsPendingRemove = m_SocketsPendingRemove, + +#endif + Time = arguments.Time, + }.Schedule(dep); + } + + [BurstCompile] + struct ReceiveJob : IJob + { + public PacketsQueue ReceiveQueue; + public NativeReference InternalData; + public ConnectionList ConnectionList; + + // The job system doesn't like that ConnectionData stores an IntPtr as the socket handle. This problem also + // happens in UDPNetworkInterface which is also forced to use [NativeDisableUnsafePtrRestriction]. + // Without the decorator the job system throws the exception: + // "ReceiveJob.ConnectionMap.m_DefaultDataValue.Socket.handle uses unsafe Pointers which is not allowed. + // Unsafe Pointers can lead to crashes and no safety against race conditions can be provided.\nIf you + // really need to use unsafe pointers, you can disable this..." + [NativeDisableUnsafePtrRestriction] + public ConnectionDataMap ConnectionMap; + + public long Time; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public NativeList SocketsPendingRemove; + public NativeList SocketsPendingAdd; + + private void AllSocketsDeferredAdd(NetworkSocket socket) => SocketsPendingAdd.Add(socket); + private void AllSocketsDeferredRemove(NetworkSocket socket) => SocketsPendingRemove.Add(socket); +#else + private void AllSocketsDeferredAdd(NetworkSocket socket) {} + private void AllSocketsDeferredRemove(NetworkSocket socket) {} +#endif + + private void Abort(ref ConnectionId connectionId, ref ConnectionData connectionData, Error.DisconnectReason reason = default) + { + ConnectionList.FinishDisconnecting(ref connectionId, reason); + ConnectionMap.ClearData(ref connectionId); + TCPSocket.Close(connectionData.Socket); + AllSocketsDeferredRemove(connectionData.Socket); + } + + public unsafe void Execute() + { + // Accept new connections. + if (IsValid(InternalData.Value.ListenSocket)) + { + var acceptedSocket = TCPSocket.Accept(InternalData.Value.ListenSocket, out var localEndpoint); + if (IsValid(acceptedSocket)) + { + AllSocketsDeferredAdd(acceptedSocket); + + var connectionId = ConnectionList.StartConnecting(ref localEndpoint); + ConnectionList.FinishConnectingFromRemote(ref connectionId); + ConnectionMap[connectionId] = new ConnectionData + { + Socket = acceptedSocket + }; + } + } + + // Update each connection from the connection list + var count = ConnectionList.Count; + for (int i = 0; i < count; i++) + { + var connectionId = ConnectionList.ConnectionAt(i); + var connectionState = ConnectionList.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected) + continue; + + var connectionData = ConnectionMap[connectionId]; + + // Detect if the upper layer is requesting to connect. + if (connectionState == NetworkConnection.State.Connecting) + { + // Initialize ConnectTime. The time here is a signed 64bit and we're never going to run at time + // 0 so if the connection has ConnectTime == 0 it's the creation of this connection data and + // just have to initialize the ConnectTime in a way to trigger the first connection try. + if (connectionData.ConnectTime == 0) + { + connectionData.ConnectTime = Time; + connectionData.LastConnectAttemptTime = Math.Max(0, Time - InternalData.Value.ConnectTimeoutMS); + } + + // Disconnect if maximum connection attempts reached + if (connectionData.LastConnectAttempt >= InternalData.Value.MaxConnectAttempts) + { + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.MaxConnectionAttempts); + continue; + } + + // Check if it's time to retry connect + if (Time - connectionData.LastConnectAttemptTime >= InternalData.Value.ConnectTimeoutMS) + { + var remoteEndpoint = ConnectionList.GetConnectionEndpoint(connectionId); + if (!IsValid(connectionData.Socket)) + { + connectionData.Socket = TCPSocket.Connect(remoteEndpoint); + if (IsValid(connectionData.Socket)) + AllSocketsDeferredAdd(connectionData.Socket); + } + + connectionData.LastConnectAttempt++; + connectionData.LastConnectAttemptTime = Time; + + if (IsValid(connectionData.Socket)) + { + if (TCPSocket.IsConnectionReady(connectionData.Socket, out var readyError)) + { + ConnectionList.FinishConnectingFromLocal(ref connectionId); + } + else if (readyError.code != ErrorCode.Success) + { + // If something went wrong trying to complete the connection just close this socket + // and in the next attempt we'll create a new one. + TCPSocket.Close(connectionData.Socket); + AllSocketsDeferredRemove(connectionData.Socket); + connectionData.Socket = InvalidSocket; + } + } + } + + ConnectionMap[connectionId] = connectionData; + continue; + } + + // Detect if the upper layer is requesting to disconnect. + if (connectionState == NetworkConnection.State.Disconnecting) + { + Abort(ref connectionId, ref connectionData); + continue; + } + + var endpoint = ConnectionList.GetConnectionEndpoint(connectionId); + ErrorState error = default; + // Read data from the connection if we can. Receive should return chunks of up to MTU. + // Close the connection in case of a receive error. + while (true) + { + // No need to disconnect in case the receive queue becomes full just let the TCP socket buffer + // the incoming data. + if (!ReceiveQueue.EnqueuePacket(out var packetProcessor)) + break; + + packetProcessor.ConnectionRef = connectionId; + packetProcessor.EndpointRef = endpoint; + var nbytes = TCPSocket.Receive(connectionData.Socket, (byte*)packetProcessor.GetUnsafePayloadPtr(), packetProcessor.BytesAvailableAtEnd, out error); + if (error.code != ErrorCode.Success || nbytes <= 0) + { + packetProcessor.Drop(); + break; + } + packetProcessor.SetUnsafeMetadata(nbytes); + } + + if (error.code != ErrorCode.Success) + { + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.Timeout); + break; + } + + // Update the connection data + ConnectionMap[connectionId] = connectionData; + } + } + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + { + return new SendJob + { + SendQueue = arguments.SendQueue, + InternalData = m_InternalData, + ConnectionMap = m_ConnectionMap, + ConnectionList = m_ConnectionList, +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SocketsPendingRemove = m_SocketsPendingRemove, +#endif + }.Schedule(dep); + } + + [BurstCompile] + unsafe struct SendJob : IJob + { + public PacketsQueue SendQueue; + + public NativeReference InternalData; + + public ConnectionList ConnectionList; + + // See the comment in ReceiveJob + [NativeDisableUnsafePtrRestriction] + public ConnectionDataMap ConnectionMap; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public NativeList SocketsPendingRemove; + + private void AllSocketsDeferredRemove(NetworkSocket socket) => SocketsPendingRemove.Add(socket); +#else + private void AllSocketsDeferredRemove(NetworkSocket socket) {} +#endif + + public void Execute() + { + // Each packet is sent individually. The connection is aborted if a packet cannot be transmiited + // entirely. + for (int i = 0; i < SendQueue.Count; i++) + { + var packetProcessor = SendQueue[i]; + if (packetProcessor.Length == 0) + continue; + + var connectionId = packetProcessor.ConnectionRef; + var connectionState = ConnectionList.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected) + continue; + + var connectionData = ConnectionMap[connectionId]; + + var nbytes = TCPSocket.Send(connectionData.Socket, (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset, packetProcessor.Length, out var error); + if (error.code != ErrorCode.Success || nbytes != packetProcessor.Length) + { + Warn($"Send buffer overflow trying to send data to {ConnectionList.GetConnectionEndpoint(connectionId)}"); + + ConnectionList.StartDisconnecting(ref connectionId); + ConnectionList.FinishDisconnecting(ref connectionId, Error.DisconnectReason.Timeout); + ConnectionMap.ClearData(ref connectionId); + TCPSocket.Close(connectionData.Socket); + AllSocketsDeferredRemove(connectionData.Socket); + continue; + } + + ConnectionMap[connectionId] = connectionData; + } + } + } + } +} +#endif diff --git a/Runtime/TCPNetworkInterface.cs.meta b/Runtime/TCPNetworkInterface.cs.meta new file mode 100644 index 0000000..47ec7d4 --- /dev/null +++ b/Runtime/TCPNetworkInterface.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4073a1aa75cbd76408bebb1375ca214b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SecureProtocol.meta b/Runtime/TLS.meta similarity index 77% rename from Runtime/SecureProtocol.meta rename to Runtime/TLS.meta index aa263d1..3c97c49 100644 --- a/Runtime/SecureProtocol.meta +++ b/Runtime/TLS.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f706e76170ba79d408b2d83cc56a1acd +guid: 8d83f1c3256b0034dac6785ecf16b8bd folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/TLS/DTLSUtilities.cs b/Runtime/TLS/DTLSUtilities.cs new file mode 100644 index 0000000..f0b11d0 --- /dev/null +++ b/Runtime/TLS/DTLSUtilities.cs @@ -0,0 +1,48 @@ +#if ENABLE_MANAGED_UNITYTLS + +using System.Runtime.CompilerServices; + +namespace Unity.Networking.Transport.TLS +{ + /// Utility functions used by the DTLS layer. + internal unsafe static class DTLSUtilities + { + /// Check if a DTLS message is a Client Hello message. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsClientHello(ref PacketProcessor packetProcessor) + { + // A DTLS message is a client hello if it's content type (offset 0 in the header) has + // value 0x16 and if the handshake type (offset 13 in the header) has value 0x01. + // Furthermore, any valid client hello will be at least 25 bytes long. + // + // Relying on DTLS header details like that isn't really optimal. Ideally we'd have our + // DTLS library expose something to check if a message is a handshake message (and which + // type of handshake message it is), but until then we rely on this hack. + + var ptr = (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset; + var size = packetProcessor.Length; + + return size >= 25 && ptr[0] == (byte)0x16 && ptr[13] == (byte)0x01; + } + + /// Check if a DTLS message is a Server Hello message. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsServerHello(ref PacketProcessor packetProcessor) + { + // A DTLS message is a server hello if it's content type (offset 0 in the header) has + // value 0x16 and if the handshake type (offset 13 in the header) has value 0x02. + // Furthermore, any valid server hello will be at least 25 bytes long. + // + // Relying on DTLS header details like that isn't really optimal. Ideally we'd have our + // DTLS library expose something to check if a message is a handshake message (and which + // type of handshake message it is), but until then we rely on this hack. + + var ptr = (byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset; + var size = packetProcessor.Length; + + return size >= 25 && ptr[0] == (byte)0x16 && ptr[13] == (byte)0x02; + } + } +} + +#endif \ No newline at end of file diff --git a/Runtime/TLS/DTLSUtilities.cs.meta b/Runtime/TLS/DTLSUtilities.cs.meta new file mode 100644 index 0000000..646f734 --- /dev/null +++ b/Runtime/TLS/DTLSUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b81d44ed761c174abe25fd6f912a925 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SecureProtocol/SecureParameters.cs b/Runtime/TLS/SecureParameters.cs similarity index 50% rename from Runtime/SecureProtocol/SecureParameters.cs rename to Runtime/TLS/SecureParameters.cs index d0c7dcf..f3eff22 100644 --- a/Runtime/SecureProtocol/SecureParameters.cs +++ b/Runtime/TLS/SecureParameters.cs @@ -6,15 +6,6 @@ namespace Unity.Networking.Transport.TLS { - /// Secure transport protocols. - public enum SecureTransportProtocol : uint - { - /// Standard TLS implementation for TCP connections. - TLS = 0, - /// Standard TLS implementation for UDP connections. - DTLS = 1, - } - /// Client authentication policies (server only). public enum SecureClientAuthPolicy : uint { @@ -38,37 +29,55 @@ public struct SecureNetworkProtocolParameter : INetworkParameter /// Server/client private key (PEM format). public FixedString4096Bytes RsaKey; /// Server/client certificate's common name. - public FixedString32Bytes Hostname; - /// Protocol to which the settings will apply. - public SecureTransportProtocol Protocol; + public FixedString512Bytes Hostname; /// Client authentication policy (server only, defaults to optional). public SecureClientAuthPolicy ClientAuthenticationPolicy; - /// Timeout in milliseconds for secure reads. - public uint SSLReadTimeoutMs; - /// Maximum secure handshake timeout (milliseconds, defaults to 60000). - public uint SSLHandshakeTimeoutMax; - /// Minimum secure handshake timeout (milliseconds, defaults to 1000). - public uint SSLHandshakeTimeoutMin; public bool Validate() => true; } public static class SecureParameterExtensions { + /// Set client security parameters (for WebSocket usage). + /// Hostname of the server to connect to. + public static ref NetworkSettings WithSecureClientParameters( + ref this NetworkSettings settings, + ref FixedString512Bytes serverName) + { + var parameter = new SecureNetworkProtocolParameter + { + Pem = default, + Rsa = default, + RsaKey = default, + Hostname = serverName, + ClientAuthenticationPolicy = SecureClientAuthPolicy.None, + }; + + settings.AddRawParameterStruct(ref parameter); + + return ref settings; + } + + /// Set client security parameters (for WebSocket usage). + /// Hostname of the server to connect to. + public static ref NetworkSettings WithSecureClientParameters( + ref this NetworkSettings settings, + string serverName) + { + var fixedServerName = new FixedString512Bytes(serverName); + + settings.WithSecureClientParameters(ref fixedServerName); + + return ref settings; + } + /// Set client security parameters (server authentication only). /// CA certificate that signed the server's certificate (PEM format). /// Common name (CN) in the server certificate. - /// Secure read timeout (in milliseconds). - /// Maximum handshake timeout (in milliseconds). - /// Minimum handshake timeout (in milliseconds). public static ref NetworkSettings WithSecureClientParameters( ref this NetworkSettings settings, ref FixedString4096Bytes caCertificate, - ref FixedString32Bytes serverName, - uint readTimeout = 0, - uint handshakeTimeoutMax = 60000, - uint handshakeTimeoutMin = 1000 - ) + ref FixedString512Bytes serverName) { var parameter = new SecureNetworkProtocolParameter { @@ -76,11 +85,7 @@ public static ref NetworkSettings WithSecureClientParameters( Rsa = default, RsaKey = default, Hostname = serverName, - Protocol = SecureTransportProtocol.DTLS, ClientAuthenticationPolicy = SecureClientAuthPolicy.None, - SSLReadTimeoutMs = readTimeout, - SSLHandshakeTimeoutMax = handshakeTimeoutMax, - SSLHandshakeTimeoutMin = handshakeTimeoutMin, }; settings.AddRawParameterStruct(ref parameter); @@ -88,24 +93,33 @@ public static ref NetworkSettings WithSecureClientParameters( return ref settings; } + /// Set client security parameters (server authentication only). + /// CA certificate that signed the server's certificate (PEM format). + /// Common name (CN) in the server certificate. + public static ref NetworkSettings WithSecureClientParameters( + ref this NetworkSettings settings, + string caCertificate, + string serverName) + { + var fixedCaCertificate = new FixedString4096Bytes(caCertificate); + var fixedServerName = new FixedString512Bytes(serverName); + + settings.WithSecureClientParameters(ref fixedCaCertificate, ref fixedServerName); + + return ref settings; + } + /// Set client security parameters (for client authentication). /// Client's certificate (PEM format). /// Client's private key (PEM format). /// CA certificate that signed the server's certificate (PEM format). /// Common name (CN) in the server certificate. - /// Secure read timeout (in milliseconds). - /// Maximum handshake timeout (in milliseconds). - /// Minimum handshake timeout (in milliseconds). public static ref NetworkSettings WithSecureClientParameters( ref this NetworkSettings settings, ref FixedString4096Bytes certificate, ref FixedString4096Bytes privateKey, ref FixedString4096Bytes caCertificate, - ref FixedString32Bytes serverName, - uint readTimeout = 0, - uint handshakeTimeoutMax = 60000, - uint handshakeTimeoutMin = 1000 - ) + ref FixedString512Bytes serverName) { var parameter = new SecureNetworkProtocolParameter { @@ -113,11 +127,7 @@ public static ref NetworkSettings WithSecureClientParameters( Rsa = certificate, RsaKey = privateKey, Hostname = serverName, - Protocol = SecureTransportProtocol.DTLS, ClientAuthenticationPolicy = SecureClientAuthPolicy.None, - SSLReadTimeoutMs = readTimeout, - SSLHandshakeTimeoutMax = handshakeTimeoutMax, - SSLHandshakeTimeoutMin = handshakeTimeoutMin, }; settings.AddRawParameterStruct(ref parameter); @@ -125,20 +135,39 @@ public static ref NetworkSettings WithSecureClientParameters( return ref settings; } + /// Set client security parameters (for client authentication). + /// Client's certificate (PEM format). + /// Client's private key (PEM format). + /// CA certificate that signed the server's certificate (PEM format). + /// Common name (CN) in the server certificate. + public static ref NetworkSettings WithSecureClientParameters( + ref this NetworkSettings settings, + string certificate, + string privateKey, + string caCertificate, + string serverName) + { + var fixedCertificate = new FixedString4096Bytes(certificate); + var fixedPrivateKey = new FixedString4096Bytes(privateKey); + var fixedCaCertificate = new FixedString4096Bytes(caCertificate); + var fixedServerName = new FixedString512Bytes(serverName); + + settings.WithSecureClientParameters( + ref fixedCertificate, + ref fixedPrivateKey, + ref fixedCaCertificate, + ref fixedServerName); + + return ref settings; + } + /// Set server security parameters (server authentication only). /// Server's certificate chain (PEM format). /// Server's private key (PEM format). - /// Secure read timeout (in milliseconds). - /// Maximum handshake timeout (in milliseconds). - /// Minimum handshake timeout (in milliseconds). public static ref NetworkSettings WithSecureServerParameters( ref this NetworkSettings settings, ref FixedString4096Bytes certificate, - ref FixedString4096Bytes privateKey, - uint readTimeout = 0, - uint handshakeTimeoutMax = 60000, - uint handshakeTimeoutMin = 1000 - ) + ref FixedString4096Bytes privateKey) { var parameter = new SecureNetworkProtocolParameter { @@ -146,11 +175,7 @@ public static ref NetworkSettings WithSecureServerParameters( Rsa = certificate, RsaKey = privateKey, Hostname = default, - Protocol = SecureTransportProtocol.DTLS, ClientAuthenticationPolicy = SecureClientAuthPolicy.None, - SSLReadTimeoutMs = readTimeout, - SSLHandshakeTimeoutMax = handshakeTimeoutMax, - SSLHandshakeTimeoutMin = handshakeTimeoutMin, }; settings.AddRawParameterStruct(ref parameter); @@ -158,26 +183,35 @@ public static ref NetworkSettings WithSecureServerParameters( return ref settings; } + /// Set server security parameters (server authentication only). + /// Server's certificate chain (PEM format). + /// Server's private key (PEM format). + public static ref NetworkSettings WithSecureServerParameters( + ref this NetworkSettings settings, + string certificate, + string privateKey) + { + var fixedCertificate = new FixedString4096Bytes(certificate); + var fixedPrivateKey = new FixedString4096Bytes(privateKey); + + settings.WithSecureServerParameters(ref fixedCertificate, ref fixedPrivateKey); + + return ref settings; + } + /// Set server security parameters (for client authentication). /// Server's certificate chain (PEM format). /// Server's private key (PEM format). /// CA certificate that signed the client certificates (PEM format). /// Common name (CN) in the client certificates. /// Client authentication policy. - /// Secure read timeout (in milliseconds). - /// Maximum handshake timeout (in milliseconds). - /// Minimum handshake timeout (in milliseconds). public static ref NetworkSettings WithSecureServerParameters( ref this NetworkSettings settings, ref FixedString4096Bytes certificate, ref FixedString4096Bytes privateKey, ref FixedString4096Bytes caCertificate, - ref FixedString32Bytes clientName, - SecureClientAuthPolicy clientAuthenticationPolicy = SecureClientAuthPolicy.Required, - uint readTimeout = 0, - uint handshakeTimeoutMax = 60000, - uint handshakeTimeoutMin = 1000 - ) + ref FixedString512Bytes clientName, + SecureClientAuthPolicy clientAuthenticationPolicy = SecureClientAuthPolicy.Required) { var parameter = new SecureNetworkProtocolParameter { @@ -185,11 +219,7 @@ public static ref NetworkSettings WithSecureServerParameters( Rsa = certificate, RsaKey = privateKey, Hostname = clientName, - Protocol = SecureTransportProtocol.DTLS, ClientAuthenticationPolicy = clientAuthenticationPolicy, - SSLReadTimeoutMs = readTimeout, - SSLHandshakeTimeoutMax = handshakeTimeoutMax, - SSLHandshakeTimeoutMin = handshakeTimeoutMin, }; settings.AddRawParameterStruct(ref parameter); @@ -197,64 +227,31 @@ public static ref NetworkSettings WithSecureServerParameters( return ref settings; } - [Obsolete("Use WithSecureClientParameters or WithSecureServerParameters instead.")] - public static ref NetworkSettings WithSecureParameters( - ref this NetworkSettings settings, - ref FixedString4096Bytes pem, - ref FixedString32Bytes hostname, - SecureTransportProtocol protocol = SecureTransportProtocol.DTLS, - SecureClientAuthPolicy clientAuthenticationPolicy = SecureClientAuthPolicy.Optional, - uint sslReadTimeoutMs = 0, - uint sslHandshakeTimeoutMax = 60000, - uint sslHandshakeTimeoutMin = 1000 - ) - { - var parameter = new SecureNetworkProtocolParameter - { - Pem = pem, - Rsa = default, - RsaKey = default, - Hostname = hostname, - Protocol = protocol, - ClientAuthenticationPolicy = clientAuthenticationPolicy, - SSLReadTimeoutMs = sslReadTimeoutMs, - SSLHandshakeTimeoutMax = sslHandshakeTimeoutMax, - SSLHandshakeTimeoutMin = sslHandshakeTimeoutMin, - }; - - settings.AddRawParameterStruct(ref parameter); - - return ref settings; - } - - [Obsolete("Use WithSecureClientParameters or WithSecureServerParameters instead.")] - public static ref NetworkSettings WithSecureParameters( + /// Set server security parameters (for client authentication). + /// Server's certificate chain (PEM format). + /// Server's private key (PEM format). + /// CA certificate that signed the client certificates (PEM format). + /// Common name (CN) in the client certificates. + /// Client authentication policy. + public static ref NetworkSettings WithSecureServerParameters( ref this NetworkSettings settings, - ref FixedString4096Bytes pem, - ref FixedString4096Bytes rsa, - ref FixedString4096Bytes rsaKey, - ref FixedString32Bytes hostname, - SecureTransportProtocol protocol = SecureTransportProtocol.DTLS, - SecureClientAuthPolicy clientAuthenticationPolicy = SecureClientAuthPolicy.Optional, - uint sslReadTimeoutMs = 0, - uint sslHandshakeTimeoutMax = 60000, - uint sslHandshakeTimeoutMin = 1000 - ) + string certificate, + string privateKey, + string caCertificate, + string clientName, + SecureClientAuthPolicy clientAuthenticationPolicy = SecureClientAuthPolicy.Required) { - var parameter = new SecureNetworkProtocolParameter - { - Pem = pem, - Rsa = rsa, - RsaKey = rsaKey, - Hostname = hostname, - Protocol = protocol, - ClientAuthenticationPolicy = clientAuthenticationPolicy, - SSLReadTimeoutMs = sslReadTimeoutMs, - SSLHandshakeTimeoutMax = sslHandshakeTimeoutMax, - SSLHandshakeTimeoutMin = sslHandshakeTimeoutMin, - }; + var fixedCertificate = new FixedString4096Bytes(certificate); + var fixedPrivateKey = new FixedString4096Bytes(privateKey); + var fixedCaCertificate = new FixedString4096Bytes(caCertificate); + var fixedClientName = new FixedString512Bytes(clientName); - settings.AddRawParameterStruct(ref parameter); + settings.WithSecureServerParameters( + ref fixedCertificate, + ref fixedPrivateKey, + ref fixedCaCertificate, + ref fixedClientName, + clientAuthenticationPolicy); return ref settings; } @@ -271,4 +268,4 @@ public static SecureNetworkProtocolParameter GetSecureParameters(ref this Networ } } -#endif \ No newline at end of file +#endif diff --git a/Runtime/SecureProtocol/SecureParameters.cs.meta b/Runtime/TLS/SecureParameters.cs.meta similarity index 100% rename from Runtime/SecureProtocol/SecureParameters.cs.meta rename to Runtime/TLS/SecureParameters.cs.meta diff --git a/Runtime/TLS/UnityTLSCallbacks.cs b/Runtime/TLS/UnityTLSCallbacks.cs new file mode 100644 index 0000000..9b69b10 --- /dev/null +++ b/Runtime/TLS/UnityTLSCallbacks.cs @@ -0,0 +1,212 @@ +#if ENABLE_MANAGED_UNITYTLS + +using System; +using System.Runtime.InteropServices; +using AOT; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.TLS.LowLevel; +using UnityEngine; + +namespace Unity.Networking.Transport.TLS +{ + /// Callbacks to use with UnityTLS. + internal unsafe static class UnityTLSCallbacks + { + /// Structure that callbacks expect a pointer to as their user data. + public struct CallbackContext + { + /// The last received encrypted packet (if any). + public PacketProcessor ReceivedPacket; + + /// Queue from which to grab packets to send/replace. + public PacketsQueue SendQueue; + + /// Index of the packet to replace in the send queue (if any). + /// Negative value means enqueueing a new packet on the queue. + public int SendQueueIndex; + + /// Padding already reserved for the layer in the packet. + public int PacketPadding; + + /// Endpoint of newly-enqueued packets. + public NetworkEndpoint NewPacketsEndpoint; + + /// Connection ID of newly-enqueued packets. + public ConnectionId NewPacketsConnection; + } + + // TODO Most of the boilerplate here could be avoided by using C# 9.0 function pointers. + + private static Binding.unitytls_client_data_send_callback s_SendCallbackDelegate; + private static Binding.unitytls_client_data_receive_callback s_ReceiveCallbackDelegate; + private static Binding.unitytls_client_log_callback s_LogCallbackDelegate; + + private struct FunctionPointersKey {} + + private static readonly SharedStatic> + s_SendCallbackPtr = SharedStatic> + .GetOrCreate, FunctionPointersKey>(); + + private static readonly SharedStatic> + s_ReceiveCallbackPtr = SharedStatic> + .GetOrCreate, FunctionPointersKey>(); + + private static readonly SharedStatic> + s_LogCallbackPtr = SharedStatic> + .GetOrCreate, FunctionPointersKey>(); + + private static bool s_Initialized; + + /// Function pointer to the send callback. + public static IntPtr SendCallbackPtr => s_Initialized ? s_SendCallbackPtr.Data.Value : IntPtr.Zero; + + /// Function pointer to the receive callback. + public static IntPtr ReceiveCallbackPtr => s_Initialized ? s_ReceiveCallbackPtr.Data.Value : IntPtr.Zero; + + /// Function pointer to the log callback. + public static IntPtr LogCallbackPtr => s_Initialized ? s_LogCallbackPtr.Data.Value : IntPtr.Zero; + + /// Initialize the function pointers of the callbacks. + /// Must be called from managed code. + public static void Initialize() + { + if (!s_Initialized) + { + s_Initialized = true; + + s_SendCallbackDelegate = SendCallback; + s_ReceiveCallbackDelegate = ReceiveCallback; + s_LogCallbackDelegate = LogCallback; + + var sendPtr = Marshal.GetFunctionPointerForDelegate(s_SendCallbackDelegate); + s_SendCallbackPtr.Data = new FunctionPointer(sendPtr); + + var recvPtr = Marshal.GetFunctionPointerForDelegate(s_ReceiveCallbackDelegate); + s_ReceiveCallbackPtr.Data = new FunctionPointer(recvPtr); + + var logPtr = Marshal.GetFunctionPointerForDelegate(s_LogCallbackDelegate); + s_LogCallbackPtr.Data = new FunctionPointer(logPtr); + } + } + + // For some reason UnityTLS doesn't expose those in the bindings... + private const int UNITYTLS_ERR_SSL_WANT_READ = -0x6900; + private const int UNITYTLS_ERR_SSL_WANT_WRITE = -0x6880; + + [BurstCompile(DisableDirectCall = true)] + [MonoPInvokeCallback(typeof(Binding.unitytls_client_data_send_callback))] + private static int SendCallback(IntPtr userData, byte* data, UIntPtr dataLength, uint status) + { + var ctx = (CallbackContext*)userData; + var length = (int)dataLength.ToUInt32(); + + // If we're provided a send queue index, it means we're expected to write the data in + // the packet it refers to. Otherwise we take a packet from the provided send queue (in + // the case of TLS, this could mean writing the payload over multiple packets). + if (ctx->SendQueueIndex >= 0) + { + var packet = ctx->SendQueue[ctx->SendQueueIndex]; + + // Compute the new packet offset. The stack has already reserved the packet padding + // at the start of the packet, we want to use this space for the encrypted packet. + var newOffset = packet.Offset - ctx->PacketPadding; + if (newOffset < 0) + { + Debug.LogError($"Invalid offset in packet processor ({packet.Offset}, should be >={ctx->PacketPadding})."); + // TODO Is this really the correct error code for this situation? + return UNITYTLS_ERR_SSL_WANT_WRITE; + } + + // We reset the metadata to that of a 0-length packet at the right offset so that + // AppendToPayload copies the encrypted data at the right place. + packet.SetUnsafeMetadata(0, newOffset); + packet.AppendToPayload(data, length); + } + else + { + var offset = 0; + while (offset < length) + { + // TODO What's the correct way of handling partial sends? + if (!ctx->SendQueue.EnqueuePacket(out var packet)) + return offset > 0 ? offset : UNITYTLS_ERR_SSL_WANT_WRITE; + + // No need to adjust the offset when sending directly through the send queue, + // but we do need to set the endpoint and connection appropriately. + packet.EndpointRef = ctx->NewPacketsEndpoint; + packet.ConnectionRef = ctx->NewPacketsConnection; + + var packetLength = math.min(length - offset, packet.BytesAvailableAtEnd); + packet.AppendToPayload(data + offset, packetLength); + + offset += packetLength; + } + } + + return length; + } + + [BurstCompile(DisableDirectCall = true)] + [MonoPInvokeCallback(typeof(Binding.unitytls_client_data_receive_callback))] + private static int ReceiveCallback(IntPtr userData, byte* data, UIntPtr dataLength, uint status) + { + var ctx = (CallbackContext*)userData; + if (!ctx->ReceivedPacket.IsCreated || ctx->ReceivedPacket.Length == 0) + return UNITYTLS_ERR_SSL_WANT_READ; + + // The value of dataLength is the length of the buffer provided by UnityTLS. For DTLS, + // this is always some super large value that's sure to accomodate the entire received + // packet. But when using TLS, the values will be much smaller because UnityTLS expects + // to read the stream little pieces at a time. + var removeLength = math.min((int)dataLength.ToUInt32(), ctx->ReceivedPacket.Length); + ctx->ReceivedPacket.RemoveFromPayloadStart(data, removeLength); + + return removeLength; + } + + [BurstCompile(DisableDirectCall = true)] + [MonoPInvokeCallback(typeof(Binding.unitytls_client_log_callback))] + private static void LogCallback(int level, byte* file, UIntPtr line, byte* function, byte* message, UIntPtr messageLength) + { + FixedString512Bytes log = "[UnityTLS"; + + // Append the file name and line number if provided. + if (file != null && RawStringLength(file) != 0) + { + log.Append(':'); + log.Append(file, RawStringLength(file)); + log.Append(':'); + log.Append(line.ToUInt32()); + } + + // Append the function name if provided. + if (function != null && RawStringLength(function) != 0) + { + log.Append(':'); + log.Append(function, RawStringLength(function)); + } + + // Append the actual log message. + log.Append(']'); + log.Append(' '); + log.Append(message, (int)messageLength.ToUInt32()); + + // TODO Use the log level to pick the right logging method. + Debug.Log(log); + } + + // Can't believe we're reimplementing strlen() from C... + private static int RawStringLength(byte *str) + { + var length = 0; + while (str[length] != '\0') + length++; + return length; + } + } +} + +#endif \ No newline at end of file diff --git a/Runtime/TLS/UnityTLSCallbacks.cs.meta b/Runtime/TLS/UnityTLSCallbacks.cs.meta new file mode 100644 index 0000000..de9024b --- /dev/null +++ b/Runtime/TLS/UnityTLSCallbacks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d67355893faaaf2429339586babb7551 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/TLS/UnityTLSConfiguration.cs b/Runtime/TLS/UnityTLSConfiguration.cs new file mode 100644 index 0000000..5385e04 --- /dev/null +++ b/Runtime/TLS/UnityTLSConfiguration.cs @@ -0,0 +1,168 @@ +#if ENABLE_MANAGED_UNITYTLS + +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Networking.Transport.Relay; +using Unity.TLS.LowLevel; + +namespace Unity.Networking.Transport.TLS +{ + /// Secure transport protocols supported by UnityTLS. + internal enum SecureTransportProtocol : uint + { + TLS = 0, + DTLS = 1, + } + + /// Utility containter for a UnityTLS configuration. + internal unsafe struct UnityTLSConfiguration : IDisposable + { + private NativeReference m_Config; + private NativeReference m_Callbacks; + + public Binding.unitytls_client_config* ConfigPtr => (Binding.unitytls_client_config*)m_Config.GetUnsafePtr(); + public UnityTLSCallbacks.CallbackContext* CallbackContextPtr => (UnityTLSCallbacks.CallbackContext*)m_Callbacks.GetUnsafePtr(); + + // We need to store pointers into SecureNetworkProtocolParameter or RelayNetworkParameter + // inside the UnityTLS configuration, so store them in native containers so that they'll + // have a stable address. + private NativeReference m_SecureParameters; + private NativeReference m_RelayParameters; + private NativeReference m_RelayHostname; + + public bool IsCreated => m_Config.IsCreated; + + private static void InitializeFromSecureParameters(Binding.unitytls_client_config* config, ref SecureNetworkProtocolParameter parameters) + { + config->clientAuth = (uint)parameters.ClientAuthenticationPolicy; + + if (parameters.Hostname != default) + config->hostname = parameters.Hostname.GetUnsafePtr(); + + if (parameters.Pem != default) + { + config->caPEM = new Binding.unitytls_dataRef() + { + dataPtr = parameters.Pem.GetUnsafePtr(), + dataLen = new UIntPtr((uint)parameters.Pem.Length) + }; + } + + if (parameters.Rsa != default && parameters.RsaKey != default) + { + config->serverPEM = new Binding.unitytls_dataRef() + { + dataPtr = parameters.Rsa.GetUnsafePtr(), + dataLen = new UIntPtr((uint)parameters.Rsa.Length) + }; + + config->privateKeyPEM = new Binding.unitytls_dataRef() + { + dataPtr = parameters.RsaKey.GetUnsafePtr(), + dataLen = new UIntPtr((uint)parameters.RsaKey.Length) + }; + } + } + + private static void InitializeFromRelayParameters(Binding.unitytls_client_config* config, ref RelayNetworkParameter parameters) + { + // We don't set the protocol, client authentication policy, and different timeouts + // because either their values are customized through SecureNetworkProtocolParameter, + // or we want to use the defaults and then unitytls_client_init_config will have set + // them appropriately for our needs. + + fixed (byte* hmacPtr = parameters.ServerData.HMACKey.Value) + { + config->psk = new Binding.unitytls_dataRef() + { + dataPtr = hmacPtr, + dataLen = new UIntPtr(RelayHMACKey.k_Length) + }; + } + + fixed (byte* allocPtr = parameters.ServerData.AllocationId.Value) + { + config->pskIdentity = new Binding.unitytls_dataRef() + { + dataPtr = allocPtr, + dataLen = new UIntPtr(RelayAllocationId.k_Length) + }; + } + } + + public UnityTLSConfiguration(ref NetworkSettings settings, SecureTransportProtocol protocol, ushort mtu = 0) + { + UnityTLSCallbacks.Initialize(); + + m_Config = new NativeReference(Allocator.Persistent); + m_Callbacks = new NativeReference(Allocator.Persistent); + + m_SecureParameters = default; + m_RelayParameters = default; + m_RelayHostname = default; + + Binding.unitytls_client_init_config(ConfigPtr); + + if (settings.TryGet(out var secureParams)) + { + m_SecureParameters = new NativeReference(Allocator.Persistent); + + // Can't just assign to value since SecureNetworkProtocolParameter is too big (on + // Mono you can't pass parameters larger than 10K bytes as values to a property). + var paramsPtr = (SecureNetworkProtocolParameter*)m_SecureParameters.GetUnsafePtr(); + *paramsPtr = secureParams; + + InitializeFromSecureParameters(ConfigPtr, ref UnsafeUtility.AsRef(paramsPtr)); + } + + if (settings.TryGet(out var relayParams)) + { + // Relay authentication doesn't require a hostname, but UnityTLS will still try to + // send an empty string if none is provided, which causes issues. See MTT-1753. + FixedString32Bytes hostname = "relay"; + + m_RelayParameters = new NativeReference(relayParams, Allocator.Persistent); + m_RelayHostname = new NativeReference(hostname, Allocator.Persistent); + + var paramsPtr = (RelayNetworkParameter*)m_RelayParameters.GetUnsafePtr(); + InitializeFromRelayParameters(ConfigPtr, ref UnsafeUtility.AsRef(paramsPtr)); + + ConfigPtr->hostname = (byte*)m_RelayHostname.GetUnsafePtr(); + } + + var netConfig = settings.GetNetworkConfigParameters(); + ConfigPtr->ssl_handshake_timeout_min = (uint)netConfig.connectTimeoutMS; + ConfigPtr->ssl_handshake_timeout_max = (uint)(netConfig.maxConnectAttempts * netConfig.connectTimeoutMS); + + ConfigPtr->transportProtocol = (uint)protocol; + ConfigPtr->transportUserData = (IntPtr)CallbackContextPtr; + + ConfigPtr->dataSendCB = UnityTLSCallbacks.SendCallbackPtr; + ConfigPtr->dataReceiveCB = UnityTLSCallbacks.ReceiveCallbackPtr; + //ConfigPtr->logCallback = UnityTLSCallbacks.LogCallbackPtr; + + ConfigPtr->mtu = mtu; + } + + public void Dispose() + { + if (IsCreated) + { + m_Config.Dispose(); + m_Callbacks.Dispose(); + } + + if (m_SecureParameters.IsCreated) + m_SecureParameters.Dispose(); + + if (m_RelayParameters.IsCreated) + m_RelayParameters.Dispose(); + + if (m_RelayHostname.IsCreated) + m_RelayHostname.Dispose(); + } + } +} + +#endif \ No newline at end of file diff --git a/Runtime/TLS/UnityTLSConfiguration.cs.meta b/Runtime/TLS/UnityTLSConfiguration.cs.meta new file mode 100644 index 0000000..09f7554 --- /dev/null +++ b/Runtime/TLS/UnityTLSConfiguration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc45d07a537305e45882f280d9bc6929 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/TransportFunctionPointer.cs b/Runtime/TransportFunctionPointer.cs index b724c34..90f95a2 100644 --- a/Runtime/TransportFunctionPointer.cs +++ b/Runtime/TransportFunctionPointer.cs @@ -4,53 +4,29 @@ namespace Unity.Networking.Transport { - /// - /// Represents a wrapper around burst compatible function pointers in a portable way - /// public struct TransportFunctionPointer where T : Delegate { - /// - /// Initializes a new instance of the class - /// - /// The execute delegate public TransportFunctionPointer(T executeDelegate) { Ptr = BurstCompiler.CompileFunctionPointer(executeDelegate); } - /// - /// Initializes a new instance of the class - /// - /// The pointer public TransportFunctionPointer(FunctionPointer Pointer) { Ptr = Pointer; } - /// - /// returns a wrapped Burst compiled function pointer - /// - /// The burst compilable delegate - /// A transport function pointer of t public static TransportFunctionPointer Burst(T burstCompilableDelegate) { return new TransportFunctionPointer(BurstCompiler.CompileFunctionPointer(burstCompilableDelegate)); } - /// - /// Returns a wrapped managed function pointer - /// - /// The managed delegate - /// A transport function pointer of t public static TransportFunctionPointer Managed(T managedDelegate) { GCHandle.Alloc(managedDelegate); // Ensure delegate is never garbage-collected. return new TransportFunctionPointer(new FunctionPointer(Marshal.GetFunctionPointerForDelegate(managedDelegate))); } - /// - /// Returns Burst - /// public readonly FunctionPointer Ptr; } } diff --git a/Runtime/UDPNetworkInterface.cs b/Runtime/UDPNetworkInterface.cs new file mode 100644 index 0000000..ae259a9 --- /dev/null +++ b/Runtime/UDPNetworkInterface.cs @@ -0,0 +1,773 @@ +#if !UNITY_WEBGL || UNITY_EDITOR +using System; +using System.Collections.Generic; +using Unity.Baselib.LowLevel; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using ErrorState = Unity.Baselib.LowLevel.Binding.Baselib_ErrorState; +using ErrorCode = Unity.Baselib.LowLevel.Binding.Baselib_ErrorCode; +using Unity.Mathematics; + +namespace Unity.Networking.Transport +{ + using NetworkRequest = Binding.Baselib_RegisteredNetwork_Request; + using RegisteredNetworkEndpoint = Binding.Baselib_RegisteredNetwork_Endpoint; + using NetworkSocket = Binding.Baselib_RegisteredNetwork_Socket_UDP; + + [BurstCompile] + public struct UDPNetworkInterface : INetworkInterface + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private class SocketList + { + public struct SocketId + { + public NetworkSocket socket; + } + public List OpenSockets = new List(); + + ~SocketList() + { + foreach (var socket in OpenSockets) + { + Binding.Baselib_RegisteredNetwork_Socket_UDP_Close(socket.socket); + } + } + } + private static SocketList AllSockets = new SocketList(); +#endif + + // Array size to use when batching send/receive requests. We need to batch these requests + // because the array is stack-allocated, and using the send/receive queue sizes could lead + // to a stack overflow. + const uint k_RequestsBatchSize = 64; + + // Safety value for the maximum number of times we can recreate a socket. Recreating a + // socket so many times would indicate some deeper issue that we won't solve by opening + // new sockets all the time. This also prevents logging endlessly if we get stuck in a + // loop of recreating sockets very frequently. + const uint k_MaxNumSocketRecreate = 1000; + + private struct PacketBufferLayout + { + public uint MetadataOffset; + public uint EndpointOffset; + public uint PayloadOffset; + } + + internal enum SocketStatus + { + SocketNormal, + SocketNeedsRecreate, + SocketFailed, + } + + internal struct InternalState + { + public NetworkSocket Socket; + public SocketStatus SocketStatus; + public NetworkEndpoint LocalEndpoint; + public int ReceiveQueueCapacity; + public int SendQueueCapacity; + public long LastUpdateTime; + public long LastSocketRecreateTime; + public uint NumSocketRecreate; + } + + private PacketsQueue m_ReceiveQueue; + private UnsafeBaselibNetworkArray m_SendBuffers; + private UnsafeBaselibNetworkArray m_ReceiveBuffers; + private PacketBufferLayout m_PacketBufferLayout; + + internal NativeReference m_InternalState; + + /// + /// Returns the local endpoint. + /// + /// NetworkInterfaceEndPoint + public unsafe NetworkEndpoint LocalEndpoint => m_InternalState.Value.LocalEndpoint; + + public bool IsCreated => m_InternalState.IsCreated; + + /// + /// Initializes a instance of the BaselibNetworkInterface struct. + /// + /// An array of INetworkParameter. There is currently only that can be passed. + public unsafe int Initialize(ref NetworkSettings settings, ref int packetPadding) + { + var networkConfiguration = settings.GetNetworkConfigParameters(); + + var state = new InternalState + { + ReceiveQueueCapacity = networkConfiguration.receiveQueueCapacity, + SendQueueCapacity = networkConfiguration.sendQueueCapacity, + }; + + m_InternalState = new NativeReference(state, Allocator.Persistent); + + var bufferSize = NetworkParameterConstants.MTU + UnsafeUtility.SizeOf() + (int)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize; + + m_ReceiveBuffers = new UnsafeBaselibNetworkArray(state.ReceiveQueueCapacity, bufferSize); + m_SendBuffers = new UnsafeBaselibNetworkArray(state.SendQueueCapacity, bufferSize); + + return 0; + } + + internal void CreateQueues(int sendQueueCapacity, int receiveQueueCapacity, out PacketsQueue sendQueue, out PacketsQueue receiveQueue) + { + var metadataSize = UnsafeUtility.SizeOf(); + var payloadSize = NetworkParameterConstants.MTU; + var endpointSize = UnsafeUtility.SizeOf(); + + // The registered network endpoint size might require some extra bytes + endpointSize = math.max(endpointSize, (int)Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize); + + m_PacketBufferLayout = new PacketBufferLayout + { + MetadataOffset = 0, + EndpointOffset = (uint)metadataSize, + PayloadOffset = (uint)(metadataSize + endpointSize), + }; + + receiveQueue = new PacketsQueue( + metadataSize, + payloadSize, + endpointSize, + receiveQueueCapacity, + GetTempPacketBuffersArray(receiveQueueCapacity, ref m_ReceiveBuffers)); + + m_ReceiveQueue = receiveQueue; + + sendQueue = new PacketsQueue( + metadataSize, + payloadSize, + endpointSize, + sendQueueCapacity, + GetTempPacketBuffersArray(sendQueueCapacity, ref m_SendBuffers)); + + if (m_SendBuffers.ElementSize < metadataSize + payloadSize + endpointSize) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException($"The required buffer size ({metadataSize + payloadSize + endpointSize}) does not fit in the allocated send buffers ({m_SendBuffers.ElementSize})"); +#else + UnityEngine.Debug.LogError($"The required buffer size ({metadataSize + payloadSize + endpointSize}) does not fit in the allocated send buffers ({m_SendBuffers.ElementSize})"); +#endif + } + + if (m_ReceiveBuffers.ElementSize < metadataSize + payloadSize + endpointSize) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException($"The required buffer size ({metadataSize + payloadSize + endpointSize}) does not fit in the allocated receive buffers ({m_ReceiveBuffers.ElementSize})"); +#else + UnityEngine.Debug.LogError($"The required buffer size ({metadataSize + payloadSize + endpointSize}) does not fit in the allocated receive buffers ({m_ReceiveBuffers.ElementSize})"); +#endif + } + } + + private unsafe NativeArray GetTempPacketBuffersArray(int capacity, ref UnsafeBaselibNetworkArray buffers) + { + var buffersList = new NativeArray(capacity, Allocator.Temp, NativeArrayOptions.ClearMemory); + + for (int i = 0; i < capacity; i++) + { + var bufferPtr = buffers.GetBufferPtr(i); + buffersList[i] = new PacketBuffer + { + Metadata = bufferPtr + (int)m_PacketBufferLayout.MetadataOffset, + Endpoint = bufferPtr + (int)m_PacketBufferLayout.EndpointOffset, + Payload = bufferPtr + (int)m_PacketBufferLayout.PayloadOffset, + }; + } + + return buffersList; + } + + public unsafe void Dispose() + { + CloseSocket(m_InternalState.Value.Socket); + + m_SendBuffers.Dispose(); + m_ReceiveBuffers.Dispose(); + + m_InternalState.Dispose(); + } + + private static unsafe FixedString512Bytes GetBaselibErrorMessage(ErrorState error) + { + FixedString512Bytes errorMessage = new FixedString512Bytes(); + errorMessage.Length = (int)Binding.Baselib_ErrorState_Explain(&error, errorMessage.GetUnsafePtr(), (uint)errorMessage.Capacity, Binding.Baselib_ErrorState_ExplainVerbosity.ErrorType_SourceLocation_Explanation); + + return errorMessage; + } + + // TODO: Remove once baselib api is fully burst compatible + struct ConvertEndpointsToRegisteredJob : IJob + { + public PacketsQueue SendQueue; + public UnsafeBaselibNetworkArray SendBuffers; + public PacketBufferLayout PacketBufferLayout; + + public void Execute() + { + var count = SendQueue.Count; + for (int i = 0; i < SendQueue.Count; i++) + { + var packetProcessor = SendQueue[i]; + var request = GetRequest(packetProcessor.m_BufferIndex, ref SendBuffers, ref PacketBufferLayout); + if (!ConvertEndpointBufferToRegisteredNetwork(request.remoteEndpoint.slice)) + packetProcessor.Drop(); + } + } + } + + [BurstCompile] + struct FlushSendJob : IJob + { + public PacketsQueue SendQueue; + [NativeDisableContainerSafetyRestriction] + public NativeReference InternalState; + public UnsafeBaselibNetworkArray SendBuffers; + public PacketBufferLayout PacketBufferLayout; + public byte WaitForCompletedSends; + + public unsafe void Execute() + { + var error = default(ErrorState); + + var socket = InternalState.Value.Socket; + + // Register new packets coming from the send queue + var pendingSendCount = 0; + var metadataSize = (uint)UnsafeUtility.SizeOf(); + var endpointSize = (uint)UnsafeUtility.SizeOf(); + for (int i = 0; i < SendQueue.Count; i++) + { + var packetProcessor = SendQueue[i]; + + if (packetProcessor.Length <= 0) + continue; + + var request = GetRequest(packetProcessor.m_BufferIndex, ref SendBuffers, ref PacketBufferLayout); + + request.payload.offset += (uint)packetProcessor.Offset; + request.payload.data += packetProcessor.Offset; + request.payload.size = (uint)packetProcessor.Length; + + // TODO: This line is not burst compatible due to baselib api. + // It's temporarily moved to a separated not burst compiled job. + // if (!ConvertEndpointBufferToRegisteredNetwork(request.remoteEndpoint.slice)) + // continue; + + pendingSendCount += (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleSend(socket, &request, 1u, &error); + + if (error.code != ErrorCode.Success) + { + UnityEngine.Debug.LogError(string.Format("Error on baselib scheduling send ({0}): {1}", error.code, GetBaselibErrorMessage(error))); + MarkSocketAsNeedingRecreate(ref InternalState); + return; + } + + // Remove the packet from the send queue, but don't release it's buffer. It will be released + // when processing completed send requests. + SendQueue.DequeuePacketNoRelease(i); + } + + do + { + // We ensure we never process more than the actual capacity to prevent unexpected deadlocks + for (var sendCount = 0; sendCount < SendQueue.Capacity; sendCount++) + { + var status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessSend(socket, &error); + if (error.code != ErrorCode.Success) + { + UnityEngine.Debug.LogError(string.Format("Error on baselib processing send ({0})", error.code)); + MarkSocketAsNeedingRecreate(ref InternalState); + return; + } + + if (status != Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) + break; + } + + if (WaitForCompletedSends != 0) + { + // Yielding gives a change to the OS of actually sending the packets. + Binding.Baselib_Thread_YieldExecution(); + + // Wait for the sends to complete (timeout is arbitrary, and even if not + // everything is sent we'll still come back here in the loop). + Binding.Baselib_RegisteredNetwork_Socket_UDP_WaitForCompletedSend(socket, 10, &error); + + // We don't check the error code because if no sends were completed, this is + // flagged as an error by baselib (but we want to keep going in that case). + } + + pendingSendCount -= DequeueSendRequests(); + } + while (WaitForCompletedSends != 0 && pendingSendCount > 0); + } + + private unsafe int DequeueSendRequests() + { + var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; + var resultBatchesCount = SendQueue.Capacity / k_RequestsBatchSize + 1; + + var totalDequeued = 0; + + var error = default(ErrorState); + var count = 0; + while ((count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueSend(InternalState.Value.Socket, results, k_RequestsBatchSize, &error)) > 0) + { + if (error.code != ErrorCode.Success) + { + UnityEngine.Debug.LogError(string.Format("Error on baselib dequeueing send ({0})", error.code)); + MarkSocketAsNeedingRecreate(ref InternalState); + return totalDequeued; + } + + totalDequeued += count; + + // Once a send has completed, re-enqueue its packet. This way if there's any + // other layer below it will have an opportunity to process it. + for (var i = 0; i < count; ++i) + SendQueue.EnqueuePacket(results[i].requestUserdata.ToInt32() - 1, out _); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var failedCount = 0; + for (var i = 0; i < count; ++i) + { + if (results[i].status == Binding.Baselib_RegisteredNetwork_CompletionStatus.Failed) + failedCount++; + } + + if (failedCount > 0) + UnityEngine.Debug.LogWarning(string.Format("Baselib failed to send {0} packets", failedCount)); +#endif + + if (resultBatchesCount-- < 0) // Deadlock guard, this should never happen + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new Exception("Trying to dequeue more packets than the actual sent count in baselib."); +#else + UnityEngine.Debug.LogError("Trying to dequeue more packets than the actual sent count in baselib."); + break; +#endif + } + } + + return totalDequeued; + } + } + + // TODO: Remove once baselib api is fully burst compatible + struct ConvertEndpointsToGenericJob : IJob + { + public PacketsQueue ReceiveQueue; + public UnsafeBaselibNetworkArray ReceiveBuffers; + public PacketBufferLayout PacketBufferLayout; + + public void Execute() + { + var count = ReceiveQueue.Count; + for (int i = 0; i < ReceiveQueue.Count; i++) + { + var packetProcessor = ReceiveQueue[i]; + var request = GetRequest(packetProcessor.m_BufferIndex, ref ReceiveBuffers, ref PacketBufferLayout); + if (!ConvertEndpointBufferToGeneric(request.remoteEndpoint.slice)) + packetProcessor.Drop(); + } + } + } + + [BurstCompile] + struct ReceiveJob : IJob + { + public PacketsQueue ReceiveQueue; + [NativeDisableContainerSafetyRestriction] + public NativeReference InternalState; + public UnsafeBaselibNetworkArray ReceiveBuffers; + public OperationResult Result; + public PacketBufferLayout PacketBufferLayout; + public long UpdateTime; + + public unsafe void Execute() + { + // Update last update time of internal state. + var state = InternalState.Value; + state.LastUpdateTime = UpdateTime; + InternalState.Value = state; + + var socket = InternalState.Value.Socket; + + // If we just recreated the socket, need to reset the receive queues. + if (InternalState.Value.LastSocketRecreateTime == UpdateTime) + ResetReceiveQueue(ref ReceiveQueue); + + // Baselib requires receives to be scheduled before the message arrives, so we keep them always scheduled. + // At this point the process of the received messages has already happened, users accessed them as events + // and their buffers should be released. + if (ScheduleAllReceives(socket, ref ReceiveQueue, ref ReceiveBuffers, ref PacketBufferLayout) != 0) + { + MarkSocketAsNeedingRecreate(ref InternalState); + return; + } + + var error = default(ErrorState); + + var pollCount = 0; + var status = default(Binding.Baselib_RegisteredNetwork_ProcessStatus); + while ((status = Binding.Baselib_RegisteredNetwork_Socket_UDP_ProcessRecv(socket, &error)) == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending + && pollCount++ < ReceiveQueue.Capacity) {} + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (status == Binding.Baselib_RegisteredNetwork_ProcessStatus.Pending) + { + UnityEngine.Debug.LogWarning("There are pending receive packets after the baselib process receive"); + } +#endif + + var results = stackalloc Binding.Baselib_RegisteredNetwork_CompletionResult[(int)k_RequestsBatchSize]; + + var dequeueAgain = true; + var totalCount = 0; + var failedCount = 0; + + while (dequeueAgain) + { + // Pop Completed Requests off the CompletionQ + var count = (int)Binding.Baselib_RegisteredNetwork_Socket_UDP_DequeueRecv(socket, results, k_RequestsBatchSize, &error); + if (error.code != ErrorCode.Success) + { + MarkSocketAsNeedingRecreate(ref InternalState); + Result.ErrorCode = (int)error.code; // TODO should we really return baselib error codes to users? + return; + } + + totalCount += count; + + for (int i = 0; i < count; i++) + { + var bufferIndex = (int)results[i].requestUserdata - 1; + + if (results[i].status == Binding.Baselib_RegisteredNetwork_CompletionStatus.Failed) + { + failedCount++; + continue; + } + + var receivedBytes = (int)results[i].bytesTransferred; + if (receivedBytes <= 0) + continue; + + if (!ReceiveQueue.EnqueuePacket(bufferIndex, out var packetProcessor)) + { + Result.ErrorCode = (int)Error.StatusCode.NetworkReceiveQueueFull; + UnityEngine.Debug.LogError("Could not enqueue received packet."); + return; + } + + // TODO: These lines are not burst compatible due to baselib api. + // // The receivied endpoint is in RegisteredNetwork format, we need to parse it to generic. + // var endpointSlice = ReceiveBuffers.AtIndexAsSlice(bufferIndex); + // endpointSlice.offset = PacketBufferLayout.EndpointOffset; + // endpointSlice.data = new IntPtr((byte*)endpointSlice.data + PacketBufferLayout.EndpointOffset); + // if (!ConvertEndpointBufferToGeneric(endpointSlice)) + // receivedBytes = 0; + + packetProcessor.SetUnsafeMetadata(receivedBytes); + } + + // If we filled our batch with requests, there might be more to dequeue. + dequeueAgain = count == (int)k_RequestsBatchSize; + } + + // All receive requests being marked as failed is as close as we're going to get to + // a signal that the socket has failed with the current baselib API (at least on + // platforms that use the basic POSIX sockets implementation under the hood). Note + // that we can't do the same check on send requests, since there might be legit + // scenarios where sends are failing temporarily without the socket being borked. + if (totalCount > 0 && totalCount == failedCount) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError("All socket receive requests were marked as failed, likely because socket itself has failed."); +#endif + MarkSocketAsNeedingRecreate(ref InternalState); + } + } + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + { + // TODO: Move this inside the receive job (requires MTT-3703). + if (m_InternalState.Value.SocketStatus == SocketStatus.SocketNeedsRecreate) + RecreateSocket(arguments.Time, ref arguments.ReceiveQueue); + + if (m_InternalState.Value.SocketStatus == SocketStatus.SocketFailed) + { + arguments.ReceiveResult.ErrorCode = (int)Error.StatusCode.NetworkSocketError; + return dep; + } + + var job = new ReceiveJob + { + InternalState = m_InternalState, + ReceiveQueue = arguments.ReceiveQueue, + ReceiveBuffers = m_ReceiveBuffers, + Result = arguments.ReceiveResult, + PacketBufferLayout = m_PacketBufferLayout, + UpdateTime = arguments.Time, + }.Schedule(dep); + + // TODO: Remove this job once baseli api if fully burst compatible + return new ConvertEndpointsToGenericJob + { + ReceiveQueue = arguments.ReceiveQueue, + ReceiveBuffers = m_ReceiveBuffers, + PacketBufferLayout = m_PacketBufferLayout, + }.Schedule(job); + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + { + if (m_InternalState.Value.SocketStatus != SocketStatus.SocketNormal) + return dep; + + // TODO: Remove this job once baseli api if fully burst compatible + var convertJob = new ConvertEndpointsToRegisteredJob + { + SendQueue = arguments.SendQueue, + SendBuffers = m_SendBuffers, + PacketBufferLayout = m_PacketBufferLayout, + }.Schedule(dep); + + var job = new FlushSendJob + { + InternalState = m_InternalState, + SendQueue = arguments.SendQueue, + SendBuffers = m_SendBuffers, + PacketBufferLayout = m_PacketBufferLayout, + // TODO Find a way to expose this to users. + WaitForCompletedSends = (byte)0, + }; + return job.Schedule(convertJob); + } + + /// + /// Binds the BaselibNetworkInterface to the endpoint passed. + /// + /// A valid ipv4 or ipv6 address + /// int + public unsafe int Bind(NetworkEndpoint endpoint) + { + var state = m_InternalState.Value; + + var result = CreateSocket(state.SendQueueCapacity, state.ReceiveQueueCapacity, endpoint, out var newSocket); + if (result == 0) + { + CloseSocket(state.Socket); + state.Socket = newSocket; + state.SocketStatus = SocketStatus.SocketNormal; + + // Need to release acquisition status of all packet buffers since we closed their socket. + ResetReceiveQueue(ref m_ReceiveQueue); + + result = ScheduleAllReceives(newSocket, ref m_ReceiveQueue, ref m_ReceiveBuffers, ref m_PacketBufferLayout); + + // Store the local endpoint in the internal state. + var error = default(ErrorState); + Binding.Baselib_RegisteredNetwork_Socket_UDP_GetNetworkAddress(newSocket, &state.LocalEndpoint.rawNetworkAddress, &error); + if (error.code != ErrorCode.Success) + state.LocalEndpoint = endpoint; + } + + m_InternalState.Value = state; + + return result; + } + + public int Listen() + { + return 0; + } + + private static unsafe int CreateSocket(int sendQueueCapacity, int receiveQueueCapacity, NetworkEndpoint endpoint, out NetworkSocket socket) + { + var error = default(ErrorState); + socket = Binding.Baselib_RegisteredNetwork_Socket_UDP_Create( + &endpoint.rawNetworkAddress, + Binding.Baselib_NetworkAddress_AddressReuse.Allow, + checked((uint)sendQueueCapacity), + checked((uint)receiveQueueCapacity), + &error); + + if (error.code != ErrorCode.Success) + { + return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AllSockets.OpenSockets.Add(new SocketList.SocketId { socket = socket }); +#endif + return 0; + } + + private static void CloseSocket(NetworkSocket socket) + { + if (socket.handle != IntPtr.Zero) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AllSockets.OpenSockets.Remove(new SocketList.SocketId { socket = socket }); +#endif + Binding.Baselib_RegisteredNetwork_Socket_UDP_Close(socket); + } + } + + private void RecreateSocket(long updateTime, ref PacketsQueue receiveQueue) + { + var state = m_InternalState.Value; + + // If we already recreated the socket in the last update or if we hit the limit of + // socket recreations, then something's wrong at the socket layer and recreating it + // likely won't solve the issue. Just fail the socket in that scenario. + if (state.LastSocketRecreateTime == state.LastUpdateTime || state.NumSocketRecreate >= k_MaxNumSocketRecreate) + { + UnityEngine.Debug.LogError("Unrecoverable socket failure. An unknown condition is preventing the application from reliably creating sockets."); + state.SocketStatus = SocketStatus.SocketFailed; + } + else + { + UnityEngine.Debug.LogWarning("Socket error encountered; attempting recovery by creating a new one."); + 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++; + } + } + + m_InternalState.Value = state; + } + + private static void MarkSocketAsNeedingRecreate(ref NativeReference internalState) + { + var state = internalState.Value; + state.SocketStatus = SocketStatus.SocketNeedsRecreate; + internalState.Value = state; + } + + private static void ResetReceiveQueue(ref PacketsQueue receiveQueue) + { + if (receiveQueue.BuffersInUse != 0) + { + receiveQueue.Clear(); + receiveQueue.UnsafeResetAcquisitionState(); + } + } + + private static unsafe bool ConvertEndpointBufferToRegisteredNetwork(Binding.Baselib_RegisteredNetwork_BufferSlice endpointSlice) + { + var endpoint = *(Binding.Baselib_NetworkAddress*)endpointSlice.data; + var error = default(ErrorState); + + Binding.Baselib_RegisteredNetwork_Endpoint_Create( + &endpoint, + endpointSlice, + &error + ); + + if (error.code != (int)ErrorCode.Success) + { + UnityEngine.Debug.LogError(string.Format("Error creating registered enpoint: {0}", GetBaselibErrorMessage(error))); + return false; + } + + return true; + } + + private static unsafe bool ConvertEndpointBufferToGeneric(Binding.Baselib_RegisteredNetwork_BufferSlice endpointSlice) + { + var endpoint = default(NetworkEndpoint); + var error = default(ErrorState); + + + Binding.Baselib_RegisteredNetwork_Endpoint_GetNetworkAddress( + new RegisteredNetworkEndpoint { slice = endpointSlice }, + &endpoint.rawNetworkAddress, + &error + ); + + if (error.code != (int)ErrorCode.Success) + { + UnityEngine.Debug.LogError(string.Format("Error creating registered enpoint: {0}", GetBaselibErrorMessage(error))); + return false; + } + + *(NetworkEndpoint*)endpointSlice.data = endpoint; + return true; + } + + private unsafe static NetworkRequest GetRequest(int bufferIndex, ref UnsafeBaselibNetworkArray buffers, ref PacketBufferLayout bufferLayout) + { + var bufferSlice = buffers.AtIndexAsSlice(bufferIndex); + + var request = new NetworkRequest + { + payload = bufferSlice, + remoteEndpoint = new RegisteredNetworkEndpoint + { + slice = bufferSlice, + }, + requestUserdata = new IntPtr(bufferIndex + 1), + }; + + request.payload.offset = bufferLayout.PayloadOffset; + request.payload.data = new IntPtr((byte*)request.payload.data + bufferLayout.PayloadOffset); + + request.remoteEndpoint.slice.offset = bufferLayout.EndpointOffset; + request.remoteEndpoint.slice.data = new IntPtr((byte*)request.remoteEndpoint.slice.data + bufferLayout.EndpointOffset); + request.remoteEndpoint.slice.size = Binding.Baselib_RegisteredNetwork_Endpoint_MaxSize; + + return request; + } + + private static unsafe int ScheduleAllReceives(NetworkSocket socket, ref PacketsQueue receiveQueue, ref UnsafeBaselibNetworkArray receiveBuffers, ref PacketBufferLayout packetBufferLayout) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (packetBufferLayout.PayloadOffset == 0 && packetBufferLayout.MetadataOffset == 0) + throw new ArgumentNullException("Invalid packetBufferLayour"); +#endif + var error = default(ErrorState); + + var requests = stackalloc Binding.Baselib_RegisteredNetwork_Request[(int)k_RequestsBatchSize]; + var count = 0; + + do + { + count = 0; + while (count < k_RequestsBatchSize && receiveQueue.TryAcquireBuffer(out var bufferIndex)) + { + requests[count++] = GetRequest(bufferIndex, ref receiveBuffers, ref packetBufferLayout); + } + + Binding.Baselib_RegisteredNetwork_Socket_UDP_ScheduleRecv(socket, requests, (uint)count, &error); + + if (error.code != ErrorCode.Success) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + UnityEngine.Debug.LogError($"Baselib failed to schedule receive ({error}):\n\t{GetBaselibErrorMessage(error)}"); +#endif + return (int)error.code == -1 ? (int)Error.StatusCode.NetworkSocketError : -(int)error.code; + } + } + while (count == k_RequestsBatchSize); + + return 0; + } + } +} +#endif // !UNITY_WEBGL || UNITY_EDITOR diff --git a/Runtime/BaselibNetworkInterface.cs.meta b/Runtime/UDPNetworkInterface.cs.meta similarity index 100% rename from Runtime/BaselibNetworkInterface.cs.meta rename to Runtime/UDPNetworkInterface.cs.meta diff --git a/Runtime/UnderlyingConnectionList.cs b/Runtime/UnderlyingConnectionList.cs new file mode 100644 index 0000000..254634d --- /dev/null +++ b/Runtime/UnderlyingConnectionList.cs @@ -0,0 +1,87 @@ +using Unity.Collections; + +namespace Unity.Networking.Transport +{ + internal interface IUnderlyingConnectionList + { + /// + /// Tries to open a new connection in the underlying layer. + /// + /// The endpoint to connect to. + /// The connection id in the underlying layer. Default will create a new connection and will override this value. + /// Returns true if the connection is fully stablished. + /// + /// Returning false means the connection was created but it has not been fully stablished yet, + /// eg. there is a handshake pending to complete. + /// + bool TryConnect(ref NetworkEndpoint endpoint, ref ConnectionId underlyingConnection); + + /// + /// Tries to disconnect a connection in the underlying layer. + /// + /// The connection to disconnect to. + /// Returns true if the connection has been fully disconnected. + bool TryDisconnect(ref ConnectionId connectionId); + + /// + /// Gets the list of disconnections in the underlying layer for the current update. + /// + /// The allocator to use for the NativeArray + /// Returns a NativeArray with the disconnections of the underlying layer. + public NativeArray QueryFinishedDisconnections(Allocator allocator); + } + + internal struct NullUnderlyingConnectionList : IUnderlyingConnectionList + { + public bool TryConnect(ref NetworkEndpoint endpoint, ref ConnectionId underlyingConnection) + { + underlyingConnection = default; + return true; + } + + public bool TryDisconnect(ref ConnectionId connectionId) => true; + + public NativeArray QueryFinishedDisconnections(Allocator allocator) => default; + } + + internal struct UnderlyingConnectionList : IUnderlyingConnectionList + { + private ConnectionList Connections; + + public UnderlyingConnectionList(ref ConnectionList connections) + { + Connections = connections; + } + + public bool TryConnect(ref NetworkEndpoint endpoint, ref ConnectionId underlyingConnection) + { + if (underlyingConnection != default) + { + if (Connections.GetConnectionState(underlyingConnection) == NetworkConnection.State.Connected) + return true; + } + else + { + underlyingConnection = Connections.StartConnecting(ref endpoint); + } + return false; + } + + public bool TryDisconnect(ref ConnectionId connectionId) + { + var state = Connections.GetConnectionState(connectionId); + if (state == NetworkConnection.State.Disconnected) + return true; + + if (state != NetworkConnection.State.Disconnecting) + { + Connections.StartDisconnecting(ref connectionId); + } + + return false; + } + + public NativeArray QueryFinishedDisconnections(Allocator allocator) + => Connections.QueryFinishedDisconnections(allocator); + } +} diff --git a/Runtime/UnderlyingConnectionList.cs.meta b/Runtime/UnderlyingConnectionList.cs.meta new file mode 100644 index 0000000..8a62e92 --- /dev/null +++ b/Runtime/UnderlyingConnectionList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a987b764e0fb946e3b8e379373082b88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/Runtime/Unity.Networking.Transport.asmdef b/Runtime/Unity.Networking.Transport.asmdef index 944daf0..a29627e 100644 --- a/Runtime/Unity.Networking.Transport.asmdef +++ b/Runtime/Unity.Networking.Transport.asmdef @@ -1,11 +1,17 @@ { "name": "Unity.Networking.Transport", + "rootNamespace": "", "references": [ - "Unity.Burst", - "Unity.Collections", - "Unity.Mathematics" + "Unity.Burst", + "Unity.Collections", + "Unity.Mathematics" ], "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": true + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "noEngineReferences": false } diff --git a/Runtime/UnityTransportProtocol.cs b/Runtime/UnityTransportProtocol.cs deleted file mode 100644 index 0577c1e..0000000 --- a/Runtime/UnityTransportProtocol.cs +++ /dev/null @@ -1,379 +0,0 @@ -using System; -using AOT; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Protocols; -using UnityEngine.Assertions; - -namespace Unity.Networking.Transport -{ - [BurstCompile] - internal struct UnityTransportProtocol : INetworkProtocol - { - public void Initialize(NetworkSettings settings) {} - public void Dispose() {} - - public int Bind(INetworkInterface networkInterface, ref NetworkInterfaceEndPoint localEndPoint) - { - if (networkInterface.Bind(localEndPoint) != 0) - return -1; - - return 0; - } - - public int CreateConnectionAddress(INetworkInterface networkInterface, NetworkEndPoint remoteEndpoint, out NetworkInterfaceEndPoint remoteAddress) - { - remoteAddress = default; - return networkInterface.CreateInterfaceEndPoint(remoteEndpoint, out remoteAddress); - } - - public NetworkEndPoint GetRemoteEndPoint(INetworkInterface networkInterface, NetworkInterfaceEndPoint address) - { - return networkInterface.GetGenericEndPoint(address); - } - - public NetworkProtocol CreateProtocolInterface() - { - return new NetworkProtocol( - computePacketOverhead: new TransportFunctionPointer(ComputePacketOverhead), - processReceive: new TransportFunctionPointer(ProcessReceive), - processSend: new TransportFunctionPointer(ProcessSend), - processSendConnectionAccept: new TransportFunctionPointer(ProcessSendConnectionAccept), - connect: new TransportFunctionPointer(Connect), - disconnect: new TransportFunctionPointer(Disconnect), - processSendPing: new TransportFunctionPointer(ProcessSendPing), - processSendPong: new TransportFunctionPointer(ProcessSendPong), - update: new TransportFunctionPointer(Update), - needsUpdate: false, // Update is no-op - userData: IntPtr.Zero, - maxHeaderSize: UdpCHeader.Length, - maxFooterSize: SessionIdToken.k_Length - ); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ComputePacketOverheadDelegate))] - public static int ComputePacketOverhead(ref NetworkDriver.Connection connection, out int dataOffset) - { - dataOffset = UdpCHeader.Length; - var footerSize = connection.DidReceiveData == 0 ? SessionIdToken.k_Length : 0; - return dataOffset + footerSize; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessReceiveDelegate))] - public static void ProcessReceive(IntPtr stream, ref NetworkInterfaceEndPoint endpoint, int size, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData, ref ProcessPacketCommand command) - { - unsafe - { - var data = (byte*)stream; - var header = *(UdpCHeader*)data; - - if (size < UdpCHeader.Length) - { - UnityEngine.Debug.LogError("Received an invalid message header"); - command.Type = ProcessPacketCommandType.Drop; - return; - } - - var type = (UdpCProtocol)header.Type; - - command.Address = endpoint; - command.SessionId = header.SessionToken; - - if (type != UdpCProtocol.Data && (header.Flags & UdpCHeader.HeaderFlags.HasPipeline) != 0) - { - UnityEngine.Debug.LogError("Received an invalid non-data message with a pipeline"); - command.Type = ProcessPacketCommandType.Drop; - return; - } - - switch (type) - { - case UdpCProtocol.ConnectionAccept: - if ((header.Flags & UdpCHeader.HeaderFlags.HasConnectToken) == 0) - { - UnityEngine.Debug.LogError("Received an invalid ConnectionAccept without a token"); - command.Type = ProcessPacketCommandType.Drop; - return; - } - - if (size != UdpCHeader.Length + SessionIdToken.k_Length) - { - UnityEngine.Debug.LogError("Received an invalid ConnectionAccept with wrong length"); - command.Type = ProcessPacketCommandType.Drop; - return; - } - - command.Type = ProcessPacketCommandType.ConnectionAccept; - command.As.ConnectionAccept.ConnectionToken = *(SessionIdToken*)(stream + UdpCHeader.Length); - return; - - case UdpCProtocol.ConnectionReject: - command.Type = ProcessPacketCommandType.ConnectionReject; - return; - - case UdpCProtocol.ConnectionRequest: - command.Type = ProcessPacketCommandType.ConnectionRequest; - return; - - case UdpCProtocol.Disconnect: - command.Type = ProcessPacketCommandType.Disconnect; - return; - - case UdpCProtocol.Ping: - command.Type = ProcessPacketCommandType.Ping; - return; - - case UdpCProtocol.Pong: - command.Type = ProcessPacketCommandType.Pong; - return; - - case UdpCProtocol.Data: - var payloadLength = size - UdpCHeader.Length; - var hasPipeline = (header.Flags & UdpCHeader.HeaderFlags.HasPipeline) != 0 ? (byte)1 : (byte)0; - var hasConnectionToken = (header.Flags & UdpCHeader.HeaderFlags.HasConnectToken) != 0; - - if (hasConnectionToken) - { - payloadLength -= SessionIdToken.k_Length; - command.Type = ProcessPacketCommandType.DataWithImplicitConnectionAccept; - command.As.DataWithImplicitConnectionAccept.Offset = UdpCHeader.Length; - command.As.DataWithImplicitConnectionAccept.Length = payloadLength; - command.As.DataWithImplicitConnectionAccept.HasPipelineByte = hasPipeline; - command.As.DataWithImplicitConnectionAccept.ConnectionToken = *(SessionIdToken*)(stream + UdpCHeader.Length + payloadLength); - return; - } - else - { - command.Type = ProcessPacketCommandType.Data; - command.As.Data.Offset = UdpCHeader.Length; - command.As.Data.Length = payloadLength; - command.As.Data.HasPipelineByte = hasPipeline; - return; - } - } - - command.Type = ProcessPacketCommandType.Drop; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendDelegate))] - public static int ProcessSend(ref NetworkDriver.Connection connection, bool hasPipeline, ref NetworkSendInterface sendInterface, ref NetworkInterfaceSendHandle sendHandle, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - WriteSendMessageHeader(ref connection, hasPipeline, ref sendHandle, 0); - return sendInterface.EndSendMessage.Ptr.Invoke(ref sendHandle, ref connection.Address, sendInterface.UserData, ref queueHandle); - } - - internal static unsafe int WriteSendMessageHeader(ref NetworkDriver.Connection connection, bool hasPipeline, ref NetworkInterfaceSendHandle sendHandle, int offset) - { - unsafe - { - var flags = default(UdpCHeader.HeaderFlags); - var capacity = sendHandle.capacity - offset; - var size = sendHandle.size - offset; - - if (connection.DidReceiveData == 0) - { - flags |= UdpCHeader.HeaderFlags.HasConnectToken; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (size + SessionIdToken.k_Length > capacity) - throw new InvalidOperationException("SendHandle capacity overflow"); -#endif - SessionIdToken* connectionToken = (SessionIdToken*)((byte*)sendHandle.data + sendHandle.size); - *connectionToken = connection.ReceiveToken; - sendHandle.size += SessionIdToken.k_Length; - } - - if (hasPipeline) - { - flags |= UdpCHeader.HeaderFlags.HasPipeline; - } - - UdpCHeader* header = (UdpCHeader*)(sendHandle.data + offset); - *header = new UdpCHeader - { - Type = (byte)UdpCProtocol.Data, - SessionToken = connection.SendToken, - Flags = flags - }; - - return sendHandle.size - offset; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendConnectionAcceptDelegate))] - public static void ProcessSendConnectionAccept(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - NetworkInterfaceSendHandle sendHandle; - if (sendInterface.BeginSendMessage.Ptr.Invoke(out sendHandle, sendInterface.UserData, UdpCHeader.Length + SessionIdToken.k_Length) != 0) - { - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet"); - return; - } - - byte* packet = (byte*)sendHandle.data; - var size = WriteConnectionAcceptMessage(ref connection, packet, sendHandle.capacity); - - if (size < 0) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet"); - return; - } - - sendHandle.size = size; - - if (sendInterface.EndSendMessage.Ptr.Invoke(ref sendHandle, ref connection.Address, sendInterface.UserData, ref queueHandle) < 0) - { - UnityEngine.Debug.LogError("Failed to send a ConnectionAccept packet"); - return; - } - } - } - - internal static int GetConnectionAcceptMessageMaxLength() => UdpCHeader.Length + SessionIdToken.k_Length; - - internal static unsafe int WriteConnectionAcceptMessage(ref NetworkDriver.Connection connection, byte* packet, int capacity) - { - var size = UdpCHeader.Length; - - if (connection.DidReceiveData == 0) - size += SessionIdToken.k_Length; - - if (size > capacity) - { - UnityEngine.Debug.LogError("Failed to create a ConnectionAccept packet: size exceeds capacity"); - return -1; - } - - var header = (UdpCHeader*)packet; - *header = new UdpCHeader - { - Type = (byte)UdpCProtocol.ConnectionAccept, - SessionToken = connection.SendToken, - Flags = 0 - }; - - if (connection.DidReceiveData == 0) - { - header->Flags |= UdpCHeader.HeaderFlags.HasConnectToken; - *(SessionIdToken*)(packet + UdpCHeader.Length) = connection.ReceiveToken; - } - - Assert.IsTrue(size <= GetConnectionAcceptMessageMaxLength()); - - return size; - } - - private static unsafe int SendHeaderOnlyMessage(UdpCProtocol type, SessionIdToken token, ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle) - { - NetworkInterfaceSendHandle sendHandle; - if (sendInterface.BeginSendMessage.Ptr.Invoke(out sendHandle, sendInterface.UserData, UdpCHeader.Length) != 0) - { - return -1; - } - - byte* packet = (byte*)sendHandle.data; - sendHandle.size = UdpCHeader.Length; - if (sendHandle.size > sendHandle.capacity) - { - sendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, sendInterface.UserData); - return -1; - } - - var header = (UdpCHeader*)packet; - *header = new UdpCHeader - { - Type = (byte)type, - SessionToken = token, - Flags = 0 - }; - - - if (sendInterface.EndSendMessage.Ptr.Invoke(ref sendHandle, ref connection.Address, sendInterface.UserData, ref queueHandle) < 0) - { - return -1; - } - - return UdpCHeader.Length; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ConnectDelegate))] - public static void Connect(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var type = UdpCProtocol.ConnectionRequest; - var token = connection.ReceiveToken; - var res = SendHeaderOnlyMessage(type, token, ref connection, ref sendInterface, ref queueHandle); - if (res == -1) - { - UnityEngine.Debug.LogError("Failed to send ConnectionRequest message"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.DisconnectDelegate))] - public static void Disconnect(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var type = UdpCProtocol.Disconnect; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, ref connection, ref sendInterface, ref queueHandle); - if (res == -1) - { - UnityEngine.Debug.LogError("Failed to send Disconnect message"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPingDelegate))] - public static void ProcessSendPing(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var type = UdpCProtocol.Ping; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, ref connection, ref sendInterface, ref queueHandle); - if (res == -1) - { - UnityEngine.Debug.LogError("Failed to send Ping message"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.ProcessSendPongDelegate))] - public static void ProcessSendPong(ref NetworkDriver.Connection connection, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - unsafe - { - var type = UdpCProtocol.Pong; - var token = connection.SendToken; - var res = SendHeaderOnlyMessage(type, token, ref connection, ref sendInterface, ref queueHandle); - if (res == -1) - { - UnityEngine.Debug.LogError("Failed to send Pong message"); - } - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkProtocol.UpdateDelegate))] - public static void Update(long updateTime, ref NetworkSendInterface sendInterface, ref NetworkSendQueueHandle queueHandle, IntPtr userData) - { - // No-op - } - } -} diff --git a/Runtime/UnityTransportProtocol.cs.meta b/Runtime/UnityTransportProtocol.cs.meta deleted file mode 100644 index 8a6f29a..0000000 --- a/Runtime/UnityTransportProtocol.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5373d19bca88ce348bb1ee168c4ae441 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Utilities.cs b/Runtime/Utilities.cs index 82ef48f..5fa4cfa 100644 --- a/Runtime/Utilities.cs +++ b/Runtime/Utilities.cs @@ -1,12 +1,13 @@ using System; -using System.Diagnostics; +using System.Runtime.CompilerServices; +using Unity.Baselib.LowLevel; using Unity.Collections; namespace Unity.Networking.Transport.Utilities { /// /// A NativeMultiQueue is a set of several FIFO queues split into buckets. - /// Each bucket has its own first and last item, and each bucket can have + /// Each bucket has its own first and last item and each bucket can have /// items pushed and popped individually. /// internal struct NativeMultiQueue : IDisposable where T : unmanaged @@ -15,17 +16,13 @@ internal struct NativeMultiQueue : IDisposable where T : unmanaged private NativeList m_QueueHeadTail; private NativeArray m_MaxItems; - /// - /// Whether this queue has been allocated (and not yet deallocated). - /// - /// True if this queue has been allocated (and not yet deallocated). public bool IsCreated => m_Queue.IsCreated; /// - /// Instantiates a new NativeMultiQueue which has a single bucket and the - /// specified capacity for the number of items for that bucket. Accessing buckets - /// out of range will grow the number of buckets, and pushing more items than the - /// initial capacity will increase the number of items for each bucket. + /// New NativeMultiQueue has a single bucket and the specified number + /// of items for that bucket. Accessing buckets out of range will grow + /// the number of buckets and pushing more items than the initial capacity + /// will increase the number of items for each bucket. /// public NativeMultiQueue(int initialMessageCapacity) { @@ -35,9 +32,6 @@ public NativeMultiQueue(int initialMessageCapacity) m_QueueHeadTail = new NativeList(2, Allocator.Persistent); } - /// - /// Releases all resources (memory and safety handles). - /// public void Dispose() { m_MaxItems.Dispose(); @@ -46,10 +40,9 @@ public void Dispose() } /// - /// Enqueue a new item to a specific bucket. If the specified bucket is larger - /// than the current amount of buckets, the queue's number of buckets will be - /// increased to match. If enqueueing the item would exceed the queue's capacity, - /// the queue's capacity will be increased. + /// Enqueue a new item to a specific bucket. If the bucket does not yet exist + /// the number of buckets will be increased and if the queue is full the number + /// of items for each bucket will be increased. /// public void Enqueue(int bucket, T value) { @@ -84,8 +77,8 @@ public void Enqueue(int bucket, T value) } /// - /// Dequeue an item from a specific bucket. If the bucket does not exist, or if the - /// bucket is empty, the call will fail and return false. + /// Dequeue an item from a specific bucket. If the bucket does not exist or if the + /// bucket is empty the call will fail and return false. /// public bool Dequeue(int bucket, out T value) { @@ -115,8 +108,8 @@ public bool Dequeue(int bucket, out T value) } /// - /// Peek the next item in a specific bucket. If the bucket does not exist, or if the - /// bucket is empty, the call will fail and return false. + /// Peek the next item in a specific bucket. If the bucket does not exist or if the + /// bucket is empty the call will fail and return false. /// public bool Peek(int bucket, out T value) { @@ -137,7 +130,7 @@ public bool Peek(int bucket, out T value) } /// - /// Remove all items from a specific bucket. If the bucket does not exist, + /// Remove all items from a specific bucket. If the bucket does not exist /// the call will not do anything. /// public void Clear(int bucket) @@ -149,19 +142,9 @@ public void Clear(int bucket) } } - /// - /// Utility class used when dealing with sequenced pipeline stages. - /// - /// public static class SequenceHelpers { - /// - /// Calculate the difference between two sequence IDs, taking integer overflow/underflow into account. - /// For example, both AbsDistance(65535, 0) and AbsDistance(0, 65535) will return 1, not 65535. - /// - /// The first sequence ID. Compared against the second. - /// The second sequence ID. Compared against the first. - /// An integer value equal to the distance between the sequence IDs. + // Calculate difference between the sequence IDs taking into account wrapping, so when you go from 65535 to 0 the distance is 1 public static int AbsDistance(ushort lhs, ushort rhs) { int distance; @@ -172,28 +155,12 @@ public static int AbsDistance(ushort lhs, ushort rhs) return distance; } - /// - /// This method was originally added in February 2019, but does not seem to be used anywhere currently. - /// Its original context seems to have been intended for a very simple version of checking whether a - /// packet's sequence ID was equal to or newer than the last received packet. - /// - /// The sequence ID of a newly-arrived packet to check - /// The sequence ID of a previously received packet - /// true if current is newer than old public static bool IsNewer(uint current, uint old) { // Invert the check so same does not count as newer return !(old - current < (1u << 31)); } - /// - /// Describes whether the non-wrapping difference between two sequenceIDs is - /// less than 2^15 (or 0x8000, or 32768). (The "16" seems to be the 16th bit - /// in a 16-bit integer.) - /// - /// The first operand. - /// The second operand. - /// Whether or not the non-wrapping difference between the two operands is less than or equal to unsigned 0x7FFF. public static bool GreaterThan16(ushort lhs, ushort rhs) { const uint max_sequence_divide_2 = 0x7FFF; @@ -201,38 +168,16 @@ public static bool GreaterThan16(ushort lhs, ushort rhs) lhs < rhs && rhs - lhs > (ushort)max_sequence_divide_2; } - /// - /// Describes whether the non-absolute difference between two sequenceIDs is - /// greater than or equal to 2^15 (or 0x8000, or 32768). (The "16" seems to - /// be the 16th bit in a 16-bit integer.) - /// - /// The first operand. - /// The second operand. - /// Whether or not the non-wrapping difference between the two operands is greater than unsigned 0x7FFF. public static bool LessThan16(ushort lhs, ushort rhs) { return GreaterThan16(rhs, lhs); } - /// - /// Describes whether a packet is stale in the context of sequenced pipelines. - /// - /// The more recent sequence ID. - /// The older sequence ID. - /// The window size - /// A boolean value containing the results of where lhs = sequence and rhs = oldSequence - windowSize. public static bool StalePacket(ushort sequence, ushort oldSequence, ushort windowSize) { return LessThan16(sequence, (ushort)(oldSequence - windowSize)); } - /// - /// Converts a bitmask integer to a string representation of its binary expression, e.g. - /// a mask value of 4 will return a string with the 3rd bit set: - /// 00000000000000000000000000000100 - /// - /// The bitmask in integer format. - /// A string that represents the bitmask. public static string BitMaskToString(uint mask) { // 31 24 16 8 0 @@ -252,18 +197,8 @@ public static string BitMaskToString(uint mask) } } - /// - /// Provides Extension methods for FixedStrings - /// public static class FixedStringHexExt { - /// - /// Appends the hex using the specified str - /// - /// The string type. Has constraints where it must be a struct, an INativeList and IUTF8Bytes. - /// The string of type T. Passed in by reference, and will be modified by this method. - /// The ushort representation of the hex value to convert to its string representation and append to T. - /// The from the attempt to modify , either None or Overflow. public static FormatError AppendHex(ref this T str, ushort val) where T : unmanaged, INativeList, IUTF8Bytes { int shamt = 12; @@ -288,9 +223,6 @@ public static FormatError AppendHex(ref this T str, ushort val) where T : unm } } - /// - /// Provides Extension methods for the class - /// public static class NativeListExt { /// @@ -302,7 +234,7 @@ public static class NativeListExt /// Requested size that should fit into list public static void ResizeUninitializedTillPowerOf2(this NativeList list, int sizeToFit) where T : unmanaged { - var n = list.Length; + var n = list.Capacity; if (sizeToFit >= n) { @@ -315,31 +247,78 @@ public static void ResizeUninitializedTillPowerOf2(this NativeList list, i sizeToFit++; //sizeToFit is now next power of 2 of initial sizeToFit - list.ResizeUninitialized(sizeToFit); + list.Capacity = sizeToFit; } } } - - /// - /// A simple method to obtain a random ushort provided by the class. - /// public static class RandomHelpers { - /// a ushort in [1..ushort.MaxValue - 1] range + // returns ushort in [1..ushort.MaxValue] range public static ushort GetRandomUShort() { - var rnd = new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp()); +#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 } - /// a ushort in [1..uint.MaxValue - 1] range + // returns ulong in [1..ulong.MaxValue] range public static ulong GetRandomULong() { - var rnd = new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp()); +#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 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()); + + for (int i = 0; i < ConnectionToken.k_Length; i++) + token.Value[i] = (byte)(rnd.NextUInt() & 0xFF); +#endif + + return token; + } + } + + internal static class TimerHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong GetTicks() + { + return Binding.Baselib_Timer_GetHighPrecisionTimerTicks(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long GetCurrentTimestampMS() + { + // Normally we'd use Baselib_Timer_GetTicksToNanosecondsConversionRatio for more precise + // timestamp calculations, but it can't be used in DOTS Runtime (yet) because its + // bindings link directly to the version returning a struct, whereas normal bindings use + // the injected version that returns the struct through an out parameter. + return (long)(Binding.Baselib_Timer_GetTimeSinceStartupInSeconds() * 1000); + } + + // Used in tests to sleep inside Burst-compiled code. + internal static void Sleep(uint ms) + { + Binding.Baselib_Timer_WaitForAtLeast(ms); + } } } diff --git a/Tests/Editor/Utilities.meta b/Runtime/Utilities.meta similarity index 77% rename from Tests/Editor/Utilities.meta rename to Runtime/Utilities.meta index b6dc595..2356f00 100644 --- a/Tests/Editor/Utilities.meta +++ b/Runtime/Utilities.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 211d98bb19d89bb428e8025880ae32e6 +guid: 4053f9377195b4922a11e58d444b8cae folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/Utilities/ManagedCallWrapper.cs b/Runtime/Utilities/ManagedCallWrapper.cs new file mode 100644 index 0000000..ca82165 --- /dev/null +++ b/Runtime/Utilities/ManagedCallWrapper.cs @@ -0,0 +1,80 @@ +using System; +using System.Runtime.InteropServices; +using AOT; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Networking.Transport +{ + // There are situations where we need to call managed function pointers (delegate *managed<>) + // from an unmanged context, for instance if we require a function pointer with type arguments -- because + // unmanaged function pointers don't allow generic types. + // + // This struct provides an unmanaged function pointer that receives a managed function pointer + // with a void* argument. That managed function pointer is then called on Invoke() with whatever + // arguments we need to pass, and all arguments are passed as a void* pointer + size. + internal unsafe struct ManagedCallWrapper + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void MethodDelegate(void* functionPtr, void* arguments, int argumentsSize); + + [MonoPInvokeCallback(typeof(MethodDelegate))] + private static void Method(void* functionPtr, void* arguments, int argumentsSize) + => ((delegate * < void*, int, void >)functionPtr)(arguments, argumentsSize); + + private static IntPtr s_CachedWrapperPtr; + + private static void Initialize() + { + if (s_CachedWrapperPtr != default) + return; + + var methodDelegate = new MethodDelegate(Method); + GCHandle.Alloc(methodDelegate); + s_CachedWrapperPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); + } + + [NativeDisableUnsafePtrRestriction] IntPtr m_ManagedFunctionPtr; + [NativeDisableUnsafePtrRestriction] IntPtr m_WrapperPtr; + + public bool IsCreated => m_ManagedFunctionPtr != default; + + /// + /// Creates a wrapper for a managed function pointer that can be called from unmanged context + /// + /// + /// The function pointer of a method that receives a void* containing + /// the arguments and an int containing the size in bytes of those arguments. + /// + public ManagedCallWrapper(delegate* < void*, int, void > managedFunctionPtr) + { + Initialize(); + m_WrapperPtr = s_CachedWrapperPtr; + m_ManagedFunctionPtr = new IntPtr(managedFunctionPtr); + } + + public void Invoke(void* arguments, int argumentsSize) + { + if (m_ManagedFunctionPtr == default) + throw new NullReferenceException("Trying to invoke a null function pointer"); + + ((delegate * unmanaged[Cdecl] < void*, void*, int, void >)m_WrapperPtr)(((void*)m_ManagedFunctionPtr), arguments, argumentsSize); + } + + public void Invoke(ref T arguments) where T : unmanaged + { + fixed(void* argumentsPtr = &arguments) + { + Invoke(argumentsPtr, UnsafeUtility.SizeOf()); + } + } + + public static ref A ArgumentsFromPtr(void* argumentsPtr, int size) where A : unmanaged + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (size != UnsafeUtility.SizeOf()) + throw new InvalidOperationException("The requested argument type size does not match the provided one"); +#endif + return ref *(A*)argumentsPtr; + } + } +} diff --git a/Runtime/Utilities/ManagedCallWrapper.cs.meta b/Runtime/Utilities/ManagedCallWrapper.cs.meta new file mode 100644 index 0000000..370fdac --- /dev/null +++ b/Runtime/Utilities/ManagedCallWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1de49823f6c7c49b8b90bb6a34e7e88b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Utilities/ManagedReference.cs b/Runtime/Utilities/ManagedReference.cs new file mode 100644 index 0000000..9867064 --- /dev/null +++ b/Runtime/Utilities/ManagedReference.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; + +namespace Unity.Networking.Transport.Utilities +{ + /// + /// Stores a managed object into a static array and keeps a reference to it into + /// a unmanaged struct, so the managed reference can be passed to burst context. + /// Access to the referenced is still not burst compatible. + /// + /// The type of the object to store. + internal struct ManagedReference : IDisposable + { + private class ElementSlot + { + public T Element; + } + + private static List s_ElementList = new List(); + + private static int AllocateElement(ref T element) + { + var count = s_ElementList.Count; + var slot = new ElementSlot { Element = element }; + var index = s_ElementList.FindIndex(0, count, e => e == null); + + if (index >= 0) + { + s_ElementList[index] = slot; + return index; + } + + s_ElementList.Add(slot); + return count; + } + + private static void DeallocateElement(int index) + { + s_ElementList[index] = null; + } + + private NativeReference m_ElementIndex; + + public ref T Element => ref s_ElementList[m_ElementIndex.Value].Element; + + public ManagedReference(ref T element) + { + m_ElementIndex = new NativeReference(AllocateElement(ref element), Allocator.Persistent); + } + + public void Dispose() + { + if (m_ElementIndex.IsCreated) + { + DeallocateElement(m_ElementIndex.Value); + m_ElementIndex.Dispose(); + } + } + } +} diff --git a/Runtime/Utilities/ManagedReference.cs.meta b/Runtime/Utilities/ManagedReference.cs.meta new file mode 100644 index 0000000..3c64d93 --- /dev/null +++ b/Runtime/Utilities/ManagedReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee32e0ec7217746c082eba452fb768de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface.meta b/Runtime/WebSocket.meta similarity index 77% rename from Samples~/CustomNetworkInterface.meta rename to Runtime/WebSocket.meta index e34ef9e..6e02c2f 100644 --- a/Samples~/CustomNetworkInterface.meta +++ b/Runtime/WebSocket.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3a4608c4547e7144688fa6d3ca3fb299 +guid: 30e5a732f0adcf547806c189c9c14b4c folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/WebSocket/WebSocket.cs b/Runtime/WebSocket/WebSocket.cs new file mode 100644 index 0000000..4c2c00c --- /dev/null +++ b/Runtime/WebSocket/WebSocket.cs @@ -0,0 +1,739 @@ +#if !UNITY_WEBGL || UNITY_EDITOR + +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Networking.Transport +{ + internal static class WebSocket + { + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void Warn(string msg) => UnityEngine.Debug.LogWarning(msg); + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void WarnIf(bool condition, string msg) { if (condition) UnityEngine.Debug.LogWarning(msg); } + + public enum Opcode + { + Continuation = 0, + TextData = 1, + BinaryData = 2, + Close = 8, + Ping = 9, + Pong = 10 + } + + public enum StatusCode + { + Normal = 1000, + ProtocolError = 1002, + UnsupportedDataType = 1003, + MessageTooBig = 1009, + InternalError = 1011, + } + + public enum State + { + None, + ClosedAndFlushed, + Closed, + Closing, + Opening, + Open, + } + + public enum Role + { + Server = 0, // incoming (passive) connection + Client = 1, // outgoing (active) connection + } + + public static unsafe void Connect(ref Buffer buffer, ref NetworkEndpoint remoteEndpoint, ref Keys keys) + { + FixedString32Bytes end = "\r\n"; + FixedString512Bytes handshake = "GET / HTTP/1.1\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\n"; + FixedString32Bytes key = "Sec-WebSocket-Key: "; + handshake.Append(key); + GenerateBase64Key(out key, ref keys); + handshake.Append(key); + handshake.Append(end); + FixedString128Bytes host = "Host: "; + handshake.Append(host); + handshake.Append(NetworkEndpoint.AddressToString(ref remoteEndpoint.rawNetworkAddress)); + handshake.Append(end); + handshake.Append(end); + + fixed (byte* data = buffer.Data) + { + UnsafeUtility.MemCpy(data, handshake.GetUnsafePtr(), (uint)handshake.Length); + } + buffer.Length = handshake.Length; + } + + public static unsafe State Handshake(ref Buffer recvbuffer, ref Buffer sendbuffer, bool isClient, ref Keys keys) + { + if (recvbuffer.Length == 0) + return State.Opening; + + // If we can't find an ETX the message is not complete yet. + var complete = false; + int length; + for (length = 0; length < recvbuffer.Length - 3 && !complete; ++length) + complete = recvbuffer.Data[length] == '\r' + && recvbuffer.Data[length + 1] == '\n' + && recvbuffer.Data[length + 2] == '\r' + && recvbuffer.Data[length + 3] == '\n'; + + if (!complete) + return State.Opening; + + length += 3; + fixed (byte* data = recvbuffer.Data) + { + // If you need to inspect this while debugging open the Immediate Window in Visual Studio and enter + // "System.Text.Encoding.UTF8.GetString(recvHandshake)" + var recvHandshake = data; + var recvHandshakeLength = length; + + int lineStart = 0; + int lineEnd = 0; + while (recvHandshake[lineEnd] != '\r' || recvHandshake[lineEnd + 1] != '\n') + ++lineEnd; + + int firstLineEnd = lineEnd; + var headerLookup = new NativeParallelHashMap(16, Allocator.Temp); + while (true) + { + lineEnd += 2; + lineStart = lineEnd; + while (recvHandshake[lineEnd] != '\r' || recvHandshake[lineEnd + 1] != '\n') + ++lineEnd; + + if (lineStart == lineEnd) + break; + + // Found a line - analyze it + int keyStart = lineStart; + while (recvHandshake[keyStart] == ' ' || recvHandshake[keyStart] == '\t') + ++keyStart; + + int keyEnd = keyStart; + FixedString512Bytes key = default; + + // Not allowing whitespace in keys + while (recvHandshake[keyEnd] != ':' && recvHandshake[keyEnd] != ' ' + && recvHandshake[keyEnd] != '\t' && recvHandshake[keyEnd] != '\r' + && recvHandshake[keyEnd] != '\n') + { + byte ch = recvHandshake[keyEnd]; + if (ch >= (byte)'A' && ch <= (byte)'Z') + ch = (byte)(ch + 'a' - 'A'); + key.Add(ch); + ++keyEnd; + } + + int valueStart = keyEnd; + while (recvHandshake[valueStart] != ':') + { + if (recvHandshake[valueStart] != ' ' && recvHandshake[valueStart] != '\t' + && recvHandshake[valueStart] != '\r' && recvHandshake[valueStart] != '\n') + break; + + ++valueStart; + } + if (recvHandshake[valueStart] != ':') + continue; + + ++valueStart; + while (recvHandshake[valueStart] == ' ' || recvHandshake[valueStart] == '\t') + ++valueStart; + + FixedString512Bytes value = default; + int valueEnd = valueStart; + while (valueEnd < recvHandshakeLength && recvHandshake[valueEnd] != '\r') + { + if (value.Length == value.Capacity) + { + Warn("Received invalid http message for handshake: too large for string buffer."); + return State.Closed; + } + + value.Add(recvHandshake[valueEnd]); + ++valueEnd; + } + + // Trim trailing whitespace + while (value.Length > 0 && (value[value.Length - 1] == ' ' || value[value.Length - 1] == '\t')) + value.Length = value.Length - 1; + + headerLookup.TryAdd(key, value); + } + + FixedString128Bytes keyMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + FixedString128Bytes connectionHeader = "connection"; + FixedString128Bytes upgradeHeader = "upgrade"; + FixedString512Bytes headerValue; + var invalidConnection = !headerLookup.TryGetValue(connectionHeader, out headerValue) || headerValue.Length < 7; + // Scan for "upgrade" in a coma separated list + if (!invalidConnection) + { + invalidConnection = true; + int upPos = 0; + int len = 0; + while ((len = headerValue.Length - upPos) >= 7) + { + invalidConnection = ((headerValue[upPos + 0] | 32) != 'u' || (headerValue[upPos + 1] | 32) != 'p' || (headerValue[upPos + 2] | 32) != 'g' || + (headerValue[upPos + 3] | 32) != 'r' || (headerValue[upPos + 4] | 32) != 'a' || (headerValue[upPos + 5] | 32) != 'd' || (headerValue[upPos + 6] | 32) != 'e'); + if (!invalidConnection) + { + if (len == 7 || headerValue[upPos + 7] == ',' || headerValue[upPos + 7] == ' ' || headerValue[upPos + 7] == '\t') + break; + invalidConnection = true; + } + while (upPos < headerValue.Length && headerValue[upPos] != ',') + ++upPos; + // Skip , + ++upPos; + // skip whitespace + while (upPos < headerValue.Length && (headerValue[upPos] == ' ' || headerValue[upPos] == '\t')) + ++upPos; + } + } + var invalidUpgrade = (!headerLookup.TryGetValue(upgradeHeader, out headerValue) || headerValue.Length != 9 || + (headerValue[0] | 32) != 'w' || (headerValue[1] | 32) != 'e' || (headerValue[2] | 32) != 'b' || (headerValue[3] | 32) != 's' || (headerValue[4] | 32) != 'o' || (headerValue[5] | 32) != 'c' || (headerValue[6] | 32) != 'k' || (headerValue[7] | 32) != 'e' || (headerValue[8] | 32) != 't'); + + // Try to parse the xpecetd handshake message: server expects HTTP UPGRADE; client expects + // HTTP SWITCHING PROTOCOL. + if (isClient) + { + var invalidStatusLine = (firstLineEnd < 14 || + recvHandshake[0] != 'H' || recvHandshake[1] != 'T' || recvHandshake[2] != 'T' || recvHandshake[3] != 'P' || + recvHandshake[4] != '/' || recvHandshake[5] != '1' || recvHandshake[6] != '.' || recvHandshake[7] != '1' || + recvHandshake[8] != ' ' || recvHandshake[9] != '1' || recvHandshake[10] != '0' || recvHandshake[11] != '1' || + recvHandshake[12] != ' '); + + if (invalidStatusLine) + { + Warn("Unexpected server response. Could not parse http status line."); + return State.Closed; + } + + FixedString128Bytes protocolHeader = "sec-websocket-protocol"; + FixedString128Bytes extensionHeader = "sec-websocket-extensions"; + FixedString128Bytes acceptHeader = "sec-websocket-accept"; + FixedString512Bytes wsKey; + + if (invalidConnection || invalidUpgrade || headerLookup.ContainsKey(protocolHeader) || + headerLookup.ContainsKey(extensionHeader) || !headerLookup.TryGetValue(acceptHeader, out wsKey)) + { + WarnIf(invalidConnection, "Received handshake with invalid or missing Connection key."); + WarnIf(invalidUpgrade, "Received handshake with invalid or missing Upgrade key."); + WarnIf(headerLookup.ContainsKey(protocolHeader), "Received handshake with a subprotocol != null."); + WarnIf(headerLookup.ContainsKey(extensionHeader), "Received handshake with an extension."); + WarnIf (!headerLookup.ContainsKey(acceptHeader), "Received handshake with a missing sec-websocket-accept key."); + + return State.Closed; + } + + // validate the accept header + FixedString512Bytes refWsKey = default; + GenerateBase64Key(out var clientKey, ref keys); + refWsKey.Append(clientKey); + refWsKey.Append(keyMagic); + var hash = new SHA1(refWsKey); + clientKey = hash.ToBase64(); + if (wsKey != clientKey) + { + Warn("Received handshake with incorrect sec-websocket-accept."); + return State.Closed; + } + } + else + { + FixedString512Bytes handshake; + FixedString128Bytes hostHeader = "host"; + FixedString128Bytes keyHeader = "sec-websocket-key"; + FixedString128Bytes versionHeader = "sec-websocket-version"; + var invalidRequestLine = (firstLineEnd < 14 || recvHandshake[0] != 'G' || recvHandshake[1] != 'E' || recvHandshake[2] != 'T' || recvHandshake[3] != ' ' || + recvHandshake[firstLineEnd - 9] != ' ' || + recvHandshake[firstLineEnd - 8] != 'H' || recvHandshake[firstLineEnd - 7] != 'T' || recvHandshake[firstLineEnd - 6] != 'T' || recvHandshake[firstLineEnd - 5] != 'P' || + recvHandshake[firstLineEnd - 4] != '/' || recvHandshake[firstLineEnd - 3] != '1' || recvHandshake[firstLineEnd - 2] != '.' || recvHandshake[firstLineEnd - 1] != '1'); + FixedString512Bytes wsKey; + var invalidVersion = (!headerLookup.TryGetValue(versionHeader, out headerValue) || headerValue.Length != 2 || + headerValue[0] != '1' || headerValue[1] != '3'); + var invalidKey = (!headerLookup.TryGetValue(keyHeader, out wsKey) || wsKey.Length != 24); + if (invalidRequestLine || !headerLookup.ContainsKey(hostHeader) || invalidKey || + invalidVersion || invalidConnection || invalidUpgrade) + { + WarnIf(invalidRequestLine, "Received handshake with invalid http request line."); + WarnIf (invalidVersion, "Received handshake with invalid or missing sec-websocket-version key."); + WarnIf(invalidConnection, "Received handshake with invalid or missing Connection key."); + WarnIf(invalidUpgrade, "Received handshake with invalid or missing Upgrade key."); + WarnIf(!headerLookup.ContainsKey(hostHeader), "Received handshake with a missing host key."); + WarnIf(invalidKey, "Received handshake with a missing or invalid sec-websocket-key key."); + + // Not a valid get request + handshake = "HTTP/1.1 400 Bad Request\r\nSec-WebSocket-Version: 13\r\n\r\n"; + if (sendbuffer.Available >= handshake.Length) + { + fixed(byte* destination = sendbuffer.Data) + UnsafeUtility.MemCpy(destination + sendbuffer.Length, handshake.GetUnsafePtr(), handshake.Length); + sendbuffer.Length += handshake.Length; + } + else + { + Warn("Insufficient send buffer."); + } + + return State.Closed; + } + // Only / is available + if (firstLineEnd != 14 || recvHandshake[4] != '/') + { + Warn("Received handshake with an incorrect resource name."); + + handshake = "HTTP/1.1 404 Not Found\r\n\r\n"; + if (sendbuffer.Available >= handshake.Length) + { + fixed(byte* destination = sendbuffer.Data) + UnsafeUtility.MemCpy(destination + sendbuffer.Length, handshake.GetUnsafePtr(), handshake.Length); + sendbuffer.Length += handshake.Length; + } + else + { + Warn("Insufficient send buffer."); + } + + return State.Closed; + } + + handshake = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"; + wsKey.Append(keyMagic); + var hash = new SHA1(new FixedString128Bytes(wsKey)); + FixedString128Bytes accept = "Sec-WebSocket-Accept: "; + handshake.Append(accept); + handshake.Append(hash.ToBase64()); + FixedString32Bytes end = "\r\n"; + handshake.Append(end); + handshake.Append(end); + if (sendbuffer.Available >= handshake.Length) + { + fixed(byte* destination = sendbuffer.Data) + UnsafeUtility.MemCpy(destination + sendbuffer.Length, handshake.GetUnsafePtr(), handshake.Length); + sendbuffer.Length += handshake.Length; + } + else + { + Warn("Insufficient send buffer."); + return State.Closed; + } + } + + // At this point tha handshake message has been parsed and can be discarded. Usually the handshake + // message will be the only thing in the buffer but in theory, it's possible for a server to reply + // with an HTTP SWITCHING PROTOCOL immediately followed by one or more WebSocket FRAMEs (PING, DATA + // or CLOSE) which might have been received all together in a single chunk by the underlying layer + // and would all sit in the recv buffer. + var pending = recvbuffer.Length - length; + if (pending > 0) + UnsafeUtility.MemMove(data, data + length, pending); + recvbuffer.Length = pending; + } + + return State.Open; + } + + public static unsafe bool Close(ref Buffer buffer, StatusCode status, bool useMask, uint mask) + { + var start = buffer.Length; + var code = (ushort)(BitConverter.IsLittleEndian ? (((ushort)status & 0xFF) << 8) | (((ushort)status >> 8) & 0xFF) : (ushort)status); + if (Binary(ref buffer, &code, sizeof(ushort), useMask, mask)) + { + buffer.Data[start] = 0x88; + return true; + } + return false; + } + + public static unsafe bool Ping(ref Buffer buffer, bool useMask, uint mask) + { + var start = buffer.Length; + if (Binary(ref buffer, (void*)0, 0, useMask, mask)) + { + buffer.Data[start] = 0x89; + return true; + } + return false; + } + + public static unsafe bool Pong(ref Buffer buffer, byte* payload, int payloadSize, bool useMask, uint mask) + { + var start = buffer.Length; + if (Binary(ref buffer, payload, payloadSize, useMask, mask)) + { + buffer.Data[start] = 0x8a; + return true; + } + return false; + } + + public static unsafe bool Binary(ref PacketProcessor packet, bool useMask, uint mask) + { + var destination = (byte*)packet.GetUnsafePayloadPtr(); + var offset = packet.Offset; + var capacity = packet.Capacity; + var length = packet.Length; + int headerLen = Header(destination, offset, capacity, length, useMask, mask); + if (headerLen > 0) + { + destination += offset; + offset -= headerLen; + length += headerLen; + if (useMask) + { + var maskBytes = destination - 4; + for (int i = 0; i < length; i++) + destination[i] ^= maskBytes[i & 3]; + } + packet.SetUnsafeMetadata(length, offset); + return true; + } + + return false; + } + + private static unsafe bool Binary(ref Buffer buffer, void* payload, int payloadLen, bool useMask, uint mask) + { + var padding = HeaderLen(payloadLen, useMask); + fixed(byte* ptr = buffer.Data) + { + var destination = ptr + buffer.Length; + var capacity = buffer.Available; + var headerLen = Header(destination, padding, capacity, payloadLen, useMask, mask); + if (headerLen <= 0) + return false; + + destination += headerLen; + if (useMask) + { + var maskBytes = destination - 4; + for (int i = 0; i < payloadLen; i++) + destination[i] = (byte)(((byte*)payload)[i] ^ maskBytes[i & 3]); + } + else + { + if (payloadLen > 0) + UnsafeUtility.MemCpy(destination, payload, payloadLen); + } + + buffer.Length += headerLen + payloadLen; + return true; + } + } + + private static int HeaderLen(int payloadLen, bool useMask) + { + int totalHeaderLen; + if (payloadLen < 126) + totalHeaderLen = 2; + else if (payloadLen <= 0xffff) + totalHeaderLen = 4; + else + totalHeaderLen = 10; + + if (useMask) + totalHeaderLen += 4; + + return totalHeaderLen; + } + + private static unsafe int Header(byte* destination, int padding, int capacity, int payloadLen, bool useMask, uint mask) + { + int totalHeaderLen = HeaderLen(payloadLen, useMask); + if (capacity < padding || capacity < totalHeaderLen + payloadLen || totalHeaderLen > padding) + return 0; + + destination += padding - totalHeaderLen; + + // fin + binary + *destination++ = 0x82; + (int maskLen, byte maskFlag) = useMask ? (4, (byte)0x80) : (0, (byte)0); + if (payloadLen < 126) + { + *destination++ = (byte)(maskFlag | payloadLen); + } + else if (payloadLen <= 0xffff) + { + *destination++ = (byte)(maskFlag | 126); + *destination++ = (byte)(payloadLen >> 8); + *destination++ = (byte)(payloadLen & 0xff); + } + else + { + *destination++ = (byte)(maskFlag | 127); + *destination++ = (byte)0; + *destination++ = (byte)0; + *destination++ = (byte)0; + *destination++ = (byte)0; + *destination++ = (byte)((payloadLen >> 24) & 0xff); + *destination++ = (byte)((payloadLen >> 16) & 0xff); + *destination++ = (byte)((payloadLen >> 8) & 0xff); + *destination++ = (byte)(payloadLen & 0xff); + } + + if (useMask) + { + *destination++ = (byte)(mask >> 24); + *destination++ = (byte)((mask >> 16) & 0xff); + *destination++ = (byte)((mask >> 8) & 0xff); + *destination++ = (byte)(mask & 0xff); + } + + return totalHeaderLen; + } + + // Per RFC6455 a single WebSocket frame has a maximum size limit of 2^63 bytes and a WebSocket message, + // made up of more than 1 frame has no limit imposed by the protocol. This layer, however, only supports + // handshake packets up to the 512 bytes and user payloads up to MTU - MaxHeaderSize(14). + + public const int MaxHeaderSize = 14; + public const int MaxPayloadSize = NetworkParameterConstants.MTU - MaxHeaderSize; + + public unsafe struct Keys + { + public fixed uint Key[4]; + } + + public unsafe struct Buffer + { + public const int Capacity = 2 * NetworkParameterConstants.MTU; + + public fixed byte Data[Capacity]; + public int Length; + public int Available => Capacity - Length; + } + + public unsafe struct Payload + { + public const int Capacity = MaxPayloadSize; + + public fixed byte Data[Capacity]; + public int Length; + public int Available => Capacity - Length; + } + + public struct Settings + { + public int ConnectTimeoutMS; + public int DisconnectTimeoutMS; + public int HeartbeatTimeoutMS; + } + + unsafe struct SHA1 + { + private void UpdateABCDE(int i, ref uint a, ref uint b, ref uint c, ref uint d, ref uint e, uint f, uint k) + { + var tmp = ((a << 5) | (a >> 27)) + e + f + k + words[i]; + e = d; + d = c; + c = (b << 30) | (b >> 2); + b = a; + a = tmp; + } + + private void UpdateHash() + { + for (int i = 16; i < 80; ++i) + { + words[i] = (words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16]); + words[i] = (words[i] << 1) | (words[i] >> 31); + } + + var a = h0; + var b = h1; + var c = h2; + var d = h3; + var e = h4; + + for (int i = 0; i < 20; ++i) + { + var f = (b & c) | ((~b) & d); + var k = 0x5a827999u; + UpdateABCDE(i, ref a, ref b, ref c, ref d, ref e, f, k); + } + for (int i = 20; i < 40; ++i) + { + var f = b ^ c ^ d; + var k = 0x6ed9eba1u; + UpdateABCDE(i, ref a, ref b, ref c, ref d, ref e, f, k); + } + for (int i = 40; i < 60; ++i) + { + var f = (b & c) | (b & d) | (c & d); + var k = 0x8f1bbcdcu; + UpdateABCDE(i, ref a, ref b, ref c, ref d, ref e, f, k); + } + for (int i = 60; i < 80; ++i) + { + var f = b ^ c ^ d; + var k = 0xca62c1d6u; + UpdateABCDE(i, ref a, ref b, ref c, ref d, ref e, f, k); + } + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + } + + public SHA1(in FixedString512Bytes str) + { + h0 = 0x67452301u; + h1 = 0xefcdab89u; + h2 = 0x98badcfeu; + h3 = 0x10325476u; + h4 = 0xc3d2e1f0u; + var bitLen = str.Length << 3; + var numFullChunks = bitLen >> 9; + byte* ptr = str.GetUnsafePtr(); + for (int chunk = 0; chunk < numFullChunks; ++chunk) + { + for (int i = 0; i < 16; ++i) + { + words[i] = (uint)((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]); + ptr += 4; + } + UpdateHash(); + } + var remainingBits = (bitLen & 0x1ff); + var remainingBytes = (remainingBits >> 3); + var fullWords = (remainingBytes >> 2); + for (int i = 0; i < fullWords; ++i) + { + words[i] = (uint)((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]); + ptr += 4; + } + var fullBytes = remainingBytes & 3; + switch (fullBytes) + { + case 3: + words[fullWords] = (uint)((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | 0x80u); + ptr += 3; + break; + case 2: + words[fullWords] = (uint)((ptr[0] << 24) | (ptr[1] << 16) | (0x80u << 8)); + ptr += 2; + break; + case 1: + words[fullWords] = (uint)((ptr[0] << 24) | (0x80u << 16)); + ptr += 1; + break; + case 0: + words[fullWords] = (uint)((0x80u << 24)); + break; + } + ++fullWords; + if (remainingBits >= 448) + { + // Needs two chunks, one for the remaining bits and one for size + for (int i = fullWords; i < 16; ++i) + words[i] = 0; + UpdateHash(); + for (int i = 0; i < 15; ++i) + words[i] = 0; + words[15] = (uint)bitLen; + UpdateHash(); + } + else + { + for (int i = fullWords; i < 15; ++i) + words[i] = 0; + words[15] = (uint)bitLen; + UpdateHash(); + } + } + + public FixedString32Bytes ToBase64() + { + FixedString32Bytes base64 = default; + AppendBase64(ref base64, (byte)(h0 >> 24), (byte)(h0 >> 16), (byte)(h0 >> 8)); + AppendBase64(ref base64, (byte)(h0), (byte)(h1 >> 24), (byte)(h1 >> 16)); + AppendBase64(ref base64, (byte)(h1 >> 8), (byte)(h1), (byte)(h2 >> 24)); + AppendBase64(ref base64, (byte)(h2 >> 16), (byte)(h2 >> 8), (byte)(h2)); + AppendBase64(ref base64, (byte)(h3 >> 24), (byte)(h3 >> 16), (byte)(h3 >> 8)); + AppendBase64(ref base64, (byte)(h3), (byte)(h4 >> 24), (byte)(h4 >> 16)); + AppendBase64(ref base64, (byte)(h4 >> 8), (byte)(h4)); + return base64; + } + + private fixed uint words[80]; + private uint h0; + private uint h1; + private uint h2; + private uint h3; + private uint h4; + } + + static unsafe void GenerateBase64Key(out FixedString32Bytes key, ref Keys keys) + { + key = default; + AppendBase64(ref key, (byte)(keys.Key[0] >> 24), (byte)(keys.Key[0] >> 16), (byte)(keys.Key[0] >> 8)); + AppendBase64(ref key, (byte)(keys.Key[0]), (byte)(keys.Key[1] >> 24), (byte)(keys.Key[1] >> 16)); + AppendBase64(ref key, (byte)(keys.Key[1] >> 8), (byte)(keys.Key[1]), (byte)(keys.Key[2] >> 24)); + AppendBase64(ref key, (byte)(keys.Key[2] >> 16), (byte)(keys.Key[2] >> 8), (byte)(keys.Key[2])); + AppendBase64(ref key, (byte)(keys.Key[3] >> 24), (byte)(keys.Key[3] >> 16), (byte)(keys.Key[3] >> 8)); + AppendBase64(ref key, (byte)(keys.Key[3])); + } + + static void AppendBase64(ref FixedString32Bytes base64, byte b0, byte b1, byte b2) + { + var c1 = ApplyTable((byte)(b0 >> 2)); + var c2 = ApplyTable((byte)(((b0 & 3) << 4) | (b1 >> 4))); + var c3 = ApplyTable((byte)(((b1 & 0xf) << 2) | (b2 >> 6))); + var c4 = ApplyTable((byte)(b2 & 0x3f)); + base64.Add(c1); + base64.Add(c2); + base64.Add(c3); + base64.Add(c4); + } + + static void AppendBase64(ref FixedString32Bytes base64, byte b0, byte b1) + { + var c1 = ApplyTable((byte)(b0 >> 2)); + var c2 = ApplyTable((byte)(((b0 & 3) << 4) | (b1 >> 4))); + var c3 = ApplyTable((byte)((b1 & 0xf) << 2)); + + base64.Add(c1); + base64.Add(c2); + base64.Add(c3); + base64.Add((byte)'='); + } + + static void AppendBase64(ref FixedString32Bytes base64, byte b0) + { + var c1 = ApplyTable((byte)(b0 >> 2)); + var c2 = ApplyTable((byte)((b0 & 3) << 4)); + + base64.Add(c1); + base64.Add(c2); + base64.Add((byte)'='); + base64.Add((byte)'='); + } + + static byte ApplyTable(byte val) + { + if (val < 26) + return (byte)(val + 'A'); + else if (val < 52) + return (byte)(val + 'a' - 26); + else if (val < 62) + return (byte)(val + '0' - 52); + else if (val == 62) + return (byte)'+'; + return (byte)'/'; + } + } +} +#endif diff --git a/Runtime/WebSocket/WebSocket.cs.meta b/Runtime/WebSocket/WebSocket.cs.meta new file mode 100644 index 0000000..e72e836 --- /dev/null +++ b/Runtime/WebSocket/WebSocket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15a032bc745f34345b22c52c2f58ed4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/WebSocketNetworkInterface.cs b/Runtime/WebSocketNetworkInterface.cs new file mode 100644 index 0000000..deefb06 --- /dev/null +++ b/Runtime/WebSocketNetworkInterface.cs @@ -0,0 +1,367 @@ +#if !UNITY_WEBGL || UNITY_EDITOR + +using Unity.Burst; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + [BurstCompile] + public struct WebSocketNetworkInterface : INetworkInterface + { + // In all platforms but WebGL this network interface is just a TCPNetworkInterface in disguise. + // The websocket protocol is in fact implemented in the WebSocketLayer. For WebGL this interface is + // implemented in terms of javascript bindings, it does not support Bind()/Listen() and the websocket protocol + // is implemented by the browser. + TCPNetworkInterface tcp; + + public NetworkEndpoint LocalEndpoint => tcp.LocalEndpoint; + + internal ConnectionList CreateConnectionList() => tcp.CreateConnectionList(); + + public int Initialize(ref NetworkSettings settings, ref int packetPadding) => tcp.Initialize(ref settings, ref packetPadding); + public int Bind(NetworkEndpoint endpoint) => tcp.Bind(endpoint); + public int Listen() => tcp.Listen(); + public void Dispose() => tcp.Dispose(); + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) => tcp.ScheduleReceive(ref arguments, dep); + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) => tcp.ScheduleSend(ref arguments, dep); + } +} + +#else + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using Unity.Collections; +using Unity.Jobs; + +namespace Unity.Networking.Transport +{ + public struct WebSocketNetworkInterface : INetworkInterface + { + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void Warn(string msg) => UnityEngine.Debug.LogWarning(msg); + + private const string DLL = "__Internal"; + + static class WebSocket + { + public static int s_NextSocketId = 0; + + [DllImport(DLL, EntryPoint = "js_html_utpWebSocketCreate")] + public static extern void Create(int sockId, IntPtr addrData, int addrSize); + + [DllImport(DLL, EntryPoint = "js_html_utpWebSocketDestroy")] + public static extern void Destroy(int sockId); + + [DllImport(DLL, EntryPoint = "js_html_utpWebSocketSend")] + public static extern int Send(int sockId, IntPtr data, int size); + + [DllImport(DLL, EntryPoint = "js_html_utpWebSocketRecv")] + public static extern int Recv(int sockId, IntPtr data, int size); + + [DllImport(DLL, EntryPoint = "js_html_utpWebSocketIsConnected")] + public static extern int IsConnectionReady(int sockId); + } + + unsafe struct InternalData + { + public NetworkEndpoint ListenEndpoint; + public int ConnectTimeoutMS; // maximum time to wait for a connection to complete + public int MaxConnectAttempts; // maximum number of connect retries + + // If using TLS, connections are made to the endpoint provided in the secure parameters. + public FixedString512Bytes SecureHostname; + } + + unsafe struct ConnectionData + { + public int Socket; + + public long ConnectTime; // Connect start time + public long LastConnectAttemptTime; // Time of the last attempt + public int LastConnectAttempt; // Number of attempts so far + } + + private NativeReference m_InternalData; + + // Maps a connection id from the connection list to its connection data. + private ConnectionDataMap m_ConnectionMap; + + // List of connection information carried over to the layer above + private ConnectionList m_ConnectionList; + + internal ConnectionList CreateConnectionList() + { + m_ConnectionList = ConnectionList.Create(); + return m_ConnectionList; + } + + public unsafe NetworkEndpoint LocalEndpoint => m_InternalData.Value.ListenEndpoint; + + public bool IsCreated => m_InternalData.IsCreated; + + public unsafe int Initialize(ref NetworkSettings settings, ref int packetPadding) + { + var networkConfiguration = settings.GetNetworkConfigParameters(); + + // This needs to match the value of Unity.Networking.Transport.WebSocket.MaxPayloadSize + packetPadding += 14; + + var secureHostname = new FixedString512Bytes(); +#if ENABLE_MANAGED_UNITYTLS + if (settings.TryGet(out var secureParams)) + secureHostname.CopyFrom(secureParams.Hostname); +#endif + + var state = new InternalData + { + ListenEndpoint = NetworkEndpoint.AnyIpv4, + ConnectTimeoutMS = Math.Max(0, networkConfiguration.connectTimeoutMS), + MaxConnectAttempts = Math.Max(1, networkConfiguration.maxConnectAttempts), + SecureHostname = secureHostname, + }; + m_InternalData = new NativeReference(state, Allocator.Persistent); + + m_ConnectionMap = new ConnectionDataMap(1, default, Allocator.Persistent); + return 0; + } + + public unsafe int Bind(NetworkEndpoint endpoint) + { + var state = m_InternalData.Value; + state.ListenEndpoint = endpoint; + m_InternalData.Value = state; + + return 0; + } + + public unsafe int Listen() + => throw new InvalidOperationException("WebGL does not support listening for connections"); + + public unsafe void Dispose() + { + m_InternalData.Dispose(); + + for (int i = 0; i < m_ConnectionMap.Length; ++i) + { + WebSocket.Destroy(m_ConnectionMap.DataAt(i).Socket); + } + + m_ConnectionMap.Dispose(); + m_ConnectionList.Dispose(); + } + + public JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep) + { + return new ReceiveJob + { + ReceiveQueue = arguments.ReceiveQueue, + InternalData = m_InternalData, + ConnectionList = m_ConnectionList, + ConnectionMap = m_ConnectionMap, + Time = arguments.Time, + }.Schedule(dep); + } + + struct ReceiveJob : IJob + { + public PacketsQueue ReceiveQueue; + public NativeReference InternalData; + public ConnectionList ConnectionList; + public ConnectionDataMap ConnectionMap; + public long Time; + + private void Abort(ref ConnectionId connectionId, ref ConnectionData connectionData, Error.DisconnectReason reason = default) + { + ConnectionList.FinishDisconnecting(ref connectionId, reason); + ConnectionMap.ClearData(ref connectionId); + WebSocket.Destroy(connectionData.Socket); + } + + public unsafe void Execute() + { + // Update each connection from the connection list + var count = ConnectionList.Count; + for (int i = 0; i < count; i++) + { + var connectionId = ConnectionList.ConnectionAt(i); + var connectionState = ConnectionList.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected) + continue; + + var connectionData = ConnectionMap[connectionId]; + + // Detect if the upper layer is requesting to connect. + if (connectionState == NetworkConnection.State.Connecting) + { + // The time here is a signed 64bit and we're never going to run at time 0 so if the connection + // has ConnectTime == 0 it's the creation of this connection data. + if (connectionData.ConnectTime == 0) + { + var socket = ++WebSocket.s_NextSocketId; + GetServerAddress(connectionId, out var address); + WebSocket.Create(socket, (IntPtr)address.GetUnsafePtr(), address.Length); + + connectionData.ConnectTime = Time; + connectionData.LastConnectAttemptTime = Time; + connectionData.Socket = socket; + } + + // Disconnect if maximum connection attempts reached + if (connectionData.LastConnectAttempt >= InternalData.Value.MaxConnectAttempts) + { + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.MaxConnectionAttempts); + continue; + } + + // Check if it's time to retry connect which just means incrementing attempts + if (Time - connectionData.LastConnectAttemptTime >= InternalData.Value.ConnectTimeoutMS) + { + connectionData.LastConnectAttempt++; + connectionData.LastConnectAttemptTime = Time; + + var status = WebSocket.IsConnectionReady(connectionData.Socket); + if (status > 0) + { + ConnectionList.FinishConnectingFromLocal(ref connectionId); + } + else if (status < 0) + { + Warn($"WebSocket failed: status={status}"); + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.MaxConnectionAttempts); + continue; + } + } + + ConnectionMap[connectionId] = connectionData; + continue; + } + + // Detect if the upper layer is requesting to disconnect. + if (connectionState == NetworkConnection.State.Disconnecting) + { + Abort(ref connectionId, ref connectionData); + continue; + } + + // Read data from the connection if we can. Receive should return chunks of up to MTU. + // Close the connection in case of a receive error. + var endpoint = ConnectionList.GetConnectionEndpoint(connectionId); + var nbytes = 0; + while (true) + { + // No need to disconnect in case the receive queue becomes full just let the TCP socket buffer + // the incoming data. + if (!ReceiveQueue.EnqueuePacket(out var packetProcessor)) + break; + + nbytes = WebSocket.Recv(connectionData.Socket, (IntPtr)(byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset, packetProcessor.BytesAvailableAtEnd); + if (nbytes > 0) + { + packetProcessor.ConnectionRef = connectionId; + packetProcessor.EndpointRef = endpoint; + packetProcessor.SetUnsafeMetadata(nbytes, packetProcessor.Offset); + } + else + { + packetProcessor.Drop(); + break; + } + } + + if (nbytes < 0) + { + // Disconnect + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.ClosedByRemote); + continue; + } + + // Update the connection data + ConnectionMap[connectionId] = connectionData; + } + } + + // Get the address to connect to for the given connection. If not using TLS, then this + // is just "ws://{address}:{port}" where address/port are taken from the connection's + // endpoint in the connection list. But if using TLS, then the hostname provided in the + // secure parameters overrides the address, and we connect to "wss://{hostname}:{port}" + // (with the port still taken from the connection's endpoint in the connection list). + private void GetServerAddress(ConnectionId connection, out FixedString512Bytes address) + { + var endpoint = ConnectionList.GetConnectionEndpoint(connection); + var secureHostname = InternalData.Value.SecureHostname; + + if (secureHostname.IsEmpty) + address = FixedString.Format("ws://{0}", endpoint.ToFixedString()); + else + address = FixedString.Format("wss://{0}:{1}", secureHostname, endpoint.Port); + } + } + + public JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep) + { + return new SendJob + { + SendQueue = arguments.SendQueue, + ConnectionList = m_ConnectionList, + ConnectionMap = m_ConnectionMap, + }.Schedule(dep); + } + + unsafe struct SendJob : IJob + { + public PacketsQueue SendQueue; + public ConnectionList ConnectionList; + public ConnectionDataMap ConnectionMap; + + private void Abort(ref ConnectionId connectionId, ref ConnectionData connectionData, Error.DisconnectReason reason = default) + { + ConnectionList.FinishDisconnecting(ref connectionId, reason); + ConnectionMap.ClearData(ref connectionId); + WebSocket.Destroy(connectionData.Socket); + } + + public void Execute() + { + // Each packet is sent individually. The connection is aborted if a packet cannot be transmiited + // entirely. + for (int i = 0; i < SendQueue.Count; i++) + { + var packetProcessor = SendQueue[i]; + if (packetProcessor.Length == 0) + continue; + + var connectionId = packetProcessor.ConnectionRef; + var connectionState = ConnectionList.GetConnectionState(connectionId); + + if (connectionState == NetworkConnection.State.Disconnected) + continue; + + var connectionData = ConnectionMap[connectionId]; + + var nbytes = WebSocket.Send(connectionData.Socket, (IntPtr)(byte*)packetProcessor.GetUnsafePayloadPtr() + packetProcessor.Offset, packetProcessor.Length); + if (nbytes != packetProcessor.Length) + { + Warn($"Send buffer overflow trying to send data to {ConnectionList.GetConnectionEndpoint(connectionId)}"); + + // Disconnect + ConnectionList.StartDisconnecting(ref connectionId); + Abort(ref connectionId, ref connectionData, Error.DisconnectReason.ClosedByRemote); + continue; + } + + ConnectionMap[connectionId] = connectionData; + } + } + } + } +} + +#endif diff --git a/Runtime/WebSocketNetworkInterface.cs.meta b/Runtime/WebSocketNetworkInterface.cs.meta new file mode 100644 index 0000000..3b8cd0c --- /dev/null +++ b/Runtime/WebSocketNetworkInterface.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2bd8264cb12bf4c6990bb8c0a6557940 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/WebSocketNetworkInterface.jslib b/Runtime/WebSocketNetworkInterface.jslib new file mode 100644 index 0000000..0b35eeb --- /dev/null +++ b/Runtime/WebSocketNetworkInterface.jslib @@ -0,0 +1,54 @@ +var LibraryUTPWebSocket = { + $GlobalData: { + ws: [] + }, + js_html_utpWebSocketCreate : function(sockId, addrData, addrSize) { + var addr = new TextDecoder().decode(HEAPU8.subarray(addrData, addrData + addrSize)); + var sock = new WebSocket(addr); + sock.binaryType = "arraybuffer"; + sock.utpMessageQueue = []; + sock.addEventListener('message', function (e) { + var data8 = new Uint8Array(e.data); + sock.utpMessageQueue.push(data8); + }); + GlobalData.ws[sockId] = sock; + }, + js_html_utpWebSocketDestroy : function(sockId) { + var sock = GlobalData.ws[sockId]; + if (sock && (sock.readyState == WebSocket.CONNECTING || sock.readyState == WebSocket.OPEN)) { + sock.close(); + } + GlobalData.ws[sockId] = undefined; + }, + js_html_utpWebSocketSend : function(sockId, data, size) { + var sock = GlobalData.ws[sockId]; + if (!sock || sock.readyState != WebSocket.OPEN) + return -1; + sock.send(HEAPU8.subarray(data, data + size)); + return size; + }, + js_html_utpWebSocketRecv : function(sockId, data, size) { + var sock = GlobalData.ws[sockId]; + if (!sock || sock.readyState != WebSocket.OPEN) + return -1; + if (sock.utpMessageQueue.length == 0) + return 0; + var buffer = sock.utpMessageQueue.shift(); + if (buffer.length > size) + return 0; + HEAP8.set(buffer, data); + return buffer.length; + }, + js_html_utpWebSocketIsConnected : function(sockId) { + var sock = GlobalData.ws[sockId]; + if (!sock) + return -1; + if (sock.readyState == WebSocket.OPEN) + return 1; + if (sock.readyState == WebSocket.CONNECTING) + return 0; + return -1; + } +}; +autoAddDeps(LibraryUTPWebSocket, '$GlobalData'); +mergeInto(LibraryManager.library, LibraryUTPWebSocket); diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.dll.meta b/Runtime/WebSocketNetworkInterface.jslib.meta similarity index 74% rename from Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.dll.meta rename to Runtime/WebSocketNetworkInterface.jslib.meta index 8661212..b08a802 100644 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.dll.meta +++ b/Runtime/WebSocketNetworkInterface.jslib.meta @@ -1,17 +1,20 @@ fileFormatVersion: 2 -guid: 69dcc7aac293f4eb48086f6d085ad3a5 +guid: ab1c9b70c9936425f9286479fb472f04 PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} + defineConstraints: [] isPreloaded: 0 isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 platformData: - first: Any: second: - enabled: 1 + enabled: 0 settings: {} - first: Editor: Editor @@ -20,11 +23,10 @@ PluginImporter: settings: DefaultValueInitialized: true - first: - Windows Store Apps: WindowsStoreApps + WebGL: WebGL second: - enabled: 0 - settings: - CPU: AnyCPU + enabled: 1 + settings: {} userData: assetBundleName: assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/.sample.json b/Samples~/CustomNetworkInterface/.sample.json deleted file mode 100644 index 5ae508b..0000000 --- a/Samples~/CustomNetworkInterface/.sample.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Custom Network Interface", - "description": "Shows how to build a custom network interface using the INetworkInterface API" -} \ No newline at end of file diff --git a/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity b/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity deleted file mode 100644 index fc0c5fe..0000000 --- a/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity +++ /dev/null @@ -1,341 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!29 &1 -OcclusionCullingSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_OcclusionBakeSettings: - smallestOccluder: 5 - smallestHole: 0.25 - backfaceThreshold: 100 - m_SceneGUID: 00000000000000000000000000000000 - m_OcclusionCullingData: {fileID: 0} ---- !u!104 &2 -RenderSettings: - m_ObjectHideFlags: 0 - serializedVersion: 9 - m_Fog: 0 - m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - m_FogMode: 3 - m_FogDensity: 0.01 - m_LinearFogStart: 0 - m_LinearFogEnd: 300 - m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} - m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} - m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} - m_AmbientIntensity: 1 - m_AmbientMode: 0 - m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - m_HaloStrength: 0.5 - m_FlareStrength: 1 - m_FlareFadeSpeed: 3 - m_HaloTexture: {fileID: 0} - m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} - m_DefaultReflectionMode: 0 - m_DefaultReflectionResolution: 128 - m_ReflectionBounces: 1 - m_ReflectionIntensity: 1 - m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} - m_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 11 - m_GIWorkflowMode: 1 - m_GISettings: - serializedVersion: 2 - m_BounceScale: 1 - m_IndirectOutputScale: 1 - m_AlbedoBoost: 1 - m_EnvironmentLightingMode: 0 - m_EnableBakedLightmaps: 1 - m_EnableRealtimeLightmaps: 0 - m_LightmapEditorSettings: - serializedVersion: 12 - m_Resolution: 2 - m_BakeResolution: 40 - m_AtlasSize: 1024 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAmbientOcclusion: 0 - m_Padding: 2 - m_LightmapParameters: {fileID: 0} - m_LightmapsBakeMode: 1 - m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 - m_ReflectionCompression: 2 - m_MixedBakeMode: 2 - m_BakeBackend: 1 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVRBounces: 2 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVREnvironmentMIS: 1 - m_PVRCulling: 1 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 5 - m_PVRFilteringGaussRadiusAO: 2 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ExportTrainingData: 0 - m_TrainingDataDestination: TrainingData - m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 0} - m_UseShadowmask: 1 ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 2 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.4 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - accuratePlacement: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1 &461126692 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 461126693} - - component: {fileID: 461126694} - m_Layer: 0 - m_Name: CustomNetworkInterfaceConnect - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &461126693 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 461126692} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 2 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &461126694 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 461126692} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ede7cf38fcf2f4ca087d49411dcc32f7, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!1 &1091932320 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1091932322} - - component: {fileID: 1091932321} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &1091932321 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1091932320} - m_Enabled: 1 - serializedVersion: 10 - m_Type: 1 - m_Shape: 0 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 ---- !u!4 &1091932322 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1091932320} - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 1 - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!1 &1893500274 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1893500277} - - component: {fileID: 1893500276} - - component: {fileID: 1893500275} - m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!81 &1893500275 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1893500274} - m_Enabled: 1 ---- !u!20 &1893500276 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1893500274} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &1893500277 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1893500274} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity.meta b/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity.meta deleted file mode 100644 index e12a8a4..0000000 --- a/Samples~/CustomNetworkInterface/Scenes/CustomNetworkInterface.unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 08a1d8671958c4a5d8bc894c2a26e13d -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts.meta b/Samples~/CustomNetworkInterface/Scripts.meta deleted file mode 100644 index 7c19d32..0000000 --- a/Samples~/CustomNetworkInterface/Scripts.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 460769199866fda4c855114aab30b0ce -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/Bindings.meta b/Samples~/CustomNetworkInterface/Scripts/Bindings.meta deleted file mode 100644 index 36a5537..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/Bindings.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a820f5e5bdcbd4b03a6b5dd5e66d6154 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs b/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs deleted file mode 100644 index a0c0cfe..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Unity.Collections.LowLevel.Unsafe; - -namespace Unity.Networking.Transport.Samples -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct network_iovec - { - public int len; - public void* buf; - } - - // TODO: Fix this internally incase there are other platforms that also does - // it differently so it may result in similar issues -# if (UNITY_EDITOR_OSX || ((UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR)) - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sa_family_t - { - public const int size = sizeof(byte) * 2; - [FieldOffset(0)] public byte sa_len; - [FieldOffset(1)] public byte sa_family; - } -# else - internal unsafe struct sa_family_t - { - public const int size = sizeof(ushort); - public ushort sa_family; - } -# endif - - internal unsafe struct in_addr - { - public uint s_addr; - } - - [StructLayout(LayoutKind.Explicit)] - public unsafe struct network_address : IEquatable - { - internal const int Length = 28; - [FieldOffset(0)] public fixed byte data[Length]; - [FieldOffset(28)] public int length; - - public bool Equals(network_address other) - { - if (length != other.length) - return false; - - fixed(void* p = this.data) - return UnsafeUtility.MemCmp(p, other.data, length) == 0; - } - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr - { - [FieldOffset(0)] public fixed byte data[16]; - - [FieldOffset(0)] public sa_family_t sin_family; - [FieldOffset(2)] public fixed byte sin_zero[14]; - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr_in - { - [FieldOffset(0)] public fixed byte data[16]; - - [FieldOffset(0)] public sa_family_t sin_family; - [FieldOffset(2)] public ushort sin_port; - [FieldOffset(4)] public in_addr sin_addr; - [FieldOffset(8)] public fixed byte sin_zero[8]; - } - - internal unsafe struct in_addr6 - { - public fixed byte s6_addr[16]; - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr_in6 - { - [FieldOffset(0)] public fixed byte data[28]; - - [FieldOffset(0)] public sa_family_t sin6_family; - [FieldOffset(2)] public ushort sin6_port; - [FieldOffset(4)] public uint sin6_flowinfo; - [FieldOffset(8)] public in_addr6 sin6_addr; - [FieldOffset(24)] public uint sin6_scope_id; - } - - public static unsafe class NativeBindings - { -#if UNITY_IOS && !UNITY_EDITOR - const string m_DllName = "__Internal"; -#elif UNITY_EDITOR_WIN && UNITY_2019_2_OR_NEWER - const string m_DllName = "network.bindings.dll"; -#elif UNITY_EDITOR_OSX && UNITY_2019_2_OR_NEWER - const string m_DllName = "network.bindings.bundle"; -#else - const string m_DllName = "network.bindings"; -#endif - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_initialize(); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_terminate(); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_create_and_bind(ref long socket_handle, ref network_address address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_close(ref long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_nonblocking(long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_send_buffer_size(long socket_handle, int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_receive_buffer_size(long socket_handle, int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_send_buffer_size(long socket_handle, ref int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_receive_buffer_size(long socket_handle, ref int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_receive_timeout(long socket_handle, ulong timeout, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_blocking(long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_connection_reset(long socket_handle, int value); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_socket_address(long socket_handle, ref network_address own_address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_sendmsg(long socket_handle, void* iov, int iov_len, - ref network_address address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_recvmsg(long socket_handle, void* iov, int iov_len, - ref network_address remote, ref int errorcode); - } -} diff --git a/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs.meta b/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs.meta deleted file mode 100644 index 9d44c1c..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/Bindings/network.bindings.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2acae15c7e5e24587b9e886c09c3600d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef b/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef deleted file mode 100644 index 1d62441..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "CustomNetworkInterface", - "references": [ - "Unity.Burst", - "Unity.Collections", - "Unity.Mathematics", - "Unity.Networking.Transport" - ], - "includePlatforms": [ - "Editor", - "LinuxStandalone64", - "macOSStandalone", - "WindowsStandalone64" - ], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": false, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} diff --git a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef.meta b/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef.meta deleted file mode 100644 index 9acec10..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterface.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 0e15ea315e84f4e6fb625f051cc45de3 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs b/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs deleted file mode 100644 index 464fcb2..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs +++ /dev/null @@ -1,156 +0,0 @@ -using Unity.Burst; -using UnityEngine; -using Unity.Networking.Transport; -using Unity.Collections; -using Unity.Jobs; - -namespace Unity.Networking.Transport.Samples -{ - public class CustomNetworkInterfaceConnect : MonoBehaviour - { - const int ServerPort = 9000; - struct PendingPing - { - public int id; - public float time; - } - - private NetworkDriver m_ClientDriver; - private NetworkConnection m_clientToServerConnection; - public NetworkDriver m_ServerDriver; - private NativeList m_serverConnections; - // pendingPing is a ping sent to the server which have not yet received a response. - private PendingPing m_pendingPing; - // The ping stats are two integers, time for last ping and number of pings - private int m_lastPingTime; - private int m_numPingsSent; - - void Start() - { - // Create a NetworkDriver for the client. We could bind to a specific address but in this case we rely on the - // implicit bind since we do not need to bing to anything special - m_ClientDriver = new NetworkDriver(new UDPNetworkInterface()); - - // Create the server driver, bind it to a port and start listening for incoming connections - m_ServerDriver = new NetworkDriver(new UDPNetworkInterface()); - var addr = NetworkEndPoint.AnyIpv4; - addr.Port = ServerPort; - if (m_ServerDriver.Bind(addr) != 0) - Debug.Log($"Failed to bind to port {ServerPort}"); - else - m_ServerDriver.Listen(); - - m_serverConnections = new NativeList(16, Allocator.Persistent); - } - - void OnDestroy() - { - m_ClientDriver.Dispose(); - - m_ServerDriver.Dispose(); - m_serverConnections.Dispose(); - } - - void FixedUpdate() - { - ClientUpdate(); - ServerUpdate(); - } - - void ClientUpdate() - { - // Update the NetworkDriver. It schedules a job so we must wait for that job with Complete - m_ClientDriver.ScheduleUpdate().Complete(); - - // If the client ui indicates we should be sending pings but we do not have an active connection we create one - if (!m_clientToServerConnection.IsCreated) - { - var serverEP = NetworkEndPoint.LoopbackIpv4; - serverEP.Port = ServerPort; - m_clientToServerConnection = m_ClientDriver.Connect(serverEP); - } - - DataStreamReader strm; - NetworkEvent.Type cmd; - // Process all events on the connection. If the connection is invalid it will return Empty immediately - while ((cmd = m_clientToServerConnection.PopEvent(m_ClientDriver, out strm)) != NetworkEvent.Type.Empty) - { - if (cmd == NetworkEvent.Type.Connect) - { - // When we get the connect message we can start sending data to the server - // Set the ping id to a sequence number for the new ping we are about to send - m_pendingPing = new PendingPing {id = m_numPingsSent, time = Time.fixedTime}; - // Create a 4 byte data stream which we can store our ping sequence number in - - if (m_ClientDriver.BeginSend(m_clientToServerConnection, out var pingData) == 0) - { - pingData.WriteInt(m_numPingsSent); - m_ClientDriver.EndSend(pingData); - } - // Update the number of sent pings - ++m_numPingsSent; - } - else if (cmd == NetworkEvent.Type.Data) - { - // When the pong message is received we calculate the ping time and disconnect - m_lastPingTime = (int)((Time.fixedTime - m_pendingPing.time) * 1000); - m_clientToServerConnection.Disconnect(m_ClientDriver); - m_clientToServerConnection = default(NetworkConnection); - UnityEngine.Debug.Log($"Ping: {m_lastPingTime}"); - } - else if (cmd == NetworkEvent.Type.Disconnect) - { - // If the server disconnected us we clear out connection - m_clientToServerConnection = default(NetworkConnection); - } - } - } - - void ServerUpdate() - { - // Update the NetworkDriver. It schedules a job so we must wait for that job with Complete - m_ServerDriver.ScheduleUpdate().Complete(); - - // Accept all new connections - while (true) - { - var con = m_ServerDriver.Accept(); - // "Nothing more to accept" is signaled by returning an invalid connection from accept - if (!con.IsCreated) - break; - m_serverConnections.Add(con); - } - - for (int i = 0; i < m_serverConnections.Length; ++i) - { - DataStreamReader strm; - NetworkEvent.Type cmd; - // Pop all events for the connection - while ((cmd = m_ServerDriver.PopEventForConnection(m_serverConnections[i], out strm)) != NetworkEvent.Type.Empty) - { - if (cmd == NetworkEvent.Type.Data) - { - // For ping requests we reply with a pong message - int id = strm.ReadInt(); - // Create a temporary DataStreamWriter to keep our serialized pong message - if (m_ServerDriver.BeginSend(m_serverConnections[i], out var pongData) == 0) - { - pongData.WriteInt(id); - // Send the pong message with the same id as the ping - m_ServerDriver.EndSend(pongData); - } - } - else if (cmd == NetworkEvent.Type.Disconnect) - { - // This connection no longer exist, remove it from the list - // The next iteration will operate on the new connection we swapped in so as long as it exist the - // loop can continue - m_serverConnections.RemoveAtSwapBack(i); - if (i >= m_serverConnections.Length) - break; - } - } - } - } - } -} diff --git a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs.meta b/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs.meta deleted file mode 100644 index f7babf6..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/CustomNetworkInterfaceConnect.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ede7cf38fcf2f4ca087d49411dcc32f7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs b/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs deleted file mode 100644 index 44a0675..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System; -using System.Collections.Generic; -using AOT; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; - -namespace Unity.Networking.Transport.Samples -{ -#if NET_DOTS - public class NetworkInterfaceException : Exception - { - public NetworkInterfaceException(int err) - : base("Socket error: " + err) - { - } - } -#else - public class NetworkInterfaceException : System.Net.Sockets.SocketException - { - public NetworkInterfaceException(int err) - : base(err) - { - } - } -#endif - - public struct UDPNetworkInterface : INetworkInterface - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - private class SocketList - { - public HashSet OpenSockets = new HashSet(); - - ~SocketList() - { - foreach (var socket in OpenSockets) - { - long sockHand = socket; - int errorcode = 0; - NativeBindings.network_close(ref sockHand, ref errorcode); - } - } - } - private static SocketList AllSockets = new SocketList(); -#endif - - NativeArray m_UserData; - - public unsafe NetworkInterfaceEndPoint LocalEndPoint - { - get - { - var localEndPoint = new network_address {length = network_address.Length}; - int errorcode = 0; - var result = NativeBindings.network_get_socket_address(m_UserData[0], ref localEndPoint, ref errorcode); - if (result != 0) - { - throw new NetworkInterfaceException(errorcode); - } - - var endpoint = default(NetworkInterfaceEndPoint); - endpoint.dataLength = UnsafeUtility.SizeOf(); - UnsafeUtility.MemCpy(endpoint.data, &localEndPoint, endpoint.dataLength); - return endpoint; - } - } - - private static unsafe NetworkInterfaceEndPoint ParseNetworkAddress(NetworkEndPoint endPoint) - { - NetworkInterfaceEndPoint ep = default(NetworkInterfaceEndPoint); - var addr = (network_address*)ep.data; - var sai = (sockaddr_in*)addr->data; -#if (UNITY_EDITOR_OSX || ((UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR)) - sai->sin_family.sa_family = (byte)NetworkFamily.Ipv4; - sai->sin_family.sa_len = (byte)sizeof(sockaddr_in); -#else - sai->sin_family.sa_family = (ushort)NetworkFamily.Ipv4; -#endif - sai->sin_port = endPoint.RawPort; - var bytes = endPoint.GetRawAddressBytes(); - sai->sin_addr.s_addr = *(uint*)bytes.GetUnsafeReadOnlyPtr(); - - addr->length = sizeof(sockaddr_in); - ep.dataLength = sizeof(network_address); - return ep; - } - - public int CreateInterfaceEndPoint(NetworkEndPoint address, out NetworkInterfaceEndPoint endpoint) - { - if (address.Family != NetworkFamily.Ipv4) - throw new ArgumentException("Invalid family type"); - - endpoint = ParseNetworkAddress(address); - return 0; - } - - public unsafe NetworkEndPoint GetGenericEndPoint(NetworkInterfaceEndPoint endpoint) - { - var address = NetworkEndPoint.AnyIpv4; - var addr = (network_address*)endpoint.data; - var sai = (sockaddr_in*)addr->data; - address.RawPort = sai->sin_port; - if (sai->sin_addr.s_addr != 0) - { - var bytes = new NativeArray(4, Allocator.Temp); - UnsafeUtility.MemCpy(bytes.GetUnsafePtr(), UnsafeUtility.AddressOf(ref sai->sin_addr.s_addr), 4); - address.SetRawAddressBytes(bytes); - } - return address; - } - - public unsafe int Initialize(NetworkSettings settings) - { - NativeBindings.network_initialize(); - - var ep = default(NetworkInterfaceEndPoint); - var result = 0; - - if ((result = CreateInterfaceEndPoint(NetworkEndPoint.AnyIpv4, out ep)) != (int)Error.StatusCode.Success) - return result; - - long sockHand; - int ret = CreateAndBindSocket(out sockHand, *(network_address*)ep.data); - - if (ret == 0) - { - m_UserData = new NativeArray(1, Allocator.Persistent); - m_UserData[0] = sockHand; - } - return ret; - } - - public void Dispose() - { - Close(); - NativeBindings.network_terminate(); - m_UserData.Dispose(); - } - - struct ReceiveJob : IJob - { - public NetworkPacketReceiver receiver; - public long socket; - - public unsafe void Execute() - { - var address = new network_address {length = network_address.Length}; - receiver.ReceiveErrorCode = 0; - - while (true) - { - var size = NetworkParameterConstants.MTU; - var ptr = receiver.AllocateMemory(ref size); - if (ptr == IntPtr.Zero) - return; - - var resultReceive = NativeReceive((byte*)ptr.ToPointer(), size, ref address); - if (resultReceive <= 0) - { - if (resultReceive != 0) - receiver.ReceiveErrorCode = -resultReceive; - return; - } - - var endpoint = default(NetworkInterfaceEndPoint); - endpoint.dataLength = UnsafeUtility.SizeOf(); - UnsafeUtility.MemCpy(endpoint.data, &address, endpoint.dataLength); - - var resultAppend = receiver.AppendPacket(ptr, ref endpoint, resultReceive, NetworkPacketReceiver.AppendPacketMode.NoCopyNeeded); - if (resultAppend == false) - return; - } - } - - unsafe int NativeReceive(void* data, int length, ref network_address address) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - if (length <= 0) - throw new ArgumentException("Can't receive into 0 bytes or less of buffer memory"); -#endif - var iov = new network_iovec - { - buf = data, - len = length, - }; - - int errorcode = 0; - var result = NativeBindings.network_recvmsg(socket, &iov, 1, ref address, ref errorcode); - if (result == -1) - { - if (errorcode == 10035 || errorcode == 35 || errorcode == 11) - return 0; - - receiver.ReceiveErrorCode = errorcode; - } - return result; - } - } - - public JobHandle ScheduleReceive(NetworkPacketReceiver receiver, JobHandle dep) - { - var job = new ReceiveJob {receiver = receiver, socket = m_UserData[0]}; - return job.Schedule(dep); - } - - public JobHandle ScheduleSend(NativeQueue sendQueue, JobHandle dep) - { - return dep; - } - - public unsafe int Bind(NetworkInterfaceEndPoint endpoint) - { - long newSocket; - int ret = CreateAndBindSocket(out newSocket, *(network_address*)endpoint.data); - if (ret != 0) - return ret; - Close(); - - m_UserData[0] = newSocket; - - return 0; - } - - public int Listen() - { - return 0; - } - - private void Close() - { - if (m_UserData[0] < 0) - return; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AllSockets.OpenSockets.Remove(m_UserData[0]); -#endif - int errorcode = 0; - long sockHand = m_UserData[0]; - NativeBindings.network_close(ref sockHand, ref errorcode); - - m_UserData[0] = -1; - } - - static TransportFunctionPointer BeginSendMessageFunctionPointer = new TransportFunctionPointer(BeginSendMessage); - static TransportFunctionPointer EndSendMessageFunctionPointer = new TransportFunctionPointer(EndSendMessage); - static TransportFunctionPointer AbortSendMessageFunctionPointer = new TransportFunctionPointer(AbortSendMessage); - public unsafe NetworkSendInterface CreateSendInterface() - { - return new NetworkSendInterface - { - BeginSendMessage = BeginSendMessageFunctionPointer, - EndSendMessage = EndSendMessageFunctionPointer, - AbortSendMessage = AbortSendMessageFunctionPointer, - UserData = (IntPtr)m_UserData.GetUnsafePtr() - }; - } - - [MonoPInvokeCallback(typeof(NetworkSendInterface.BeginSendMessageDelegate))] - public static unsafe int BeginSendMessage(out NetworkInterfaceSendHandle handle, IntPtr userData, int requiredPayloadSize) - { - handle.id = 0; - handle.size = 0; - handle.capacity = requiredPayloadSize; - handle.data = (IntPtr)UnsafeUtility.Malloc(handle.capacity, 8, Allocator.Temp); - handle.flags = default; - return 0; - } - - [MonoPInvokeCallback(typeof(NetworkSendInterface.EndSendMessageDelegate))] - public static unsafe int EndSendMessage(ref NetworkInterfaceSendHandle handle, ref NetworkInterfaceEndPoint address, IntPtr userData, ref NetworkSendQueueHandle sendQueue) - { - network_iovec iov; - iov.buf = (void*)handle.data; - iov.len = handle.size; - int errorcode = 0; - var addr = address; - return NativeBindings.network_sendmsg(*(long*)userData, &iov, 1, ref *(network_address*)addr.data, ref errorcode); - } - - [MonoPInvokeCallback(typeof(NetworkSendInterface.AbortSendMessageDelegate))] - private static void AbortSendMessage(ref NetworkInterfaceSendHandle handle, IntPtr userData) - { - } - - int CreateAndBindSocket(out long socket, network_address address) - { - socket = -1; - int errorcode = 0; - int ret = NativeBindings.network_create_and_bind(ref socket, ref address, ref errorcode); - if (ret != 0) - return errorcode; -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AllSockets.OpenSockets.Add(socket); -#endif - if ((ret = NativeBindings.network_set_nonblocking(socket, ref errorcode)) != 0) - return errorcode; - if ((ret = NativeBindings.network_set_send_buffer_size(socket, ushort.MaxValue, ref errorcode)) != 0) - return errorcode; - if ((ret = NativeBindings.network_set_receive_buffer_size(socket, ushort.MaxValue, ref errorcode)) != 0) - return errorcode; -#if (UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN) - // Avoid WSAECONNRESET errors when sending to an endpoint which isn't open yet (unclean connect/disconnects) - NativeBindings.network_set_connection_reset(socket, 0); -#endif - return 0; - } - } -} diff --git a/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs.meta b/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs.meta deleted file mode 100644 index 0d6b142..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/UDPNetworkInterface.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 26ef3682e760f414591d4cef054aba2e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/Makefile b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/Makefile deleted file mode 100644 index 520a312..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/Makefile +++ /dev/null @@ -1,94 +0,0 @@ -CURRENT_DIR := $(shell pwd) -BIN_DIR := bin -BUILD_DIR := build -NETWORK_BINDINGS_DIR := network.bindings -NETWORK_BINDINGS_SRC_DIR := source -NETWORK_BINDINGS_OBJ_DIR := $(BUILD_DIR)/$(NETWORK_BINDINGS_DIR)/obj -UNITY_PROJECT_BINDINGS_DIR := $(CURRENT_DIR)/../Bindings - -NATIVE_CC := clang -NATIVE_FLAGS := -Wall -Wextra -std=gnu99 -g -NATIVE_FLAGSR := -Wall -Wextra -g -LINUX_NATIVE_FLAGS := -fPIC - -MAC_LINKER_FLAGS := -bundle -LINUX_LINKER_FLAGS := -shared -Wl,-soname,libnetwork.bindings.so -OS_TYPE := $(shell uname) - -# ============================================================================= - -all: createdirs network.bindings - -createdirs: - @if [ ! -d $(BIN_DIR) ];then mkdir $(BIN_DIR);fi - @if [ ! -d $(BUILD_DIR) ];then mkdir $(BUILD_DIR);fi - @if [ ! -d $(NETWORK_BINDINGS_OBJ_DIR) ];then mkdir -p $(NETWORK_BINDINGS_OBJ_DIR);fi - -# ============================================================================= - -NATIVE_HDRS := \ - $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.h - -# ============================================================================= - -NATIVE_OBJS := \ - $(NETWORK_BINDINGS_OBJ_DIR)/network.bindings.o - - -DRIVER_OBJS := \ - $(NETWORK_BINDINGS_OBJ_DIR)/network.bindings.o \ - $(NETWORK_BINDINGS_OBJ_DIR)/driver.o -# ============================================================================= - -$(NETWORK_BINDINGS_OBJ_DIR)/network.bindings.o: $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.c -ifeq ($(OS_TYPE),Linux) - $(NATIVE_CC) $(LINUX_NATIVE_FLAGS) $(NATIVE_FLAGS) -o $@ -c $< -else - $(NATIVE_CC) $(NATIVE_FLAGS) -o $@ -c $< -endif - -$(NETWORK_BINDINGS_OBJ_DIR)/driver.o: $(NETWORK_BINDINGS_SRC_DIR)/driver/driver.cpp - clang++ $(NATIVE_FLAGSR) -I$(CURRENT_DIR)/$(NETWORK_BINDINGS_SRC_DIR)/ -o $@ -c $< - -# ============================================================================= - -network.bindings: $(NATIVE_OBJS) -ifeq ($(OS_TYPE),Linux) - $(NATIVE_CC) $(NATIVE_FLAGS) $(LINUX_LINKER_FLAGS) $^ -o $(BIN_DIR)/lib$@.so -else - $(NATIVE_CC) $(NATIVE_FLAGS) $(MAC_LINKER_FLAGS) $^ -o $(BIN_DIR)/$@.bundle -endif - -#gamesocket.managed: -# mcs -t:library gamesocket.managed/multiplay_managed.cs -out:$(BIN_DIR)/gamesocket.managed.dll -sdk:2 -unsafe - -driver: $(DRIVER_OBJS) - clang++ $^ -o $(BIN_DIR)/$@ - -# Native library needs to be in a public place like ~/lib or /usr/lib or inside the unity project root -install: - #./install_lib.sh libgamesocket.native.dylib - mkdir -p $(UNITY_PROJECT_BINDINGS_DIR) - cp $(BIN_DIR)/*network.bindings.* $(UNITY_PROJECT_BINDINGS_DIR) - cp $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.cs $(UNITY_PROJECT_BINDINGS_DIR) - cp $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.cs.meta $(UNITY_PROJECT_BINDINGS_DIR) -ifeq ($(OS_TYPE),Linux) - cp $(NETWORK_BINDINGS_SRC_DIR)/libnetwork.bindings.so.meta $(UNITY_PROJECT_BINDINGS_DIR) -else - cp $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.bundle.meta $(UNITY_PROJECT_BINDINGS_DIR) - # On iOS just copy source files to the ios directory - mkdir -p $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.c $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/network.bindings.h $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/platform.h $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/meta/ios.meta $(UNITY_PROJECT_BINDINGS_DIR) - cp $(NETWORK_BINDINGS_SRC_DIR)/meta/network.bindings.c.meta $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/meta/network.bindings.h.meta $(UNITY_PROJECT_BINDINGS_DIR)/ios - cp $(NETWORK_BINDINGS_SRC_DIR)/meta/platform.h.meta $(UNITY_PROJECT_BINDINGS_DIR)/ios -endif - -clean: - @rm -rf $(BIN_DIR) - @rm -rf $(BUILD_DIR) - -.PHONY: clean createdirs libgamesocket.native.dylib gamesocket.managed diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/build.bat b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/build.bat deleted file mode 100644 index 7881a47..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/build.bat +++ /dev/null @@ -1,49 +0,0 @@ -@echo off -SETLOCAL EnableDelayedExpansion - -if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" ( - echo "WARNING: You need VS 2017 version 15.2 or later (for vswhere.exe)" -) - -for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath`) do ( - set InstallDir=%%i -) - -if exist "!InstallDir!\VC\Auxiliary\Build\vcvars64.bat" ( - echo Found !InstallDir! - call "!InstallDir!\VC\Auxiliary\Build\vcvars64.bat" -) else ( - echo "Could not find !InstallDir!\VC\Auxiliary\Build\vcvars64.bat" -) - -set network_bindings_dir=..\Bindings - -set bin_dir=bin -set dll=%bin_dir%\network.bindings.dll -set pdb=%bin_dir%\network.bindings.pdb - -set cs_bindings=source\network.bindings.cs -set dllmeta=source\network.bindings.dll.meta -set pdbmeta=source\network.bindings.pdb.meta - -if not exist %cs_bindings% ( - set errno=missing %cs_bindings% - goto error -) - -msbuild /P:Configuration=Release network.bindings.sln - -xcopy %dll% %network_bindings_dir%\ /Y >nul 2>nul -xcopy %pdb% %network_bindings_dir%\ /Y >nul 2>nul -xcopy %cs_bindings% %network_bindings_dir%\ /Y >nul 2>nul -xcopy %cs_bindings%.meta %network_bindings_dir%\ /Y >nul 2>nul -xcopy %dllmeta% %network_bindings_dir%\ /Y >nul 2>nul -xcopy %pdbmeta% %network_bindings_dir%\ /Y >nul 2>nul - -goto end -:error -set esc= -set red=%esc%[31m -set white=%esc%[37m -echo %red% %errno% %white% -:end diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/network.bindings.sln b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/network.bindings.sln deleted file mode 100644 index 92aac1c..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/network.bindings.sln +++ /dev/null @@ -1,41 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2026 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "network.bindings", "source\network.bindings.vcxproj", "{2234670D-68E4-4F31-A6AC-E6891322818C}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driver", "source\driver\driver.vcxproj", "{9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2234670D-68E4-4F31-A6AC-E6891322818C}.Debug|x64.ActiveCfg = Debug|x64 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Debug|x64.Build.0 = Debug|x64 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Debug|x86.ActiveCfg = Debug|Win32 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Debug|x86.Build.0 = Debug|Win32 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Release|x64.ActiveCfg = Release|x64 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Release|x64.Build.0 = Release|x64 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Release|x86.ActiveCfg = Release|Win32 - {2234670D-68E4-4F31-A6AC-E6891322818C}.Release|x86.Build.0 = Release|Win32 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Debug|x64.ActiveCfg = Debug|x64 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Debug|x64.Build.0 = Debug|x64 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Debug|x86.ActiveCfg = Debug|Win32 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Debug|x86.Build.0 = Debug|Win32 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Release|x64.ActiveCfg = Release|x64 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Release|x64.Build.0 = Release|x64 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Release|x86.ActiveCfg = Release|Win32 - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FE593C9B-B28F-47C5-8975-7236194851FD} - EndGlobalSection -EndGlobal diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/shell.bat b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/shell.bat deleted file mode 100644 index b44c2e3..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/shell.bat +++ /dev/null @@ -1,17 +0,0 @@ -@echo off -SETLOCAL EnableDelayedExpansion - -if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" ( - echo "WARNING: You need VS 2017 version 15.2 or later (for vswhere.exe)" -) - -for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath`) do ( - set InstallDir=%%i -) - -if exist "!InstallDir!\VC\Auxiliary\Build\vcvars64.bat" ( - echo Found !InstallDir! - call "!InstallDir!\VC\Auxiliary\Build\vcvars64.bat" -) else ( - echo "Could not find !InstallDir!\VC\Auxiliary\Build\vcvars64.bat" -) diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.cpp b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.cpp deleted file mode 100644 index edf7818..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include -#include -#include - -#if defined(__APPLE__) || defined(__linux__) -#include -#include -#endif - -static int retval; -#define call(function, ...) \ - retval = function(__VA_ARGS__); \ - assert(retval >= SUCCESS); - -int main(int argc, char** argv) -{ - printf("sizeof network_address = %d\n", (int)sizeof(network_address)); - printf("sizeof sockaddr_in = %d\n", (int)sizeof(sockaddr_in)); - printf("sizeof sockaddr_in6 = %d\n", (int)sizeof(sockaddr_in6)); - - int32_t errorcode; - network_initialize(); - int64_t server; - network_address localhost; - localhost.length = sizeof(sockaddr_in); - memset(&localhost.addr_in, 0, localhost.length); - localhost.addr_in.sin_family = AF_INET; - localhost.addr_in.sin_port = htons(1337); - localhost.addr_in.sin_addr.s_addr = htonl((127 << 24) | 1); - call(network_create_and_bind, &server, &localhost, &errorcode); - assert(server != SOCKET_ERROR); - - int64_t client; - network_address any; - any.length = sizeof(sockaddr_in); - memset(&localhost.addr_in, 0, localhost.length); - any.addr_in.sin_family = AF_INET; - call(network_create_and_bind, &client, &any, &errorcode); - assert(client != SOCKET_ERROR); - - network_address server_address; - server_address.length = sizeof(network_address); - call(network_get_socket_address, server, &server_address, &errorcode); - - network_address client_address; - client_address.length = sizeof(network_address); - call(network_get_socket_address, client, &client_address, &errorcode); - - network_iov_t iov[1]; - - const char client_send[] = "Hello Networked World!"; - iov->data = (uint8_t*)client_send; - iov->length = sizeof(client_send); - - call(network_sendmsg, client, iov, 1, &server_address, &errorcode); - - char server_recv[sizeof(client_send)]; - iov->data = (uint8_t*)server_recv; - iov->length = sizeof(client_send); - - network_address remote; - remote.length = sizeof(remote); - call(network_recvmsg, server, iov, 1, &remote, &errorcode); - - assert(memcmp(client_send, server_recv, sizeof(client_send)) == 0); - - call(network_close, &client, &errorcode); - assert(client == SUCCESS); - call(network_close, &server, &errorcode); - assert(server == SUCCESS); - - network_terminate(); - - printf("all passed!\n"); - - return 0; -} - -#undef call diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.vcxproj b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.vcxproj deleted file mode 100644 index 86bdc32..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/driver/driver.vcxproj +++ /dev/null @@ -1,169 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {9E30FB61-DCD0-4A94-AA88-5E44E2037E5A} - Win32Proj - driver - 10.0.16299.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)bin\ - $(SolutionDir)build\driver\ - - - true - ..\gamesocket\bin\x64\Debug\;$(LibraryPath) - $(SolutionDir)bin\ - $(SolutionDir)build\driver\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)build\driver\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)build\driver\ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\;%(AdditionalIncludeDirectories) - - - Console - true - - - - - - - Level3 - Disabled - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\;%(AdditionalIncludeDirectories) - - - Console - true - %(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\;%(AdditionalIncludeDirectories) - - - Console - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\;%(AdditionalIncludeDirectories) - - - Console - true - true - true - - - - - - - - {2234670d-68e4-4f31-a6ac-e6891322818c} - - - - - - diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/android.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/android.meta deleted file mode 100644 index 86b0914..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/android.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: aa5c2fc9651e74ca5b21ac219ab0b6af -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/arm64.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/arm64.meta deleted file mode 100644 index 35de3ac..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/arm64.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1a39d686a75924fa387bc767a41f0a6c -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/armv7.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/armv7.meta deleted file mode 100644 index 3f217ab..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/armv7.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d2432b207d1034705804f67f910df282 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/ios.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/ios.meta deleted file mode 100644 index 7c9af62..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/ios.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1be21c1ea98114a97b2db5b0bd708755 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/platform.h.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/platform.h.meta deleted file mode 100644 index cf660c4..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/platform.h.meta +++ /dev/null @@ -1,103 +0,0 @@ -fileFormatVersion: 2 -guid: 6d2f995cc8e914dc68eff40e31bb9485 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - isOverridable: 1 - platformData: - - first: - '': Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux: 1 - Exclude Linux64: 1 - Exclude LinuxUniversal: 1 - Exclude OSXUniversal: 1 - Exclude WebGL: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Facebook: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Facebook: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Linux - second: - enabled: 0 - settings: - CPU: x86 - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: x86_64 - - first: - Standalone: LinuxUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/x86.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/x86.meta deleted file mode 100644 index 218d471..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/meta/x86.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f69e0175e4d99471eb4916c9090fccdf -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.c b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.c deleted file mode 100644 index f985dfd..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.c +++ /dev/null @@ -1,345 +0,0 @@ -#include "network.bindings.h" - -#include -#include -#include - -#if PLATFORM_WIN -#include -#include -#include -#else -#include -#include -#include -#include -#define closesocket(x) \ - close(x) -#endif - -// TODO: fix this -static int g_initialized; - -#if PLATFORM_WIN -const unsigned int SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; -#endif - -static int32_t -native_get_last_error() -{ -#if PLATFORM_WIN - return WSAGetLastError(); -#else - return errno; -#endif -} - -static int32_t -native_setsockopt(int64_t handle, int level, int optname, void *optval, int optlen, int32_t* errorcode) -{ - int retval; -#if PLATFORM_WIN - if ((retval = setsockopt((SOCKET)handle, level, optname, (char*)optval, optlen)) != 0) -#else - if ((retval = setsockopt((int)handle, level, optname, optval, (socklen_t)optlen)) != 0) -#endif - *errorcode = native_get_last_error(); - - return retval; -} - -static int32_t -native_getsockopt(int64_t handle, int level, int optname, void *optval, int optlen, int32_t* errorcode) -{ - int retval; - // Note: we don't use the return size as we don't have any variable size callers. -#if PLATFORM_WIN - if ((retval = getsockopt((SOCKET)handle, level, optname, (char*)optval, &optlen)) != 0) -#else - if ((retval = getsockopt((int)handle, level, optname, optval, (socklen_t*)&optlen)) != 0) -#endif - *errorcode = native_get_last_error(); - - return retval; -} - -static int32_t -native_set_blocking(int64_t handle, int blocking, int32_t *errorcode) -{ - int retval; -#if PLATFORM_WIN - if ((retval = ioctlsocket(handle, FIONBIO, (u_long*)&blocking)) != 0) -#else - if ((retval = fcntl(handle, F_GETFL, 0)) < 0) - { - *errorcode = native_get_last_error(); - return retval; - } - - if (blocking == 1) - retval |= O_NONBLOCK; - else - retval &= O_NONBLOCK; - - if ((retval = fcntl(handle, F_SETFL, retval)) < 0) -#endif - *errorcode = native_get_last_error(); - return retval; -} - -EXPORT_API int32_t -network_initialize() -{ - int retval = 0; - if (g_initialized == 0) - { -#if PLATFORM_WIN - WSADATA wsaData = {0}; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - } - ++g_initialized; - return retval; -} - -EXPORT_API int32_t -network_terminate() -{ - if (g_initialized > 0) - --g_initialized; - - if (g_initialized == 0) - { -#if PLATFORM_WIN - WSACleanup(); -#endif - } - return SUCCESS; -} - -EXPORT_API int32_t -network_set_nonblocking(int64_t handle, int32_t *errorcode) -{ - return native_set_blocking(handle, 1, errorcode); -} - -EXPORT_API int32_t -network_set_blocking(int64_t handle, int32_t *errorcode) -{ - return native_set_blocking(handle, 0, errorcode); -} - -EXPORT_API int32_t -network_set_send_buffer_size(int64_t handle, int size, int32_t *errorcode) -{ - return native_setsockopt(handle, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size), errorcode); -} - -EXPORT_API int32_t -network_get_send_buffer_size(int64_t handle, int *size, int32_t *errorcode) -{ - return native_getsockopt(handle, SOL_SOCKET, SO_SNDBUF, size, sizeof(*size), errorcode); -} - -EXPORT_API int32_t -network_set_receive_buffer_size(int64_t handle, int size, int32_t* errorcode) -{ - return native_setsockopt(handle, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size), errorcode); -} - -EXPORT_API int32_t -network_get_receive_buffer_size(int64_t handle, int *size, int32_t* errorcode) -{ - return native_getsockopt(handle, SOL_SOCKET, SO_RCVBUF, size, sizeof(*size), errorcode); -} - -EXPORT_API int32_t -network_set_receive_timeout(int64_t handle, int64_t timeout_msec, int32_t* errorcode) -{ -#if PLATFORM_WIN - DWORD to = (DWORD)timeout_msec; -#else - struct timeval to; - to.tv_sec = (long)(timeout_msec / 1000); - to.tv_usec = (long)((timeout_msec - (to.tv_sec * 1000)) / 1000000); -#endif - return native_setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to), errorcode); -} - -EXPORT_API int32_t -network_set_send_timeout(int64_t handle, int64_t timeout_msec, int32_t* errorcode) -{ -#if PLATFORM_WIN - DWORD to = (DWORD)timeout_msec; -#else - struct timeval to; - to.tv_sec = (time_t)(timeout_msec / 1000); - to.tv_usec = (suseconds_t)(timeout_msec - (to.tv_sec * 1000)) / 1000000; -#endif - return native_setsockopt(handle, SOL_SOCKET, SO_SNDTIMEO, &to, sizeof(to), errorcode); -} - -EXPORT_API int32_t -network_set_connection_reset(int64_t handle, int value) -{ - int result = 0; -#if PLATFORM_WIN - BOOL newValue = (BOOL)value; - DWORD bytesReturned = 0; - result = WSAIoctl(handle, SIO_UDP_CONNRESET, &newValue, sizeof(newValue), NULL, 0, &bytesReturned, NULL, NULL); -#else - // Avoid compiler warnings on non-windows - (void)handle; - (void)value; -#endif - return result; -} - -EXPORT_API int32_t network_get_socket_address(int64_t socket_handle, network_address* own_address, int32_t* errorcode) -{ - int retval = 0; -#if PLATFORM_WIN - if ((retval = getsockname(socket_handle, (struct sockaddr *)own_address, (int *)&own_address->length)) == 0) -#else - if ((retval = getsockname(socket_handle, (struct sockaddr *)own_address, (socklen_t*)&own_address->length)) < 0) -#endif - *errorcode = native_get_last_error(); - return retval; -} - -EXPORT_API int32_t -network_create_and_bind(int64_t *socket_handle, network_address *address, int32_t* errorcode) -{ - int64_t s = socket(address->addr.sa_family, SOCK_DGRAM, IPPROTO_UDP); - if (s < 0) - { - *errorcode = native_get_last_error(); - return -1; - } - if (address->addr.sa_family == AF_INET6) - { - int off = 0; - int result = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off)); - if (result != 0) - { - *errorcode = native_get_last_error(); - closesocket(s); - return -1; - } - } -#if PLATFORM_WIN - if (bind(s, (const SOCKADDR*)address, (int)address->length) != 0) -#else - if (bind(s, (const struct sockaddr*)address, (int)address->length) != 0) -#endif - { - *errorcode = native_get_last_error(); - closesocket(s); - return -1; - } - - *socket_handle = s; - return 0; -} - -EXPORT_API int32_t -network_sendmsg(int64_t socket_handle, network_iov_t *iov, int32_t iov_len, network_address *address, int32_t* errorcode) -{ - int ret = 0; -#if PLATFORM_WIN - WSABUF *iv = (WSABUF*)alloca(sizeof(WSABUF) * iov_len); - for (int32_t i = 0; i < iov_len; ++i) - { - iv[i].buf = iov[i].data; - iv[i].len = iov[i].length; - } - - uint32_t bytes_send; - ret = WSASendTo(socket_handle, iv, iov_len, - &bytes_send, 0, (const SOCKADDR *)address, - address->length, NULL, NULL); - if (ret != SOCKET_ERROR) - ret = bytes_send; -#else - struct iovec *iv = (struct iovec*)alloca(sizeof(struct iovec) * iov_len); - for (int32_t i = 0; i < iov_len; ++i) - { - iv[i].iov_base = iov[i].data; - iv[i].iov_len = iov[i].length; - } - - struct msghdr message; - message.msg_name = (struct sockaddr *)address; - message.msg_namelen = address->length; - message.msg_iov = iv; - message.msg_iovlen = iov_len; - message.msg_control = NULL; - message.msg_controllen = 0; - message.msg_flags = 0; - - ret = sendmsg(socket_handle, (const struct msghdr *)&message, 0); -#endif - if (ret < 0) - *errorcode = native_get_last_error(); - return ret; -} - -EXPORT_API int32_t -network_recvmsg(int64_t socket_handle, network_iov_t *iov, int32_t iov_len, network_address *remote, int32_t* errorcode) -{ - int ret = 0; - -#if PLATFORM_WIN - WSABUF *iv = (WSABUF*)alloca(sizeof(WSABUF) * iov_len); - for (int32_t i = 0; i < iov_len; ++i) - { - iv[i].buf = iov[i].data; - iv[i].len = iov[i].length; - } - - uint32_t flags = 0; - uint32_t bytes_received = 0; - ret = WSARecvFrom(socket_handle, iv, iov_len, - &bytes_received, &flags, (SOCKADDR *)remote, - &remote->length, NULL, NULL); - if (ret != SOCKET_ERROR) - ret = bytes_received; -#else - struct iovec *iv = (struct iovec*)alloca(sizeof(struct iovec) * iov_len); - for (int32_t i = 0; i < iov_len; ++i) - { - iv[i].iov_base = iov[i].data; - iv[i].iov_len = iov[i].length; - } - - struct msghdr message; - message.msg_name = (struct sockaddr *)remote; - message.msg_namelen = remote->length; - message.msg_iov = iv; - message.msg_iovlen = iov_len; - message.msg_control = NULL; - message.msg_controllen = 0; - message.msg_flags = 0; - - ret = recvmsg(socket_handle, &message, 0); - remote->length = message.msg_namelen; - if (message.msg_flags & MSG_TRUNC) - { - *errorcode = 10040; // Match the error you would get on windows - return -1; - } -#endif - if (ret < 0) - *errorcode = native_get_last_error(); - return ret; -} - -EXPORT_API int32_t network_close(int64_t *socket_handle, int32_t* errorcode) -{ - int retval = closesocket(*socket_handle); - if (retval == SOCKET_ERROR) - *errorcode = native_get_last_error(); - - *socket_handle = 0; - return retval; -} diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs deleted file mode 100644 index a0c0cfe..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Unity.Collections.LowLevel.Unsafe; - -namespace Unity.Networking.Transport.Samples -{ - [StructLayout(LayoutKind.Sequential)] - public unsafe struct network_iovec - { - public int len; - public void* buf; - } - - // TODO: Fix this internally incase there are other platforms that also does - // it differently so it may result in similar issues -# if (UNITY_EDITOR_OSX || ((UNITY_STANDALONE_OSX || UNITY_IOS) && !UNITY_EDITOR)) - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sa_family_t - { - public const int size = sizeof(byte) * 2; - [FieldOffset(0)] public byte sa_len; - [FieldOffset(1)] public byte sa_family; - } -# else - internal unsafe struct sa_family_t - { - public const int size = sizeof(ushort); - public ushort sa_family; - } -# endif - - internal unsafe struct in_addr - { - public uint s_addr; - } - - [StructLayout(LayoutKind.Explicit)] - public unsafe struct network_address : IEquatable - { - internal const int Length = 28; - [FieldOffset(0)] public fixed byte data[Length]; - [FieldOffset(28)] public int length; - - public bool Equals(network_address other) - { - if (length != other.length) - return false; - - fixed(void* p = this.data) - return UnsafeUtility.MemCmp(p, other.data, length) == 0; - } - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr - { - [FieldOffset(0)] public fixed byte data[16]; - - [FieldOffset(0)] public sa_family_t sin_family; - [FieldOffset(2)] public fixed byte sin_zero[14]; - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr_in - { - [FieldOffset(0)] public fixed byte data[16]; - - [FieldOffset(0)] public sa_family_t sin_family; - [FieldOffset(2)] public ushort sin_port; - [FieldOffset(4)] public in_addr sin_addr; - [FieldOffset(8)] public fixed byte sin_zero[8]; - } - - internal unsafe struct in_addr6 - { - public fixed byte s6_addr[16]; - } - - [StructLayout(LayoutKind.Explicit)] - internal unsafe struct sockaddr_in6 - { - [FieldOffset(0)] public fixed byte data[28]; - - [FieldOffset(0)] public sa_family_t sin6_family; - [FieldOffset(2)] public ushort sin6_port; - [FieldOffset(4)] public uint sin6_flowinfo; - [FieldOffset(8)] public in_addr6 sin6_addr; - [FieldOffset(24)] public uint sin6_scope_id; - } - - public static unsafe class NativeBindings - { -#if UNITY_IOS && !UNITY_EDITOR - const string m_DllName = "__Internal"; -#elif UNITY_EDITOR_WIN && UNITY_2019_2_OR_NEWER - const string m_DllName = "network.bindings.dll"; -#elif UNITY_EDITOR_OSX && UNITY_2019_2_OR_NEWER - const string m_DllName = "network.bindings.bundle"; -#else - const string m_DllName = "network.bindings"; -#endif - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_initialize(); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_terminate(); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_create_and_bind(ref long socket_handle, ref network_address address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_close(ref long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_nonblocking(long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_send_buffer_size(long socket_handle, int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_receive_buffer_size(long socket_handle, int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_send_buffer_size(long socket_handle, ref int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_receive_buffer_size(long socket_handle, ref int size, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_receive_timeout(long socket_handle, ulong timeout, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_blocking(long socket_handle, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_set_connection_reset(long socket_handle, int value); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_get_socket_address(long socket_handle, ref network_address own_address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_sendmsg(long socket_handle, void* iov, int iov_len, - ref network_address address, ref int errorcode); - - [DllImport(m_DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern int network_recvmsg(long socket_handle, void* iov, int iov_len, - ref network_address remote, ref int errorcode); - } -} diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs.meta deleted file mode 100644 index 464a485..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 93dc56a3a065d4e99bd04fbedfe39914 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.h b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.h deleted file mode 100644 index 16bf9df..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include "platform.h" - -#if PLATFORM_WIN -# include -# include -# pragma comment(lib, "ws2_32.lib") -#else -# include -# include -# define WSAEWOULDBLOCK EAGAIN -# define WSAECONNRESET ECONNRESET -# define SOCKET_ERROR -1 -#endif - -#if PLATFORM_ANDROID -#include -#endif - -#define SUCCESS 0 - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct -{ - union - { - struct sockaddr addr; - struct sockaddr_in addr_in; - struct sockaddr_in6 addr_in6; - }; - int length; -} network_address; - -typedef struct -{ - int32_t length; - uint8_t *data; -} network_iov_t; - -// TODO: Should we name them ..._udp... -EXPORT_API int32_t network_initialize(); -EXPORT_API int32_t network_terminate(); - -EXPORT_API int32_t network_create_and_bind(int64_t *socket_handle, network_address *address, int32_t *errorcode); -EXPORT_API int32_t network_sendmsg(int64_t socket_handle, network_iov_t *iov, int32_t iov_len, network_address *address, int32_t *errorcode); -EXPORT_API int32_t network_recvmsg(int64_t socket_handle, network_iov_t *iov, int32_t iov_len, network_address *remote, int32_t *errorcode); -EXPORT_API int32_t network_close(int64_t *socket_handle, int32_t *errorcode); - -EXPORT_API int32_t network_set_nonblocking(int64_t socket_handle, int32_t *errorcode); -EXPORT_API int32_t network_set_blocking(int64_t socket_handle, int32_t *errorcode); - -EXPORT_API int32_t network_set_send_buffer_size(int64_t handle, int size, int32_t *errorcode); -EXPORT_API int32_t network_get_send_buffer_size(int64_t handle, int *size, int32_t *errorcode); - -EXPORT_API int32_t network_set_receive_buffer_size(int64_t handle, int size, int32_t *errorcode); -EXPORT_API int32_t network_get_receive_buffer_size(int64_t handle, int *size, int32_t *errorcode); - -EXPORT_API int32_t network_set_connection_reset(int64_t handle, int value); -EXPORT_API int32_t network_get_socket_address(int64_t socket_handle, network_address* own_address, int32_t* errorcode); - -EXPORT_API int32_t network_set_receive_timeout(int64_t handle, int64_t timeout_msec, int32_t *errorcode); -EXPORT_API int32_t network_set_send_timeout(int64_t handle, int64_t timeout_msec, int32_t *errorcode); - -#ifdef __cplusplus -} -#endif diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.pdb.meta b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.pdb.meta deleted file mode 100644 index 4699231..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.pdb.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 70549431d228545739524a2a46ef1d9f -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.vcxproj b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.vcxproj deleted file mode 100644 index 6f259db..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/network.bindings.vcxproj +++ /dev/null @@ -1,180 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {2234670D-68E4-4F31-A6AC-E6891322818C} - Win32Proj - multiplaynative - 10.0.16299.0 - network.bindings - - - - DynamicLibrary - true - v141 - MultiByte - - - DynamicLibrary - false - v141 - true - MultiByte - - - DynamicLibrary - true - v141 - MultiByte - - - DynamicLibrary - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)bin\ - $(SolutionDir)build\network.bindings\ - - - true - $(SolutionDir)bin\ - $(SolutionDir)build\network.bindings\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)build\network.bindings\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)build\network.bindings\ - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;NETWORKnetwork.network.bindings_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - - - - - - - Level3 - Disabled - _DEBUG;_WINDOWS;_USRDLL;NETWORK_EXPORTS;%(PreprocessorDefinitions) - true - /DGAMESOCKET_EXPORTS - MultiThreadedDebug - - - Windows - true - - - - - - - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;NETWORKnetwork.network.bindings_EXPORTS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_WINDOWS;_USRDLL;NETWORK_EXPORTS;%(PreprocessorDefinitions) - true - MultiThreaded - - - Windows - true - true - true - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/platform.h b/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/platform.h deleted file mode 100644 index 347146f..0000000 --- a/Samples~/CustomNetworkInterface/Scripts/network.bindings~/source/platform.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) -#define PLATFORM_WIN 1 -#elif defined(__MACH__) || defined(__APPLE__) -#define PLATFORM_OSX 1 -#elif defined(__ANDROID__) -#define PLATFORM_ANDROID 1 -#elif defined(__linux__) -#define PLATFORM_LINUX 1 -#endif - -#include - -#if PLATFORM_WIN -#define WIN32_LEAN_AND_MEAN -#ifdef NETWORK_EXPORTS -#define EXPORT_API __declspec(dllexport) -#else -#define EXPORT_API __declspec(dllimport) -#endif -#elif PLATFORM_ANDROID -#define EXPORT_API __attribute__((visibility("default"))) -#else -#define EXPORT_API -#endif diff --git a/Samples~/Ping/README.md b/Samples~/Ping/README.md deleted file mode 100644 index 527e9ea..0000000 --- a/Samples~/Ping/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Unity transport Ping Sample - -In order to use this advanced sample, please install the jobs package from the package manager. - - From the editor : Window → Package Manager - -If the Package is not yet accessible from the Unity registry, you need to enable the preview packages. - - From the Package Manager : press the cogs icon → Advanced Project Settings → Enable Preview Packages. diff --git a/Samples~/Ping/README.md.meta b/Samples~/Ping/README.md.meta deleted file mode 100644 index 1b9ab36..0000000 --- a/Samples~/Ping/README.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 892cdd54cfd66486c83b260f91a57f84 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples~/Ping/Scripts/Ping.asmdef b/Samples~/Ping/Scripts/Ping.asmdef index 607ac8f..088b4a5 100644 --- a/Samples~/Ping/Scripts/Ping.asmdef +++ b/Samples~/Ping/Scripts/Ping.asmdef @@ -14,15 +14,7 @@ "overrideReferences": false, "precompiledReferences": [], "autoReferenced": false, - "defineConstraints": [ - "ENABLE_JOBS" - ], - "versionDefines": [ - { - "name": "com.unity.jobs", - "expression": "0.0", - "define": "ENABLE_JOBS" - } - ], + "defineConstraints": [], + "versionDefines": [], "noEngineReferences": false } \ No newline at end of file diff --git a/Samples~/Ping/Scripts/PingClientBehaviour.cs b/Samples~/Ping/Scripts/PingClientBehaviour.cs index f80fa6b..5bd18df 100644 --- a/Samples~/Ping/Scripts/PingClientBehaviour.cs +++ b/Samples~/Ping/Scripts/PingClientBehaviour.cs @@ -105,7 +105,7 @@ void FixedUpdate() // enough since we can get multiple FixedUpdate per frame on slow clients m_updateHandle.Complete(); - var serverEP = PingClientUIBehaviour.ServerEndPoint; + var serverEP = PingClientUIBehaviour.ServerEndpoint; // If the client ui indicates we should be sending pings but we do not have an active connection we create one if (serverEP.IsValid && !m_clientToServerConnection[0].IsCreated) m_clientToServerConnection[0] = m_ClientDriver.Connect(serverEP); diff --git a/Samples~/Ping/Scripts/PingClientUIBehaviour.cs b/Samples~/Ping/Scripts/PingClientUIBehaviour.cs index 59f1587..41aa97a 100644 --- a/Samples~/Ping/Scripts/PingClientUIBehaviour.cs +++ b/Samples~/Ping/Scripts/PingClientUIBehaviour.cs @@ -9,8 +9,8 @@ namespace Unity.Networking.Transport.Samples selected ip. */ public class PingClientUIBehaviour : MonoBehaviour { - // The EndPoint the ping client should ping, will be a non-created end point when ping should not run. - public static NetworkEndPoint ServerEndPoint { get; private set; } + // The Endpoint the ping client should ping, will be a non-created end point when ping should not run. + public static NetworkEndpoint ServerEndpoint { get; private set; } // Ping statistics static int s_PingTime; @@ -22,7 +22,7 @@ void Start() { s_PingTime = 0; s_PingCounter = 0; - ServerEndPoint = default(NetworkEndPoint); + ServerEndpoint = default(NetworkEndpoint); } void OnGUI() @@ -40,7 +40,7 @@ public static void UpdateStats(int count, int time) void UpdatePingClientUI() { GUILayout.Label("PING " + s_PingCounter + ": " + s_PingTime + "ms"); - if (!ServerEndPoint.IsValid) + if (!ServerEndpoint.IsValid) { // Ping is not currently running, display ui for starting a ping if (GUILayout.Button("Start ping")) @@ -48,9 +48,9 @@ void UpdatePingClientUI() ushort port = 9000; if (string.IsNullOrEmpty(m_CustomIp)) { - var endpoint = NetworkEndPoint.LoopbackIpv4; + var endpoint = NetworkEndpoint.LoopbackIpv4; endpoint.Port = port; - ServerEndPoint = endpoint; + ServerEndpoint = endpoint; } else { @@ -60,7 +60,7 @@ void UpdatePingClientUI() port = newPort; Debug.Log($"Connecting to PingServer at {endpoint[0]}:{port}."); - ServerEndPoint = NetworkEndPoint.Parse(endpoint[0], port); + ServerEndpoint = NetworkEndpoint.Parse(endpoint[0], port); } } @@ -71,7 +71,7 @@ void UpdatePingClientUI() // Ping is running, display ui for stopping it if (GUILayout.Button("Stop ping")) { - ServerEndPoint = default(NetworkEndPoint); + ServerEndpoint = default(NetworkEndpoint); } } } diff --git a/Samples~/Ping/Scripts/PingMainThreadClientBehaviour.cs b/Samples~/Ping/Scripts/PingMainThreadClientBehaviour.cs index 2dcf0bd..0e69ef1 100644 --- a/Samples~/Ping/Scripts/PingMainThreadClientBehaviour.cs +++ b/Samples~/Ping/Scripts/PingMainThreadClientBehaviour.cs @@ -42,10 +42,10 @@ void FixedUpdate() m_ClientDriver.ScheduleUpdate().Complete(); // If the client ui indicates we should be sending pings but we do not have an active connection we create one - if (PingClientUIBehaviour.ServerEndPoint.IsValid && !m_clientToServerConnection.IsCreated) - m_clientToServerConnection = m_ClientDriver.Connect(PingClientUIBehaviour.ServerEndPoint); + if (PingClientUIBehaviour.ServerEndpoint.IsValid && !m_clientToServerConnection.IsCreated) + m_clientToServerConnection = m_ClientDriver.Connect(PingClientUIBehaviour.ServerEndpoint); // If the client ui indicates we should not be sending pings but we do have a connection we close that connection - if (!PingClientUIBehaviour.ServerEndPoint.IsValid && m_clientToServerConnection.IsCreated) + if (!PingClientUIBehaviour.ServerEndpoint.IsValid && m_clientToServerConnection.IsCreated) { m_clientToServerConnection.Disconnect(m_ClientDriver); m_clientToServerConnection = default(NetworkConnection); diff --git a/Samples~/Ping/Scripts/PingMainThreadServerBehaviour.cs b/Samples~/Ping/Scripts/PingMainThreadServerBehaviour.cs index 329f677..1f11225 100644 --- a/Samples~/Ping/Scripts/PingMainThreadServerBehaviour.cs +++ b/Samples~/Ping/Scripts/PingMainThreadServerBehaviour.cs @@ -14,7 +14,7 @@ void Start() { // Create the server driver, bind it to a port and start listening for incoming connections m_ServerDriver = NetworkDriver.Create(); - var addr = NetworkEndPoint.AnyIpv4; + var addr = NetworkEndpoint.AnyIpv4; addr.Port = 9000; if (m_ServerDriver.Bind(addr) != 0) Debug.Log("Failed to bind to port 9000"); diff --git a/Samples~/Ping/Scripts/PingServerBehaviour.cs b/Samples~/Ping/Scripts/PingServerBehaviour.cs index ba0dde2..3b709bf 100644 --- a/Samples~/Ping/Scripts/PingServerBehaviour.cs +++ b/Samples~/Ping/Scripts/PingServerBehaviour.cs @@ -18,7 +18,7 @@ void Start() ushort serverPort = 9000; // Create the server driver, bind it to a port and start listening for incoming connections m_ServerDriver = NetworkDriver.Create(); - var addr = NetworkEndPoint.AnyIpv4; + var addr = NetworkEndpoint.AnyIpv4; addr.Port = serverPort; if (m_ServerDriver.Bind(addr) != 0) Debug.Log($"Failed to bind to port {serverPort}"); diff --git a/Samples~/Pipeline/Scripts/ClientBehaviour.cs b/Samples~/Pipeline/Scripts/ClientBehaviour.cs index d56118d..fb7154e 100644 --- a/Samples~/Pipeline/Scripts/ClientBehaviour.cs +++ b/Samples~/Pipeline/Scripts/ClientBehaviour.cs @@ -20,7 +20,7 @@ void Start() m_SequencedPipeline = m_Driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); m_Connection = default(NetworkConnection); - var endpoint = NetworkEndPoint.LoopbackIpv4; + var endpoint = NetworkEndpoint.LoopbackIpv4; endpoint.Port = 9000; m_Connection = m_Driver.Connect(endpoint); } @@ -45,7 +45,7 @@ void Update() NetworkEvent.Type cmd; while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != - NetworkEvent.Type.Empty) + NetworkEvent.Type.Empty) { if (cmd == NetworkEvent.Type.Connect) { diff --git a/Samples~/Pipeline/Scripts/ServerBehaviour.cs b/Samples~/Pipeline/Scripts/ServerBehaviour.cs index 2cbddbb..518a652 100644 --- a/Samples~/Pipeline/Scripts/ServerBehaviour.cs +++ b/Samples~/Pipeline/Scripts/ServerBehaviour.cs @@ -20,7 +20,7 @@ void Start() //m_UnreliablePipeline = NetworkPipeline.Null; m_SequencedPipeline = m_Driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var endpoint = NetworkEndPoint.AnyIpv4; + var endpoint = NetworkEndpoint.AnyIpv4; endpoint.Port = 9000; if (m_Driver.Bind(endpoint) != 0) Debug.Log("Failed to bind to port 9000"); @@ -65,7 +65,7 @@ void Update() NetworkEvent.Type cmd; while ((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != - NetworkEvent.Type.Empty) + NetworkEvent.Type.Empty) { if (cmd == NetworkEvent.Type.Data) { diff --git a/Samples~/RelayPing/README.md b/Samples~/RelayPing/README.md index 4037d93..81e1d61 100644 --- a/Samples~/RelayPing/README.md +++ b/Samples~/RelayPing/README.md @@ -1,11 +1,3 @@ # Unity transport Relay Ping Sample -In order to use this advanced sample, please install the jobs package from the package manager. - - From the editor : Window → Package Manager - -If the Package is not yet accessible from the Unity registry, you need to enable the preview packages. - - From the Package Manager : press the cogs icon → Advanced Project Settings → Enable Preview Packages. - -You need also to get familiar and install [the Relay SDK](https://docs.unity.com/relay/introduction.html). \ No newline at end of file +In order to use this advanced sample, you need to install and get yourself familiar with [the Relay SDK](https://docs.unity.com/relay/introduction.html). diff --git a/Samples~/RelayPing/Scenes/SampleScene.unity.meta b/Samples~/RelayPing/Scenes/SampleScene.unity.meta index 77b0169..309d145 100644 --- a/Samples~/RelayPing/Scenes/SampleScene.unity.meta +++ b/Samples~/RelayPing/Scenes/SampleScene.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a0c0ef7484c8b22448030c05c151bfb3 +guid: 065c6771ae4bdde478e6b413c326f284 DefaultImporter: externalObjects: {} userData: diff --git a/Samples~/RelayPing/Scripts/Code.asmdef b/Samples~/RelayPing/Scripts/Code.asmdef index 84b55e7..bcea0cd 100644 --- a/Samples~/RelayPing/Scripts/Code.asmdef +++ b/Samples~/RelayPing/Scripts/Code.asmdef @@ -16,23 +16,15 @@ "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, - "defineConstraints": [ - "ENABLE_JOBS", - "ENABLE_RELAY" + "defineConstraints": [ + "ENABLE_RELAY" ], "versionDefines": [ { - "name": "com.unity.jobs", - "expression": "0.0", - "define": "ENABLE_JOBS" - }, - { "name": "Unity.Services.Relay", "expression": "0.0", "define": "ENABLE_RELAY" } ], "noEngineReferences": false -} - - \ No newline at end of file +} \ No newline at end of file diff --git a/Samples~/RelayPing/Scripts/PingClientBehaviour.cs b/Samples~/RelayPing/Scripts/PingClientBehaviour.cs index 6dc23b2..1c87113 100644 --- a/Samples~/RelayPing/Scripts/PingClientBehaviour.cs +++ b/Samples~/RelayPing/Scripts/PingClientBehaviour.cs @@ -46,13 +46,14 @@ void InitDriver(ref RelayServerData relayServerData) m_clientToServerConnection = new NativeArray(1, Allocator.Persistent); } - public IEnumerator ConnectAndBind(string joinCode) { + public IEnumerator ConnectAndBind(string joinCode) + { var initTask = UnityServices.InitializeAsync(); - while(!initTask.IsCompleted) + while (!initTask.IsCompleted) yield return null; var joinTask = Unity.Services.Relay.Relay.Instance.JoinAllocationAsync(joinCode); - while(!joinTask.IsCompleted) + while (!joinTask.IsCompleted) yield return null; if (joinTask.IsFaulted) @@ -75,7 +76,7 @@ public IEnumerator ConnectAndBind(string joinCode) { Debug.Log($"client: {allocation.AllocationId}"); - RelayServerEndpoint defaultEndPoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp, + RelayServerEndpoint defaultEndpoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp, true, false, allocation.RelayServer.IpV4, allocation.RelayServer.Port); foreach (var endPoint @@ -83,21 +84,21 @@ public IEnumerator ConnectAndBind(string joinCode) { { #if ENABLE_MANAGED_UNITYTLS if (endPoint.Secure == true && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp) - defaultEndPoint = endPoint; + defaultEndpoint = endPoint; #else if (endPoint.Secure == false && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp) - defaultEndPoint = endPoint; + defaultEndpoint = endPoint; #endif } - var serverEndpoint = NetworkEndPoint.Parse(defaultEndPoint.Host, (ushort)defaultEndPoint.Port); + 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); + var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, defaultEndpoint.Secure); relayServerData.ComputeNewNonce(); InitDriver(ref relayServerData); - if (m_ClientDriver.Bind(NetworkEndPoint.AnyIpv4) != 0) + if (m_ClientDriver.Bind(NetworkEndpoint.AnyIpv4) != 0) { Debug.LogError("Client failed to bind"); } diff --git a/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs b/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs index 10fc910..99ba429 100644 --- a/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs +++ b/Samples~/RelayPing/Scripts/PingClientUIBehaviour.cs @@ -49,39 +49,45 @@ public async void OnSignIn() await AuthenticationService.Instance.SignInAnonymouslyAsync(); Debug.Log($"Logging in with PlayerID {AuthenticationService.Instance.PlayerId}"); - if (AuthenticationService.Instance.IsSignedIn) { + if (AuthenticationService.Instance.IsSignedIn) + { signedIn = true; } } - public void ClientBindAndConnect() { - + public void ClientBindAndConnect() + { var clientBehaviour = gameObject.AddComponent() as PingClientBehaviour; StartCoroutine(clientBehaviour.ConnectAndBind(m_JoinCode)); } - public void ServerBindAndConnect() { - + public void ServerBindAndConnect() + { var serverBehaviour = gameObject.AddComponent() as PingServerBehaviour; StartCoroutine(serverBehaviour.ConnectAndBind()); } void UpdatePingClientUI() { - if (!signedIn) { - if (GUILayout.Button("SignIn")) { + if (!signedIn) + { + if (GUILayout.Button("SignIn")) + { OnSignIn(); } } - else { - - if (!isClient && !isServer){ - if (GUILayout.Button("Start Server") && (!isServer || !isClient)) { + else + { + if (!isClient && !isServer) + { + if (GUILayout.Button("Start Server") && (!isServer || !isClient)) + { isServer = true; isClient = false; ServerBindAndConnect(); } - if (GUILayout.Button("Start Client") && (!isServer || !isClient)) { + if (GUILayout.Button("Start Client") && (!isServer || !isClient)) + { isClient = true; isServer = false; ClientBindAndConnect(); @@ -89,14 +95,17 @@ void UpdatePingClientUI() } GUILayout.Label("Join Code"); - if (isServer || isClient) { + if (isServer || isClient) + { GUILayout.Label(m_JoinCode); } - else { + else + { m_JoinCode = GUILayout.TextField(m_JoinCode); } - - if (isClient) { + + if (isClient) + { GUILayout.Label("PING " + s_PingCounter + ": " + s_PingTime + "ms"); } } diff --git a/Samples~/RelayPing/Scripts/PingServerBehaviour.cs b/Samples~/RelayPing/Scripts/PingServerBehaviour.cs index d4bea73..4accf12 100644 --- a/Samples~/RelayPing/Scripts/PingServerBehaviour.cs +++ b/Samples~/RelayPing/Scripts/PingServerBehaviour.cs @@ -35,11 +35,12 @@ void InitDriver(ref RelayServerData relayServerData) m_connections = new NativeList(16, Allocator.Persistent); } - public IEnumerator ConnectAndBind() { + public IEnumerator ConnectAndBind() + { var initTask = UnityServices.InitializeAsync(); - while(!initTask.IsCompleted) + while (!initTask.IsCompleted) yield return null; - + var regionsTask = Unity.Services.Relay.Relay.Instance.ListRegionsAsync(); while (!regionsTask.IsCompleted) yield return null; @@ -55,7 +56,7 @@ public IEnumerator ConnectAndBind() { var allocationTask = Unity.Services.Relay.Relay.Instance.CreateAllocationAsync(5, regionId); while (!allocationTask.IsCompleted) yield return null; - + if (allocationTask.IsFaulted) { Debug.LogError("Allocation request failed."); @@ -76,7 +77,7 @@ public IEnumerator ConnectAndBind() { PingClientUIBehaviour.m_JoinCode = joinCodeTask.Result; - RelayServerEndpoint defaultEndPoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp, + RelayServerEndpoint defaultEndpoint = new RelayServerEndpoint("udp", RelayServerEndpoint.NetworkOptions.Udp, true, false, allocation.RelayServer.IpV4, allocation.RelayServer.Port); foreach (var endPoint @@ -84,27 +85,27 @@ public IEnumerator ConnectAndBind() { { #if ENABLE_MANAGED_UNITYTLS if (endPoint.Secure == true && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp) - defaultEndPoint = endPoint; + defaultEndpoint = endPoint; #else if (endPoint.Secure == false && endPoint.Network == RelayServerEndpoint.NetworkOptions.Udp) - defaultEndPoint = endPoint; + defaultEndpoint = endPoint; #endif } - var serverEndpoint = NetworkEndPoint.Parse(defaultEndPoint.Host, (ushort)defaultEndPoint.Port); + 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); + ref connectionData, ref key, defaultEndpoint.Secure); relayServerData.ComputeNewNonce(); InitDriver(ref relayServerData); - if (m_ServerDriver.Bind(NetworkEndPoint.AnyIpv4) != 0) + if (m_ServerDriver.Bind(NetworkEndpoint.AnyIpv4) != 0) { Debug.LogError("Server failed to bind"); } diff --git a/Samples~/Soaker/README.md b/Samples~/Soaker/README.md index 2b0ac50..9476288 100644 --- a/Samples~/Soaker/README.md +++ b/Samples~/Soaker/README.md @@ -1,10 +1,9 @@ # Unity transport Soaker Sample -In order to use this advanced sample, please install the jobs package from the package manager. +In order to use this advanced sample, please install the jobs package from the package manager. From the editor : Window → Package Manager If the Package is not yet accessible from the Unity registry, you need to enable the preview packages. - From the Package Manager : press the cogs icon → Advanced Project Settings → Enable Preview Packages. - + From the Package Manager : press the cogs icon → Advanced Project Settings → Enable Preview Packages. diff --git a/Samples~/Soaker/Scripts/SoakClient.cs b/Samples~/Soaker/Scripts/SoakClient.cs index 0ccb58f..9854ec2 100644 --- a/Samples~/Soaker/Scripts/SoakClient.cs +++ b/Samples~/Soaker/Scripts/SoakClient.cs @@ -14,7 +14,7 @@ public class SoakClient : IDisposable public NetworkPipeline Pipeline; public NetworkPipelineStageId ReliableStageId; public NetworkPipelineStageId SimulatorStageId; - public NetworkEndPoint ServerEndPoint; + public NetworkEndpoint ServerEndpoint; public string CustomIp = ""; public NativeArray ConnectionHandle; @@ -29,12 +29,12 @@ public SoakClient(double sendInterval, int packetSize, int duration) { var settings = new NetworkSettings(); settings.WithSimulatorStageParameters( - maxPacketSize: packetSize, maxPacketCount: 30, packetDelayMs: 25, packetDropPercentage: 10); + maxPacketSize: NetworkParameterConstants.MTU, maxPacketCount: 30, packetDelayMs: 25, packetDropPercentage: 10, mode: ApplyMode.AllPackets); DriverHandle = NetworkDriver.Create(settings); //Pipeline = DriverHandle.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); Pipeline = DriverHandle.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - ReliableStageId = NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)); - SimulatorStageId = NetworkPipelineStageCollection.GetStageId(typeof(SimulatorPipelineStage)); + ReliableStageId = NetworkPipelineStageId.Get(); + SimulatorStageId = NetworkPipelineStageId.Get(); if (packetSize > NetworkParameterConstants.MTU) { Debug.LogWarning("Truncating packet size to MTU"); @@ -71,9 +71,9 @@ public SoakClient(double sendInterval, int packetSize, int duration) SoakStatisticsHandle[1] = new SoakStatisticsPoint(); } - public void Start(NetworkEndPoint endpoint) + public void Start(NetworkEndpoint endpoint) { - ServerEndPoint = endpoint; + ServerEndpoint = endpoint; //Reset the context var ctx = SoakJobContextsHandle[0]; SoakJobContextsHandle[0] = new SoakJobContext @@ -119,9 +119,9 @@ public void PreUpdate() DumpSimulatorStatistics(); } - if (ServerEndPoint.IsValid && !ConnectionHandle[0].IsCreated) - ConnectionHandle[0] = DriverHandle.Connect(ServerEndPoint); - else if (!ServerEndPoint.IsValid && ConnectionHandle[0].IsCreated) + if (ServerEndpoint.IsValid && !ConnectionHandle[0].IsCreated) + ConnectionHandle[0] = DriverHandle.Connect(ServerEndpoint); + else if (!ServerEndpoint.IsValid && ConnectionHandle[0].IsCreated) ConnectionHandle[0].Disconnect(DriverHandle); } diff --git a/Samples~/Soaker/Scripts/SoakClientBehaviour.cs b/Samples~/Soaker/Scripts/SoakClientBehaviour.cs index 2a5453a..4a7276c 100644 --- a/Samples~/Soaker/Scripts/SoakClientBehaviour.cs +++ b/Samples~/Soaker/Scripts/SoakClientBehaviour.cs @@ -89,15 +89,6 @@ void Log() { var gen = new SoakStatisticsReporter(); gen.GenerateReport(m_Report, m_Manager.ClientInfos()); - /* - MemoryStream ms = new MemoryStream(); - DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(SoakStatisticsReport)); - ser.WriteObject(ms, report); - - byte[] json = ms.ToArray(); - ms.Close(); - Debug.Log(Encoding.UTF8.GetString(json, 0, json.Length)); - */ } void OnGUI() @@ -117,33 +108,6 @@ void OnGUI() StopSoak(); } } - - /* - foreach (var client in m_SoakClients) - { - //GUILayout.Label("PING " + client.SoakJobStatisticsHandle[0].FrameId + ": " + client.SoakJobStatisticsHandle[0].PingTime + "ms"); - if (!client.ServerEndPoint.IsValid) - { - if (GUILayout.Button("Start ping")) - { - if (string.IsNullOrEmpty(client.CustomIp)) - client.ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 9000); - else - { - client.ServerEndPoint = new IPEndPoint(IPAddress.Parse(client.CustomIp), 9000); - } - } - client.CustomIp = GUILayout.TextField(client.CustomIp); - } - else - { - if (GUILayout.Button("Stop ping")) - { - client.ServerEndPoint = default(NetworkEndPoint); - } - } - } - */ } } } diff --git a/Samples~/Soaker/Scripts/SoakClientJobManager.cs b/Samples~/Soaker/Scripts/SoakClientJobManager.cs index 2b2b348..2ad1ec1 100644 --- a/Samples~/Soaker/Scripts/SoakClientJobManager.cs +++ b/Samples~/Soaker/Scripts/SoakClientJobManager.cs @@ -42,7 +42,7 @@ public void Start() { Debug.Log("Soak test initiated"); - var endpoint = NetworkEndPoint.LoopbackIpv4; + var endpoint = NetworkEndpoint.LoopbackIpv4; endpoint.Port = 9000; if (!m_Started) { diff --git a/Samples~/Soaker/Scripts/SoakClientJobs.cs b/Samples~/Soaker/Scripts/SoakClientJobs.cs index d0e9b92..c706c15 100644 --- a/Samples~/Soaker/Scripts/SoakClientJobs.cs +++ b/Samples~/Soaker/Scripts/SoakClientJobs.cs @@ -1,5 +1,4 @@ using Unity.Burst; -using Unity.Networking.Transport; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; @@ -38,8 +37,8 @@ public unsafe void SendPacket(ref SoakStatisticsPoint stats, ref SoakJobContext }; if (driver.BeginSend(pipeline, connection[0], out var streamWriter) == 0) { - streamWriter.WriteBytes(message.data, SoakMessage.HeaderLength); - streamWriter.WriteBytes((byte*)packetData.GetUnsafeReadOnlyPtr(), packetData.Length); + streamWriter.WriteBytesUnsafe(message.data, SoakMessage.HeaderLength); + streamWriter.WriteBytesUnsafe((byte*)packetData.GetUnsafeReadOnlyPtr(), packetData.Length); stats.SentBytes += driver.EndSend(streamWriter); } @@ -73,7 +72,8 @@ public unsafe void Execute() else if (cmd == NetworkEvent.Type.Data) { stats.ReceivedBytes += strm.Length; - strm.ReadBytes(inbound.data, strm.Length); + + strm.ReadBytesUnsafe(inbound.data, strm.Length); if (inbound.sequence > ctx.LatestReceivedSequenceNumber) { diff --git a/Samples~/Soaker/Scripts/SoakServer.cs b/Samples~/Soaker/Scripts/SoakServer.cs index dcba5b4..c865516 100644 --- a/Samples~/Soaker/Scripts/SoakServer.cs +++ b/Samples~/Soaker/Scripts/SoakServer.cs @@ -26,8 +26,8 @@ public void Start() m_ServerDriver = NetworkDriver.Create(); //m_Pipeline = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); m_Pipeline = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - m_ReliableStageId = NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)); - var addr = NetworkEndPoint.AnyIpv4; + m_ReliableStageId = NetworkPipelineStageId.Get(); + var addr = NetworkEndpoint.AnyIpv4; addr.Port = 9000; if (m_ServerDriver.Bind(addr) != 0) Debug.Log("Failed to bind to port 9000"); diff --git a/Samples~/Soaker/Scripts/SoakServerJobs.cs b/Samples~/Soaker/Scripts/SoakServerJobs.cs index f8235d8..e56b4e5 100644 --- a/Samples~/Soaker/Scripts/SoakServerJobs.cs +++ b/Samples~/Soaker/Scripts/SoakServerJobs.cs @@ -3,6 +3,7 @@ using Unity.Collections; using Unity.Jobs; using UnityEngine.Assertions; +using Unity.Collections.LowLevel.Unsafe; namespace Unity.Networking.Transport.Samples { @@ -62,7 +63,7 @@ public void Execute(int i) { unsafe { - strm.ReadBytes(inbound.data, strm.Length); + strm.ReadBytesUnsafe(inbound.data, strm.Length); Assert.AreEqual(strm.Length, inbound.length + SoakMessage.HeaderLength); outbound.id = inbound.id; @@ -70,7 +71,7 @@ public void Execute(int i) if (driver.BeginSend(pipeline, connections[i].Connection, out var soakData) == 0) { - soakData.WriteBytes(outbound.data, SoakMessage.HeaderLength); + soakData.WriteBytesUnsafe(outbound.data, SoakMessage.HeaderLength); driver.EndSend(soakData); } } diff --git a/Samples~/Soaker/Scripts/SoakStatisticsReporter.cs b/Samples~/Soaker/Scripts/SoakStatisticsReporter.cs index 1a5d57c..89d5972 100644 --- a/Samples~/Soaker/Scripts/SoakStatisticsReporter.cs +++ b/Samples~/Soaker/Scripts/SoakStatisticsReporter.cs @@ -9,14 +9,14 @@ namespace Unity.Networking.Transport.Samples public class SoakStatisticsReporter { private string header = - @" +@" +@" diff --git a/Samples~/Soaker/Scripts/Soaker.asmdef b/Samples~/Soaker/Scripts/Soaker.asmdef index cf66e42..e57d563 100644 --- a/Samples~/Soaker/Scripts/Soaker.asmdef +++ b/Samples~/Soaker/Scripts/Soaker.asmdef @@ -13,15 +13,7 @@ "overrideReferences": false, "precompiledReferences": [], "autoReferenced": false, - "defineConstraints": [ - "ENABLE_JOBS" - ], - "versionDefines": [ - { - "name": "com.unity.jobs", - "expression": "0.0", - "define": "ENABLE_JOBS" - } - ], + "defineConstraints": [], + "versionDefines": [], "noEngineReferences": false -} +} \ No newline at end of file diff --git a/Tests.meta b/Tests.meta deleted file mode 100644 index 750266c..0000000 --- a/Tests.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2ec122f21a0281042850d4e10c829506 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor.meta b/Tests/Editor.meta deleted file mode 100644 index e4da256..0000000 --- a/Tests/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e77bee39967e3f24fb25f974b39b44e2 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/Base64Tests.cs b/Tests/Editor/Base64Tests.cs deleted file mode 100644 index 30c0bc0..0000000 --- a/Tests/Editor/Base64Tests.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using NUnit.Framework; -using Unity.Collections; - -namespace Unity.Networking.Transport.Tests -{ - public class Base64Tests - { - static string FromBase64String(string base64) - { - unsafe - { - var maxLength = base64.Length / 4 * 3 + 2; - var buffer = new byte[maxLength]; - - fixed(byte* ptr = buffer) - { - var actualLength = Base64.FromBase64String(base64, ptr, maxLength); - return new string((sbyte*)ptr, 0, actualLength); - } - } - } - - static void Check(string normal, string base64) - { - var decoded = FromBase64String(base64); - Assert.AreEqual(normal, decoded); - } - - [Test] - public void TestVector() - { - Check("", ""); - Check("f", "Zg=="); - Check("fo", "Zm8="); - Check("foo", "Zm9v"); - Check("foob", "Zm9vYg=="); - Check("fooba", "Zm9vYmE="); - Check("foobar", "Zm9vYmFy"); - } - -#if !NET_DOTS - private byte[] GenerateBinarySequence(Random rnd, int size) - { - var res = new byte[size]; - for (var i = 0; i < size; i++) - { - res[i] = (byte)rnd.Next(255); - } - return res; - } - - [Test] - public void TestRandomVector() - { - var rnd = new Random(513234124); - const int n = 4096; - var buffer = new byte[n]; - - for (int i = 1; i < n; i++) - { - unsafe - { - var seq = GenerateBinarySequence(rnd, i); - var base64String = Convert.ToBase64String(seq); - - var correctBytes = Convert.FromBase64String(base64String); - UnityEngine.Assertions.Assert.AreEqual(i, correctBytes.Length); - - for (int j = 0; j < i; j++) - { - UnityEngine.Assertions.Assert.AreEqual(seq[j], correctBytes[j]); - } - - fixed(byte* ptr = buffer) - { - var actualLength = Base64.FromBase64String(base64String, ptr, i); - - if (i != actualLength) - { - actualLength = Base64.FromBase64String(base64String, ptr, i); - } - - UnityEngine.Assertions.Assert.AreEqual(i, actualLength); - - for (int j = 0; j < i; j++) - { - UnityEngine.Assertions.Assert.AreEqual(seq[j], ptr[j]); - } - } - } - } - } - -#endif - } -} diff --git a/Tests/Editor/Base64Tests.cs.meta b/Tests/Editor/Base64Tests.cs.meta deleted file mode 100644 index 7cbfbe6..0000000 --- a/Tests/Editor/Base64Tests.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9e75ce315f1942e0a5f995af39aee448 -timeCreated: 1623792675 \ No newline at end of file diff --git a/Tests/Editor/BeginEndSendTests.cs b/Tests/Editor/BeginEndSendTests.cs deleted file mode 100644 index 0e402c9..0000000 --- a/Tests/Editor/BeginEndSendTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using Unity.Collections; -using NUnit.Framework; -using Unity.Networking.Transport.Protocols; -using UnityEngine; -using UnityEngine.TestTools; -using Unity.Jobs; - -namespace Unity.Networking.Transport.Tests -{ - public class BeginEndSendTests - { - private NetworkDriver Driver; - private NetworkDriver RemoteDriver; - private NetworkConnection ToRemoteConnection; - private NetworkConnection ToLocalConnection; - - [SetUp] - public void IPC_Setup() - { - Driver = new NetworkDriver(new IPCNetworkInterface()); - RemoteDriver = new NetworkDriver(new IPCNetworkInterface()); - - RemoteDriver.Bind(NetworkEndPoint.LoopbackIpv4); - RemoteDriver.Listen(); - ToRemoteConnection = Driver.Connect(RemoteDriver.LocalEndPoint()); - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - ToLocalConnection = RemoteDriver.Accept(); - Assert.AreNotEqual(default, ToLocalConnection); - Driver.ScheduleUpdate().Complete(); - var evt = Driver.PopEventForConnection(ToRemoteConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Connect, evt); - } - - [TearDown] - public void IPC_TearDown() - { - Driver.Dispose(); - RemoteDriver.Dispose(); - } - - [Test] - public void GivenBadNetworkId_ReturnsNetworkIdMismatch() - { - var badCon = default(NetworkConnection); - badCon.m_NetworkId = -1; - var writer = default(DataStreamWriter); - - Assert.IsTrue(Driver.BeginSend(badCon, out writer) == (int)Error.StatusCode.NetworkIdMismatch); - } - - [Test] - public void GivenBadNetworkVersion_ReturnsNetworkVersionMismatch() - { - var badCon = ToRemoteConnection; - badCon.m_NetworkVersion--; - - var writer = default(DataStreamWriter); - Assert.IsTrue(Driver.BeginSend(badCon, out writer) == (int)Error.StatusCode.NetworkVersionMismatch); - } - - [Test] - public void GivenToLargePayloadSize_ReturnsNetworkPacketOverflow() - { - var writer = default(DataStreamWriter); - Assert.IsTrue(Driver.BeginSend(ToRemoteConnection, out writer, 1501) == (int)Error.StatusCode.NetworkPacketOverflow); - } - - [Test] - public void BeginEndSimple() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - Assert.AreEqual(NetworkParameterConstants.MTU - UdpCHeader.Length, writer.Capacity); - writer.WriteInt(42); - Driver.EndSend(writer); - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(42, reader.ReadInt()); - } - - [Test] - public void NestedBeginEndSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - Assert.AreEqual(NetworkParameterConstants.MTU - UdpCHeader.Length, writer.Capacity); - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer2)); - writer2.WriteInt(4242); - Driver.EndSend(writer2); - Driver.EndSend(writer); - - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(4242, reader.ReadInt()); - evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(42, reader.ReadInt()); - } - - [Test] - public void OverlappingBeginEndSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer2)); - writer.WriteInt(42); - writer2.WriteInt(4242); - - Driver.EndSend(writer); - Driver.EndSend(writer2); - - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(42, reader.ReadInt()); - evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(4242, reader.ReadInt()); - } - - [Test] - public void MissingEndSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - LogAssert.Expect(LogType.Error, "Missing EndSend, calling BeginSend without calling EndSend will result in a memory leak"); - Driver.ScheduleUpdate().Complete(); - } - - [Test] - public void DuplicateEndSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - Driver.EndSend(writer); - Assert.Throws(() => {Driver.EndSend(writer);}); - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(42, reader.ReadInt()); - } - - [Test] - public void DuplicateAbortSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - Driver.AbortSend(writer); - Assert.Throws(() => {Driver.AbortSend(writer);}); - - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Empty, evt); - } - - [Test] - public void AbortBeforeEndSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - Driver.AbortSend(writer); - Assert.Throws(() => {Driver.EndSend(writer);}); - - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Empty, evt); - } - - [Test] - public void EndBeforeAbortSend() - { - Assert.AreEqual(0, Driver.BeginSend(ToRemoteConnection, out var writer)); - writer.WriteInt(42); - Driver.EndSend(writer); - Assert.Throws(() => {Driver.AbortSend(writer);}); - - Driver.ScheduleFlushSend(default).Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var evt = RemoteDriver.PopEventForConnection(ToLocalConnection, out var reader); - Assert.AreEqual(NetworkEvent.Type.Data, evt); - Assert.AreEqual(4, reader.Length); - Assert.AreEqual(42, reader.ReadInt()); - } - - struct BeginSendJob : IJob - { - public static DataStreamWriter writer = default; - public NetworkDriver Driver; - public NetworkConnection ToRemoteConnection; - public void Execute() - { - if (Driver.BeginSend(ToRemoteConnection, out var writer) == 0) - { - writer.WriteInt(42); - } - } - } - [Test] - public void EndSendAfterDispose() - { - var beginJob = new BeginSendJob - { - Driver = Driver, - ToRemoteConnection = ToRemoteConnection - }; - beginJob.Schedule().Complete(); - Assert.Catch(() => {Driver.EndSend(BeginSendJob.writer);}); - - LogAssert.Expect(LogType.Error, "Missing EndSend, calling BeginSend without calling EndSend will result in a memory leak"); - Driver.ScheduleUpdate().Complete(); - } - - [Test] - public void EndSendWithoutBeginSend() - { - var writer = new DataStreamWriter(16, Allocator.Temp); - Assert.Throws(() => {Driver.EndSend(writer);}); - } - - [Test] - public void EndSendWithFailedWriter() - { - Driver.BeginSend(ToRemoteConnection, out var writer); - var buffer = new NativeArray(NetworkParameterConstants.MTU + 1, Allocator.Temp); - writer.WriteBytes(buffer); - Assert.IsTrue(writer.HasFailedWrites); - Assert.AreEqual((int)Error.StatusCode.NetworkPacketOverflow, Driver.EndSend(writer)); - } - - [Test] - public void BeginSendOnClosedConnection() - { - Driver.Disconnect(ToRemoteConnection); - Assert.AreEqual((int)Error.StatusCode.NetworkStateMismatch, Driver.BeginSend(ToRemoteConnection, out _)); - } - } - - public class BeginEndExtras - { - [Test] - public void GivenStateConnecting_ReturnsError() - { - using (var Driver = new NetworkDriver(new IPCNetworkInterface())) - using (var RemoteDriver = new NetworkDriver(new IPCNetworkInterface())) - { - RemoteDriver.Bind(NetworkEndPoint.LoopbackIpv4); - RemoteDriver.Listen(); - var ToRemoteConnection = Driver.Connect(RemoteDriver.LocalEndPoint()); - - Assert.AreEqual((int)Error.StatusCode.NetworkStateMismatch, Driver.BeginSend(ToRemoteConnection, out var writer)); - } - } - } -} diff --git a/Tests/Editor/BeginEndSendTests.cs.meta b/Tests/Editor/BeginEndSendTests.cs.meta deleted file mode 100644 index 0ee2da7..0000000 --- a/Tests/Editor/BeginEndSendTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d6d15d28914b448bea75363d260e43c3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/DataStreamTests.cs b/Tests/Editor/DataStreamTests.cs deleted file mode 100644 index 4e877db..0000000 --- a/Tests/Editor/DataStreamTests.cs +++ /dev/null @@ -1,497 +0,0 @@ -using System; -using NUnit.Framework; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using UnityEngine.TestTools; -using UnityEngine; - -// using FsCheck; - -namespace Unity.Networking.Transport.Tests -{ - public class DataStreamTests - { - [Test] - public void CreateStreamWithPartOfSourceByteArray() - { - byte[] byteArray = - { - (byte)'s', (byte)'o', (byte)'m', (byte)'e', - (byte)' ', (byte)'d', (byte)'a', (byte)'t', (byte)'a' - }; - - DataStreamWriter dataStream; - dataStream = new DataStreamWriter(4, Allocator.Temp); - dataStream.WriteBytes(new NativeArray(byteArray, Allocator.Temp).GetSubArray(0, 4)); - Assert.AreEqual(dataStream.Length, 4); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (int i = 0; i < dataStream.Length; ++i) - { - Assert.AreEqual(byteArray[i], reader.ReadByte()); - } - - LogAssert.Expect(LogType.Error, "Trying to read 1 bytes from a stream where only 0 are available"); - Assert.AreEqual(0, reader.ReadByte()); - } - - [Test] - public void CreateStreamWithSourceByteArray() - { - byte[] byteArray = new byte[100]; - byteArray[0] = (byte)'a'; - byteArray[1] = (byte)'b'; - byteArray[2] = (byte)'c'; - - DataStreamWriter dataStream; - dataStream = new DataStreamWriter(byteArray.Length, Allocator.Temp); - dataStream.WriteBytes(new NativeArray(byteArray, Allocator.Temp)); - - var arr = dataStream.AsNativeArray(); - var reader = new DataStreamReader(arr); - for (var i = 0; i < byteArray.Length; ++i) - { - Assert.AreEqual(byteArray[i], reader.ReadByte()); - } - - unsafe - { - var reader2 = new DataStreamReader((byte*)arr.GetUnsafePtr(), arr.Length); - for (var i = 0; i < byteArray.Length; ++i) - { - Assert.AreEqual(byteArray[i], reader2.ReadByte()); - } - } - } - - [Test] - public void ReadIntoExistingByteArray() - { - var byteArray = new NativeArray(100, Allocator.Temp); - - DataStreamWriter dataStream; - dataStream = new DataStreamWriter(3, Allocator.Temp); - { - dataStream.WriteByte((byte)'a'); - dataStream.WriteByte((byte)'b'); - dataStream.WriteByte((byte)'c'); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - reader.ReadBytes(byteArray.GetSubArray(0, dataStream.Length)); - reader = new DataStreamReader(dataStream.AsNativeArray()); - for (int i = 0; i < reader.Length; ++i) - { - Assert.AreEqual(byteArray[i], reader.ReadByte()); - } - } - } - - [Test] - public void ReadingDataFromStreamWithSliceOffset() - { - var dataStream = new DataStreamWriter(100, Allocator.Temp); - dataStream.WriteByte((byte)'a'); - dataStream.WriteByte((byte)'b'); - dataStream.WriteByte((byte)'c'); - dataStream.WriteByte((byte)'d'); - dataStream.WriteByte((byte)'e'); - dataStream.WriteByte((byte)'f'); - var reader = new DataStreamReader(dataStream.AsNativeArray().GetSubArray(3, 3)); - Assert.AreEqual('d', reader.ReadByte()); - Assert.AreEqual('e', reader.ReadByte()); - Assert.AreEqual('f', reader.ReadByte()); - } - - [Test] - public void ReadWritePackedUInt() - { - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - uint base_val = 2000; - uint count = 277; - for (uint i = 0; i < count; ++i) - dataStream.WritePackedUInt(base_val + i, compressionModel); - - dataStream.WriteInt((int)1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (uint i = 0; i < count; ++i) - { - var val = reader.ReadPackedUInt(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(1979, reader.ReadInt()); - } - } - - [Test] - public void ReadWritePackedIntExistingData() - { - unsafe - { - var n = 300 * 4; - var data = stackalloc byte[n]; - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(data, n); - int base_val = -10; - int count = 20; - for (int i = 0; i < count; ++i) - dataStream.WritePackedInt(base_val + i, compressionModel); - - dataStream.WriteInt((int)1979); - dataStream.Flush(); - var reader = new DataStreamReader(data, n); - for (int i = 0; i < count; ++i) - { - var val = reader.ReadPackedInt(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(1979, reader.ReadInt()); - } - } - } - - [Test] - public void ReadWritePackedInt() - { - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - int base_val = -10; - int count = 20; - for (int i = 0; i < count; ++i) - dataStream.WritePackedInt(base_val + i, compressionModel); - - dataStream.WriteInt((int)1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (int i = 0; i < count; ++i) - { - var val = reader.ReadPackedInt(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(1979, reader.ReadInt()); - } - } - - [Test] - public void ReadWritePackedUIntWithDeferred() - { - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - uint base_val = 2000; - uint count = 277; - var def = dataStream; - dataStream.WriteInt((int)0); - for (uint i = 0; i < count; ++i) - dataStream.WritePackedUInt(base_val + i, compressionModel); - - dataStream.Flush(); - def.WriteInt(1979); - def = dataStream; - dataStream.WriteInt((int)0); - def.WriteInt(1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - Assert.AreEqual(1979, reader.ReadInt()); - for (uint i = 0; i < count; ++i) - { - var val = reader.ReadPackedUInt(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(1979, reader.ReadInt()); - } - } - - [Test] - public void WriteOutOfBounds() - { - var dataStream = new DataStreamWriter(9, Allocator.Temp); - Assert.IsTrue(dataStream.WriteInt(42)); - Assert.AreEqual(4, dataStream.Length); - Assert.IsTrue(dataStream.WriteInt(42)); - Assert.AreEqual(8, dataStream.Length); - Assert.IsFalse(dataStream.HasFailedWrites); - Assert.IsFalse(dataStream.WriteInt(42)); - Assert.AreEqual(8, dataStream.Length); - Assert.IsTrue(dataStream.HasFailedWrites); - - Assert.IsFalse(dataStream.WriteShort(42)); - Assert.AreEqual(8, dataStream.Length); - Assert.IsTrue(dataStream.HasFailedWrites); - - Assert.IsTrue(dataStream.WriteByte(42)); - Assert.AreEqual(9, dataStream.Length); - Assert.IsTrue(dataStream.HasFailedWrites); - - Assert.IsFalse(dataStream.WriteByte(42)); - Assert.AreEqual(9, dataStream.Length); - Assert.IsTrue(dataStream.HasFailedWrites); - } - - [Test] - public void ReadWriteFixedString32() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - - var src = new FixedString32Bytes("This is a string"); - dataStream.WriteFixedString32(src); - - //Assert.AreEqual(src.LengthInBytes+2, dataStream.Length); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadFixedString32(); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWritePackedFixedString32Delta() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - var compressionModel = new NetworkCompressionModel(Allocator.Temp); - - var src = new FixedString32Bytes("This is a string"); - var baseline = new FixedString32Bytes("This is another string"); - dataStream.WritePackedFixedString32Delta(src, baseline, compressionModel); - dataStream.Flush(); - - //Assert.LessOrEqual(dataStream.Length, src.LengthInBytes+2); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadPackedFixedString32Delta(baseline, compressionModel); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWriteFixedString64() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - - var src = new FixedString64Bytes("This is a string"); - dataStream.WriteFixedString64(src); - - //Assert.AreEqual(src.LengthInBytes+2, dataStream.Length); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadFixedString64(); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWritePackedFixedString64Delta() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - var compressionModel = new NetworkCompressionModel(Allocator.Temp); - - var src = new FixedString64Bytes("This is a string"); - var baseline = new FixedString64Bytes("This is another string"); - dataStream.WritePackedFixedString64Delta(src, baseline, compressionModel); - dataStream.Flush(); - - //Assert.LessOrEqual(dataStream.Length, src.LengthInBytes+2); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadPackedFixedString64Delta(baseline, compressionModel); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWriteFixedString128() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - - var src = new FixedString128Bytes("This is a string"); - dataStream.WriteFixedString128(src); - - //Assert.AreEqual(src.LengthInBytes+2, dataStream.Length); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadFixedString128(); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWritePackedFixedString128Delta() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - var compressionModel = new NetworkCompressionModel(Allocator.Temp); - - var src = new FixedString128Bytes("This is a string"); - var baseline = new FixedString128Bytes("This is another string"); - dataStream.WritePackedFixedString128Delta(src, baseline, compressionModel); - dataStream.Flush(); - - //Assert.LessOrEqual(dataStream.Length, src.LengthInBytes+2); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadPackedFixedString128Delta(baseline, compressionModel); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWriteFixedString512() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - - var src = new FixedString512Bytes("This is a string"); - dataStream.WriteFixedString512(src); - - //Assert.AreEqual(src.LengthInBytes+2, dataStream.Length); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadFixedString512(); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWritePackedFixedString512Delta() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - var compressionModel = new NetworkCompressionModel(Allocator.Temp); - - var src = new FixedString512Bytes("This is a string"); - var baseline = new FixedString512Bytes("This is another string"); - dataStream.WritePackedFixedString512Delta(src, baseline, compressionModel); - dataStream.Flush(); - - //Assert.LessOrEqual(dataStream.Length, src.LengthInBytes+2); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadPackedFixedString512Delta(baseline, compressionModel); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWriteFixedString4096() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - - var src = new FixedString4096Bytes("This is a string"); - dataStream.WriteFixedString4096(src); - - //Assert.AreEqual(src.LengthInBytes+2, dataStream.Length); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadFixedString4096(); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWritePackedFixedString4096Delta() - { - var dataStream = new DataStreamWriter(300 * 4, Allocator.Temp); - var compressionModel = new NetworkCompressionModel(Allocator.Temp); - - var src = new FixedString4096Bytes("This is a string"); - var baseline = new FixedString4096Bytes("This is another string"); - dataStream.WritePackedFixedString4096Delta(src, baseline, compressionModel); - dataStream.Flush(); - - //Assert.LessOrEqual(dataStream.Length, src.LengthInBytes+2); - - var reader = new DataStreamReader(dataStream.AsNativeArray()); - var dst = reader.ReadPackedFixedString4096Delta(baseline, compressionModel); - Assert.AreEqual(src, dst); - } - - [Test] - public void ReadWriteLong() - { - var dataStream = new DataStreamWriter(300 * 8, Allocator.Temp); - long base_val = -99; - long count = 277; - for (uint i = 0; i < count; ++i) - dataStream.WriteLong(base_val + i); - - dataStream.WriteLong(-1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (uint i = 0; i < count; ++i) - { - var val = reader.ReadLong(); - Assert.AreEqual(base_val + i, val); - } - - Assert.AreEqual(-1979, reader.ReadLong()); - } - - [Test] - public void ReadWritePackedLong() - { - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(300 * 8, Allocator.Temp); - long base_val = -99; - long count = 277; - for (uint i = 0; i < count; ++i) - dataStream.WritePackedLong(base_val + i, compressionModel); - - dataStream.WriteLong(-1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (uint i = 0; i < count; ++i) - { - var val = reader.ReadPackedLong(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(-1979, reader.ReadLong()); - } - } - - [Test] - public void ReadWritePackedULong() - { - using (var compressionModel = new NetworkCompressionModel(Allocator.Persistent)) - { - var dataStream = new DataStreamWriter(300 * 8, Allocator.Temp); - ulong base_val = 2000; - ulong count = 277; - for (uint i = 0; i < count; ++i) - dataStream.WritePackedULong(base_val + i, compressionModel); - - dataStream.WriteULong(1979); - dataStream.Flush(); - var reader = new DataStreamReader(dataStream.AsNativeArray()); - for (uint i = 0; i < count; ++i) - { - var val = reader.ReadPackedULong(compressionModel); - Assert.AreEqual(base_val + i, val); - } - Assert.AreEqual(1979, reader.ReadULong()); - } - } - - private struct ReaderTestJob : IJob - { - public DataStreamReader Reader; - public NativeArray ReturnValue; - - public void Execute() - { - ReturnValue[0] = Reader.ReadInt(); - } - } - - [Test] - public void ReaderAsJobParameter() - { - using (var returnValue = new NativeArray(1, Allocator.TempJob)) - { - var writer = new DataStreamWriter(sizeof(int), Allocator.Temp); - writer.WriteInt(42); - - var reader = new DataStreamReader(writer.AsNativeArray()); - - new ReaderTestJob - { - Reader = reader, - ReturnValue = returnValue - }.Run(); - - Assert.AreEqual(42, returnValue[0]); - } - } - } -} diff --git a/Tests/Editor/DataStreamTests.cs.meta b/Tests/Editor/DataStreamTests.cs.meta deleted file mode 100644 index 44493a0..0000000 --- a/Tests/Editor/DataStreamTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2a2377a965b74cf43b04ec42c9c776b4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/FragmentationPipelineTests.cs b/Tests/Editor/FragmentationPipelineTests.cs deleted file mode 100644 index 813f13a..0000000 --- a/Tests/Editor/FragmentationPipelineTests.cs +++ /dev/null @@ -1,383 +0,0 @@ -using System; -using System.Threading; -using AOT; -using NUnit.Framework; -using Unity.Networking.Transport.Utilities; -using Unity.Burst; - -namespace Unity.Networking.Transport.Tests -{ - [BurstCompile] - public unsafe struct TempDropPacketPipelineStage : INetworkPipelineStage - { - public static byte* s_StaticInstanceBuffer; - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - s_StaticInstanceBuffer = staticInstanceBuffer; - *staticInstanceBuffer = 0; - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: 0, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 2; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - byte idx = ctx.staticInstanceBuffer[1]; - if (ctx.staticInstanceBuffer[0] == idx) - { - // Drop the packet - inboundBuffer = default; - } - *ctx.staticInstanceBuffer += 1; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - public struct TempDropPacketPipelineStageCollection - { - public static void Register() - { - NetworkPipelineStageCollection.RegisterPipelineStage(new TempDropPacketPipelineStage()); - } - } - - public class FragmentationPipelineTests - { - private NetworkDriver m_ServerDriver; - private NetworkDriver m_ClientDriver; - - [SetUp] - public void IPC_Setup() - { - TempDropPacketPipelineStageCollection.Register(); - - var serverSettings = new NetworkSettings(); - serverSettings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16) - .WithFragmentationStageParameters(payloadCapacity: 4 * 1024); - m_ServerDriver = new NetworkDriver(new IPCNetworkInterface(), serverSettings); - m_ServerDriver.Bind(NetworkEndPoint.LoopbackIpv4); - m_ServerDriver.Listen(); - - var clientSettings = new NetworkSettings(); - clientSettings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16) - .WithFragmentationStageParameters(payloadCapacity: 4 * 1024) - .WithSimulatorStageParameters(maxPacketCount: 30, maxPacketSize: 16, packetDelayMs: 0, packetDropPercentage: 10); - m_ClientDriver = new NetworkDriver(new IPCNetworkInterface(), clientSettings); - } - - [TearDown] - public void IPC_TearDown() - { - m_ClientDriver.Dispose(); - m_ServerDriver.Dispose(); - } - - [Test] - public void NetworkPipeline_Fragmentation_SendRecvOnce() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt(42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(sizeof(int), readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_Fragmentation_SendRecvOversized() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - int messageSize = 3000; - int intCount = messageSize / sizeof(int); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm, messageSize) == 0) - { - for (int i = 0; i < intCount; ++i) - { - strm.WriteInt(i); - } - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - Assert.AreEqual(messageSize, readStrm.Length); - for (int i = 0; i < intCount; ++i) - { - Assert.AreEqual(i, readStrm.ReadInt()); - } - } - - [Test] - public void NetworkPipeline_Fragmentation_SendRecvMaxSize() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - int messageSize = 4 * 1024 - m_ServerDriver.MaxHeaderSize(serverPipe); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm, messageSize) == 0) - { - for (int i = 0; i < messageSize; ++i) - { - strm.WriteByte((byte)i); - } - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - Assert.AreEqual(messageSize, readStrm.Length); - for (int i = 0; i < messageSize; ++i) - { - Assert.AreEqual((byte)i, readStrm.ReadByte()); - } - } - - [Test] - public unsafe void NetworkPipeline_Fragmentation_DroppedPacket() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(FragmentationPipelineStage), - typeof(TempDropPacketPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(FragmentationPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - int messageSize = 3000; - int intCount = messageSize / sizeof(int); - int messageCount = 3; - - int packetCount = -1; - for (int dropIndex = 0; dropIndex != packetCount; ++dropIndex) - { - TempDropPacketPipelineStage.s_StaticInstanceBuffer[0] = 0; // Reset packet counter - TempDropPacketPipelineStage.s_StaticInstanceBuffer[1] = (byte)dropIndex; - - for (int j = 0; j < messageCount; ++j) - { - // Send one message - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm, messageSize) == 0) - { - for (int i = 0; i < intCount; ++i) - { - strm.WriteInt(i); - } - - m_ServerDriver.EndSend(strm); - } - } - - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - - packetCount = TempDropPacketPipelineStage.s_StaticInstanceBuffer[0]; - - { - // We have dropped one fragment. The result should be that one complete fragmented message - // is discarded, and the remaining messageCount - 1 are intact. - DataStreamReader readStrm; - NetworkEvent.Type eventType; - if (dropIndex == 0) // First pass only - { - eventType = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual(NetworkEvent.Type.Connect, eventType); - } - - for (int j = 0; j < messageCount - 1; ++j) - { - eventType = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual(NetworkEvent.Type.Data, eventType); - Assert.AreEqual(messageSize, readStrm.Length); - for (int i = 0; i < intCount; ++i) - { - Assert.AreEqual(i, readStrm.ReadInt()); - } - } - - Assert.AreEqual(NetworkEvent.Type.Empty, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - } - } - } - - [Test] - public void NetworkPipeline_Fragmentation_Unreliable_SendRecv1380_Plus() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(FragmentationPipelineStage), typeof(UnreliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(FragmentationPipelineStage), typeof(UnreliableSequencedPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - for (int messageSize = 1380; messageSize <= 1400; ++messageSize) - { - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - for (int i = 0; i < messageSize; ++i) - { - strm.WriteByte((byte)i); - } - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - Assert.AreEqual(messageSize, readStrm.Length); - for (int i = 0; i < messageSize; ++i) - { - Assert.AreEqual((byte)i, readStrm.ReadByte()); - } - } - } - - [Test] - public void NetworkPipeline_Unreliable_Fragmentation_SendRecv1380_Plus() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(FragmentationPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(FragmentationPipelineStage)); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - for (int messageSize = 1380; messageSize <= 1400; ++messageSize) - { - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - for (int i = 0; i < messageSize; ++i) - { - strm.WriteByte((byte)i); - } - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - Assert.AreEqual(messageSize, readStrm.Length); - for (int i = 0; i < messageSize; ++i) - { - Assert.AreEqual((byte)i, readStrm.ReadByte()); - } - } - } - } -} diff --git a/Tests/Editor/FragmentationPipelineTests.cs.meta b/Tests/Editor/FragmentationPipelineTests.cs.meta deleted file mode 100644 index 2277589..0000000 --- a/Tests/Editor/FragmentationPipelineTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 112c68686df4745aab3f6097caba1e2c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/HMACSHA256Tests.cs b/Tests/Editor/HMACSHA256Tests.cs deleted file mode 100644 index 0b944e3..0000000 --- a/Tests/Editor/HMACSHA256Tests.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NUnit.Framework; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Assert = UnityEngine.Assertions.Assert; -using Random = System.Random; - -namespace Unity.Networking.Transport.Tests -{ - public class HMACSHA256Tests - { - [Test] - public void TestEmptyVectorSHA256() - { - unsafe - { - var str = (FixedString32Bytes)""; - var result = new NativeArray(32, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - Assert.AreEqual(0, str.Length); - - var sha256State = SHA256.SHA256State.Create(); - sha256State.Update(str.GetUnsafePtr(), str.Length); - sha256State.Final((byte*)result.GetUnsafePtr()); - -#if !NET_DOTS - ValidateWithReferenceImplementationSHA256(ToArray(str.ToString()), result); -#endif - - uint* r1 = (uint*)result.GetUnsafePtr(); - Assert.AreEqual(*r1++, 0x42c4b0e3); - Assert.AreEqual(*r1++, 0x141cfc98); - Assert.AreEqual(*r1++, 0xc8f4fb9a); - Assert.AreEqual(*r1++, 0x24b96f99); - Assert.AreEqual(*r1++, 0xe441ae27); - Assert.AreEqual(*r1++, 0x4c939b64); - Assert.AreEqual(*r1++, 0x1b9995a4); - Assert.AreEqual(*r1, 0x55b85278); - } - } - - [Test] - public void TestVectorSHA256() - { - unsafe - { - // from https://www.di-mgt.com.au/sha_testvectors.html - - // 896 bits - var str = (FixedString512Bytes)"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"; - var result = new NativeArray(32, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - Assert.AreEqual(896 / 8, str.Length); - - var sha256State = SHA256.SHA256State.Create(); - sha256State.Update(str.GetUnsafePtr(), str.Length); - sha256State.Final((byte*)result.GetUnsafePtr()); - -#if !NET_DOTS - ValidateWithReferenceImplementationSHA256(ToArray(str.ToString()), result); -#endif - - uint* r1 = (uint*)result.GetUnsafePtr(); - Assert.AreEqual(*r1++, 0xa7165bcf); - Assert.AreEqual(*r1++, 0x8083af78); - Assert.AreEqual(*r1++, 0x9ee56c03); - Assert.AreEqual(*r1++, 0x3792047b); - Assert.AreEqual(*r1++, 0x119b240b); - Assert.AreEqual(*r1++, 0x517af0e8); - Assert.AreEqual(*r1++, 0x0345acaf); - Assert.AreEqual(*r1, 0xd1e9fe7a); - } - } - - void ValidateHMACSHA256TestVector(byte[] key, byte[] message, ref FixedString128Bytes expectedResult) - { - var expectedFromTestVector = StringToByteArray(expectedResult.ToString()); - - var resultToValidate = new NativeArray(32, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - - unsafe - { - fixed(byte* keyPtr = key) - { - fixed(byte* messagePtr = message) - { - HMACSHA256.ComputeHash(keyPtr, key.Length, - messagePtr, message.Length, - (byte*)resultToValidate.GetUnsafePtr()); - } - } - } - - AssertAreEqualSHA(expectedFromTestVector, resultToValidate, "Result is not the same as the test vector"); - -#if !NET_DOTS - ValidateWithReferenceImplementationHMAC(key, message, resultToValidate); -#endif - } - - static byte[] StringToByteArray(string hex) - { - if (hex.Length % 2 == 1) - throw new Exception("The binary key cannot have an odd number of digits"); - - var n = hex.Length / 2; - var arr = new byte[n]; - - for (var i = 0; i < n; ++i) - arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + GetHexVal(hex[(i << 1) + 1])); - - return arr; - } - - static int GetHexVal(char hex) - { - var val = (int)hex; - //For uppercase A-F letters: - //return val - (val < 58 ? 48 : 55); - //For lowercase a-f letters: - //return val - (val < 58 ? 48 : 87); - //Or the two combined, but a bit slower: - return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_1() - { - FixedString512Bytes key = ""; - for (var i = 0; i < 20; i++) - key.Append((char)0x0b); - FixedString512Bytes message = "Hi There"; - FixedString128Bytes expectedResult = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"; - - Assert.AreEqual(20, key.Length); - - ValidateHMACSHA256TestVector(ToArray(key.ToString()), ToArray(message.ToString()), ref expectedResult); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_2() - { - FixedString512Bytes key = "Jefe"; - FixedString512Bytes message = "what do ya want for nothing?"; - FixedString128Bytes expectedResult = "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"; - - ValidateHMACSHA256TestVector(ToArray(key.ToString()), ToArray(message.ToString()), ref expectedResult); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_3() - { - var key = new NativeArray(20, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 20; i++) - key[i] = 0xaa; - - var message = new NativeArray(50, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 50; i++) - message[i] = 0xdd; - - FixedString128Bytes expectedResult = "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"; - - Assert.AreEqual(20, key.Length); - Assert.AreEqual(50, message.Length); - - ValidateHMACSHA256TestVector(key.ToArray(), message.ToArray(), ref expectedResult); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_4() - { - var k = (byte)0x01; - var key = new NativeArray(25, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 25; i++) - key[i] = k++; - - var message = new NativeArray(50, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 50; i++) - message[i] = 0xcd; - - FixedString128Bytes expectedResult = "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"; - - Assert.AreEqual(25, key.Length); - Assert.AreEqual(50, message.Length); - - ValidateHMACSHA256TestVector(key.ToArray(), message.ToArray(), ref expectedResult); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_5() - { - var key = new NativeArray(131, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 131; i++) - key[i] = 0xaa; - - FixedString512Bytes message = "Test Using Larger Than Block-Size Key - Hash Key First"; - FixedString128Bytes expectedResult = "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"; - - Assert.AreEqual(131, key.Length); - - ValidateHMACSHA256TestVector(key.ToArray(), ToArray(message.ToString()), ref expectedResult); - } - - // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 - [Test] - public void TestVectorHMACSHA256_6() - { - var key = new NativeArray(131, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < 131; i++) - key[i] = 0xaa; - - FixedString512Bytes message = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm."; - FixedString128Bytes expectedResult = "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"; - - Assert.AreEqual(131, key.Length); - - ValidateHMACSHA256TestVector(key.ToArray(), ToArray(message.ToString()), ref expectedResult); - } - - void AssertAreEqualSHA(IEnumerable expected, IEnumerable actual, string message) - { - Assert.IsTrue(expected.SequenceEqual(actual), message); - } - - static byte[] ToArray(string src) - { - return Encoding.UTF8.GetBytes(src); - } - -#if !NET_DOTS - [Test] - public void TestReferenceImplementation1() - { - GenerateAndCompareHMAC(42, 10, 100); - } - - [Test] - public void TestReferenceImplementation2() - { - GenerateAndCompareHMAC(31242, 10, 0); - } - - [Test] - public void TestReferenceImplementation3() - { - GenerateAndCompareHMAC(86, 10, 10); - } - - [Test] - public void TestReferenceImplementation4() - { - GenerateAndCompareHMAC(512, 100, 100); - } - - [Test] - public void TestReferenceImplementation5() - { - GenerateAndCompareHMAC(51241, 464, 2552); - } - - [Test] - public void SHATestReferenceImplementation1() - { - GenerateAndCompareSHA(42, 10); - } - - [Test] - public void SHATestReferenceImplementation2() - { - GenerateAndCompareSHA(242, 0); - } - - [Test] - public void SHATestReferenceImplementation3() - { - GenerateAndCompareSHA(2422, 2130); - } - - [Test] - public void TestReferenceImplementationAll() - { - var rnd = new Random(42); - - for (int i = 0; i < 128; i++) - { - GenerateAndCompareSHA(rnd.Next(), rnd.Next(4096)); - } - - for (int i = 0; i < 128; i++) - { - GenerateAndCompareHMAC(rnd.Next(), rnd.Next(4096), rnd.Next(4096 * 8)); - } - } - - private void ValidateWithReferenceImplementationHMAC(byte[] key, byte[] message, IEnumerable result) - { - using (var hmac = new System.Security.Cryptography.HMACSHA256(key)) - { - var resultCorrect = hmac.ComputeHash(message); - AssertAreEqualSHA(resultCorrect, result, "Cryptography.HMACSHA256 gives different results!"); - } - } - - private void ValidateWithReferenceImplementationSHA256(byte[] message, IEnumerable result) - { - using (var hmac = new System.Security.Cryptography.SHA256Managed()) - { - var resultCorrect = hmac.ComputeHash(message); - AssertAreEqualSHA(resultCorrect, result, "Cryptography.SHA256Managed gives different results!"); - } - } - - private unsafe void GenerateAndCompareHMAC(int seed, int keyLength, int messageLength) - { - var rnd = new Random(seed); - var key = GenerateSequence(rnd, keyLength); - var message = GenerateSequence(rnd, messageLength); - var resultToValidate = new NativeArray(32, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - HMACSHA256.ComputeHash((byte*)key.GetUnsafeReadOnlyPtr(), key.Length, - (byte*)message.GetUnsafeReadOnlyPtr(), message.Length, - (byte*)resultToValidate.GetUnsafePtr()); - - ValidateWithReferenceImplementationHMAC(key.ToArray(), message.ToArray(), resultToValidate); - } - - private unsafe void GenerateAndCompareSHA(int seed, int messageLength) - { - var rnd = new Random(seed); - var message = GenerateSequence(rnd, messageLength); - var result = new NativeArray(32, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - - var sha256State = SHA256.SHA256State.Create(); - sha256State.Update((byte*)message.GetUnsafePtr(), message.Length); - sha256State.Final((byte*)result.GetUnsafePtr()); - - ValidateWithReferenceImplementationSHA256(message.ToArray(), result); - } - - private NativeArray GenerateSequence(Random rnd, int size) - { - var res = new NativeArray(size, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < size; i++) - { - res[i] = (byte)rnd.Next(255); - } - return res; - } - -#endif - } -} diff --git a/Tests/Editor/HMACSHA256Tests.cs.meta b/Tests/Editor/HMACSHA256Tests.cs.meta deleted file mode 100644 index a6a40f5..0000000 --- a/Tests/Editor/HMACSHA256Tests.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 978ef59987e344ae9a66b2514eadc7fa -timeCreated: 1623804358 \ No newline at end of file diff --git a/Tests/Editor/NetworkAddressTests.cs b/Tests/Editor/NetworkAddressTests.cs deleted file mode 100644 index 7b1d2e3..0000000 --- a/Tests/Editor/NetworkAddressTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using NUnit.Framework; - -namespace Unity.Networking.Transport.Tests -{ - [TestFixture] - public class NetworkAddressTests - { - [Test] - public unsafe void NetworkAddress_CastingTests() - { - var endpoint = new NetworkEndPoint(); - endpoint.Port = 1; - Assert.True(1 == endpoint.Port); - - Assert.True(256 == endpoint.RawPort); - } - - [Test] - public unsafe void NetworkAddress_ParseAddress_CompareToBaselibParse() - { - // 19 == SizeOf - //Assert.True(19 == UnsafeUtility.SizeOf()); - - string[] addresses = - { - "127.0.0.1", - "192.168.1.134", - "53BF:009C:0000:0000:120A:09D5:000D:CD29", - "2001:0db8::0370:7334", - "2001:db8::123.123.123.123", - "1200:0000:AB00:1234:0000:2552:7777:1313", - "21DA:D3:0:2F3B:2AA:FF:FE28:9C5A", - "FE80:0000:0000:0000:0202:B3FF:FE1E:8329", - "53BF:009C:0000:0000:120A:09D5:000D:CD29", - "0.0.0.0", - "9.255.255.255", - "11.0.0.0", - "126.255.255.255", - "129.0.0.0", - "169.253.255.255", - "169.255.0.0", - "172.15.255.255", - "172.32.0.0", - "191.0.1.255", - "192.88.98.255", - "192.88.100.0", - "192.167.255.255", - "192.169.0.0", - "198.17.255.255", - "223.255.255.255", - "[2001:db8:0:1]:80", - "http://[2001:db8:0:1]:80", - "1200:0000:AB00:1234:O000:2552:7777:1313" - }; - - NetworkFamily[] families = - { - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv4, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6, - NetworkFamily.Ipv6 - }; - } - } -} diff --git a/Tests/Editor/NetworkAddressTests.cs.meta b/Tests/Editor/NetworkAddressTests.cs.meta deleted file mode 100644 index 9ed2173..0000000 --- a/Tests/Editor/NetworkAddressTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: da44f9825ea0440e9ab582a9902fa401 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkConnectionUnitTests.cs b/Tests/Editor/NetworkConnectionUnitTests.cs deleted file mode 100644 index 16b0be7..0000000 --- a/Tests/Editor/NetworkConnectionUnitTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; - -namespace Unity.Networking.Transport.Tests -{ - public class NetworkConnectionUnitTests - { - public static class SharedConstants - { - public static byte[] ping = - { - (byte)'p', - (byte)'i', - (byte)'n', - (byte)'g' - }; - - public static byte[] pong = - { - (byte)'p', - (byte)'o', - (byte)'n', - (byte)'g' - }; - } - - private NetworkDriver Driver; - private NetworkDriver RemoteDriver; - - [SetUp] - public void IPC_Setup() - { - Driver = new NetworkDriver(new IPCNetworkInterface()); - RemoteDriver = new NetworkDriver(new IPCNetworkInterface()); - - RemoteDriver.Bind(NetworkEndPoint.LoopbackIpv4); - RemoteDriver.Listen(); - } - - [TearDown] - public void IPC_TearDown() - { - Driver.Dispose(); - RemoteDriver.Dispose(); - } - - [Test] - public void CreateAndConnect_NetworkConnection_ToRemoteEndPoint() - { - var connection = Driver.Connect(RemoteDriver.LocalEndPoint()); - Assert.That(connection.IsCreated); - Driver.ScheduleUpdate().Complete(); - - RemoteDriver.ScheduleUpdate().Complete(); - Assert.That(RemoteDriver.Accept().IsCreated); - - Driver.ScheduleUpdate().Complete(); - DataStreamReader reader; - Assert.That(connection.PopEvent(Driver, out reader) == NetworkEvent.Type.Connect); - } - - [Test] - public void CreateConnectPopAndClose_NetworkConnection_ToRemoteEndPoint() - { - var connection = Driver.Connect(RemoteDriver.LocalEndPoint()); - Assert.That(connection.IsCreated); - Driver.ScheduleUpdate().Complete(); - - RemoteDriver.ScheduleUpdate().Complete(); - var remoteId = default(NetworkConnection); - Assert.That((remoteId = RemoteDriver.Accept()) != default(NetworkConnection)); - - DataStreamReader reader; - - Driver.ScheduleUpdate().Complete(); - Assert.That(connection.PopEvent(Driver, out reader) == NetworkEvent.Type.Connect); - - connection.Close(Driver); - Driver.ScheduleUpdate().Complete(); - - RemoteDriver.ScheduleUpdate().Complete(); - Assert.That( - RemoteDriver.PopEventForConnection(remoteId, out reader) == NetworkEvent.Type.Disconnect); - } - - [Test] - public void Connection_SetupSendAndReceive() - { - var connection = Driver.Connect(RemoteDriver.LocalEndPoint()); - Assert.That(connection.IsCreated); - Driver.ScheduleUpdate().Complete(); - - RemoteDriver.ScheduleUpdate().Complete(); - var remoteId = default(NetworkConnection); - Assert.That((remoteId = RemoteDriver.Accept()) != default(NetworkConnection)); - - DataStreamReader reader; - - Driver.ScheduleUpdate().Complete(); - Assert.That(connection.PopEvent(Driver, out reader) == NetworkEvent.Type.Connect); - - if (Driver.BeginSend(NetworkPipeline.Null, connection, out var writer) == 0) - { - writer.WriteBytes(new NativeArray(SharedConstants.ping, Allocator.Temp)); - Driver.EndSend(writer); - } - Driver.ScheduleUpdate().Complete(); - - RemoteDriver.ScheduleUpdate().Complete(); - var ev = RemoteDriver.PopEventForConnection(remoteId, out reader); - Assert.That(ev == NetworkEvent.Type.Data); - Assert.That(reader.Length == SharedConstants.ping.Length); - - connection.Close(Driver); - Driver.ScheduleUpdate().Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - - Assert.That( - RemoteDriver.PopEventForConnection(remoteId, out reader) == NetworkEvent.Type.Disconnect); - } - } -} diff --git a/Tests/Editor/NetworkDriverLoadTest.cs b/Tests/Editor/NetworkDriverLoadTest.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/Editor/NetworkDriverLoadTest.cs.meta b/Tests/Editor/NetworkDriverLoadTest.cs.meta deleted file mode 100644 index 92ec3c5..0000000 --- a/Tests/Editor/NetworkDriverLoadTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 46aed37f60b217d46b4c804ca2bcf12b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkDriverUnitTests.cs b/Tests/Editor/NetworkDriverUnitTests.cs deleted file mode 100644 index fcc2d4f..0000000 --- a/Tests/Editor/NetworkDriverUnitTests.cs +++ /dev/null @@ -1,838 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NUnit.Framework; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Error; -using UnityEngine; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; -using UnityEngine.TestTools; -using Random = UnityEngine.Random; - -namespace Unity.Networking.Transport.Tests.Utilities -{ - using System.Linq; - public static class Random - { - private static System.Random random = new System.Random(); - - public static string String(int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - return new string(Enumerable.Repeat(chars, length) - .Select(s => s[random.Next(s.Length)]).ToArray()); - } - } -} - -namespace Unity.Networking.Transport.Tests -{ - public struct LocalDriverHelper : IDisposable - { - public NetworkEndPoint EndPoint { get; } - public NetworkDriver m_LocalDriver; - private NativeArray m_LocalData; - public NetworkConnection Connection { get; internal set; } - public List ClientConnections; - - public LocalDriverHelper(NetworkEndPoint endpoint, NetworkSettings settings = default) - { - m_LocalDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - m_LocalData = new NativeArray(NetworkParameterConstants.MTU, Allocator.Persistent); - - if (endpoint.IsValid) - { - EndPoint = endpoint; - } - else - { - EndPoint = NetworkEndPoint.LoopbackIpv4.WithPort(1); - } - - Connection = default(NetworkConnection); - ClientConnections = new List(); - } - - public void Dispose() - { - m_LocalDriver.Dispose(); - m_LocalData.Dispose(); - } - - public void Update() - { - m_LocalDriver.ScheduleUpdate().Complete(); - } - - public NetworkConnection Accept() - { - return m_LocalDriver.Accept(); - } - - public void Host() - { - m_LocalDriver.Bind(EndPoint); - m_LocalDriver.Listen(); - } - - public void Connect(NetworkEndPoint endpoint) - { - Assert.True(endpoint.IsValid); - Connection = m_LocalDriver.Connect(endpoint); - m_LocalDriver.ScheduleUpdate().Complete(); - } - - public unsafe void Assert_GotConnectionRequest(NetworkEndPoint from, bool accept = false) - { - int length; - NetworkInterfaceEndPoint remote; - Assert.True(EndPoint.IsLoopback || EndPoint.IsAny); - Assert.True(from.IsLoopback || from.IsAny); - var localEndPoint = IPCManager.Instance.CreateEndPoint(EndPoint.Port); - var fromEndPoint = IPCManager.Instance.CreateEndPoint(from.Port); - Assert.True( - IPCManager.Instance.PeekNext(localEndPoint, m_LocalData.GetUnsafePtr(), out length, out remote) >= - UdpCHeader.Length); - - UdpCHeader header = new UdpCHeader(); - var reader = new DataStreamReader(m_LocalData.GetSubArray(0, UdpCHeader.Length)); - Assert.True(reader.IsCreated); - - reader.ReadBytes((byte*)&header, UdpCHeader.Length); - Assert.AreEqual((int)UdpCProtocol.ConnectionRequest, header.Type); - - Assert.True(remote == fromEndPoint); - - if (accept) - { - m_LocalDriver.ScheduleUpdate().Complete(); - var con = m_LocalDriver.Accept(); - ClientConnections.Add(con); - Assert.True(con != default(NetworkConnection)); - } - } - - public unsafe void Assert_GotDisconnectionRequest(NetworkEndPoint from) - { - int length; - NetworkInterfaceEndPoint remote; - Assert.True(EndPoint.IsLoopback || EndPoint.IsAny); - Assert.True(from.IsLoopback || from.IsAny); - var localEndPoint = IPCManager.Instance.CreateEndPoint(EndPoint.Port); - var fromEndPoint = IPCManager.Instance.CreateEndPoint(from.Port); - Assert.True( - IPCManager.Instance.PeekNext(localEndPoint, m_LocalData.GetUnsafePtr(), out length, out remote) >= - UdpCHeader.Length); - - UdpCHeader header = new UdpCHeader(); - var reader = new DataStreamReader(m_LocalData.GetSubArray(0, UdpCHeader.Length)); - Assert.True(reader.IsCreated); - reader.ReadBytes((byte*)&header, UdpCHeader.Length); - Assert.True(header.Type == (int)UdpCProtocol.Disconnect); - - Assert.True(remote == fromEndPoint); - } - - public unsafe void Assert_GotDataRequest(NetworkEndPoint from, byte[] dataToCompare) - { - NetworkInterfaceEndPoint remote = default; - int headerLen = UdpCHeader.Length; - void* packetData = (byte*)m_LocalData.GetUnsafePtr(); - int payloadLen = NetworkParameterConstants.MTU; - int dataLen = 0; - Assert.True(EndPoint.IsLoopback || EndPoint.IsAny); - Assert.True(from.IsLoopback || from.IsAny); - var localEndPoint = IPCManager.Instance.CreateEndPoint(EndPoint.Port); - var fromEndPoint = IPCManager.Instance.CreateEndPoint(from.Port); - dataLen = IPCManager.Instance.ReceiveMessageEx(localEndPoint, packetData, payloadLen, ref remote); - - payloadLen = dataLen - headerLen; - if (payloadLen <= 0) - { - payloadLen = 0; - } - - UdpCHeader header = new UdpCHeader(); - var reader = new DataStreamReader(m_LocalData.GetSubArray(0, headerLen)); - Assert.True(reader.IsCreated); - reader.ReadBytes((byte*)&header, headerLen); - Assert.True(header.Type == (int)UdpCProtocol.Data); - - Assert.True(remote == fromEndPoint); - - Assert.True(payloadLen == dataToCompare.Length); - - reader = new DataStreamReader(m_LocalData.GetSubArray(headerLen, dataToCompare.Length)); - var received = new NativeArray(dataToCompare.Length, Allocator.Temp); - reader.ReadBytes(received); - - for (int i = 0, n = dataToCompare.Length; i < n; ++i) - Assert.True(received[i] == dataToCompare[i]); - } - - public unsafe void Assert_PopEventForConnection(NetworkConnection connection, NetworkEvent.Type evnt) - { - DataStreamReader reader; - var retval = m_LocalDriver.PopEventForConnection(connection, out reader); - Assert.True(retval == evnt); - } - - public unsafe void Assert_PopEvent(out NetworkConnection connection, NetworkEvent.Type evnt) - { - DataStreamReader reader; - - var retval = m_LocalDriver.PopEvent(out connection, out reader); - Assert.True(retval == evnt); - } - } - - public class NetworkDriverUnitTests - { - private const string backend = "baselib"; - [Test] - public void InitializeAndDestroyDriver() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - driver.Dispose(); - } - - [Test] - public void BindDriverToAEndPoint() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - - driver.Bind(NetworkEndPoint.LoopbackIpv4); - driver.Dispose(); - } - - [Test] - public void NoErrorOnUnboundUpdate() - { - using var driver = NetworkDriver.Create(); - driver.ScheduleUpdate().Complete(); - } - - [Test] - public void ListenOnDriver() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - - // Make sure we Bind before we Listen. - driver.Bind(NetworkEndPoint.LoopbackIpv4); - driver.Listen(); - - Assert.True(driver.Listening); - driver.Dispose(); - } - - [Test] - public void AcceptNewConnectionsOnDriver() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - - // Make sure we Bind before we Listen. - driver.Bind(NetworkEndPoint.LoopbackIpv4); - driver.Listen(); - - Assert.True(driver.Listening); - - //NetworkConnection connection; - while ((/*connection =*/ driver.Accept()) != default(NetworkConnection)) - { - //Assert.True(connectionId != NetworkParameterConstants.InvalidConnectionId); - } - - driver.Dispose(); - } - - [Test] - public void ConnectToARemoteEndPoint() - { - using (var host = new LocalDriverHelper(default(NetworkEndPoint))) - using (var driver = new NetworkDriver(new IPCNetworkInterface())) - { - host.Host(); - - NetworkConnection connectionId = driver.Connect(host.EndPoint); - Assert.True(connectionId != default(NetworkConnection)); - driver.ScheduleUpdate().Complete(); - - var local = driver.LocalEndPoint(); - host.Assert_GotConnectionRequest(local); - } - } - - [Test] - public void GetNotValidConnectionState() - { - using (var driver = new NetworkDriver(new IPCNetworkInterface())) - { - Assert.AreEqual(NetworkConnection.State.Disconnected, driver.GetConnectionState(new NetworkConnection() {m_NetworkId = Int16.MaxValue})); - Assert.AreEqual(NetworkConnection.State.Disconnected, driver.GetConnectionState(new NetworkConnection() {m_NetworkId = -1})); - } - } - - // TODO: Add tests where connection attempts are exceeded (connect fails) - // TODO: Test dropped connection accept messages (accept retries happen) - // TODO: Needs a way to explicitly assert on connect attempt stats - // In this test multiple connect requests are received on the server, from client, might be this is expected - // because of how the IPC driver works, but this situation is handled properly at least by basic driver logic. - [Test] - public void ConnectAttemptWithRetriesToARemoteEndPoint() - { - NetworkConnection connection; - NetworkEvent.Type eventType = 0; - DataStreamReader reader; - - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters(connectTimeoutMS: 15, maxConnectAttempts: 10, fixedFrameTimeMS: 10); - - // Tiny connect timeout for this test to be quicker - using (var client = new NetworkDriver(new IPCNetworkInterface(), settings)) - { - var hostAddress = NetworkEndPoint.LoopbackIpv4.WithPort(1); - client.Connect(hostAddress); - - // Wait past the connect timeout so there will be unanswered connect requests - client.ScheduleUpdate().Complete(); - client.ScheduleUpdate().Complete(); - - using (var host = new LocalDriverHelper(hostAddress)) - { - host.Host(); - - // Now give the next connect attempt time to happen - // TODO: Would be better to be able to see internal state here and explicitly wait until next connect attempt happens - //client.ScheduleUpdate().Complete(); - - host.Assert_GotConnectionRequest(client.LocalEndPoint(), true); - - // Wait for the client to get the connect event back - for (int i = 0; i < 2; ++i) - { - client.ScheduleUpdate().Complete(); - eventType = client.PopEvent(out connection, out reader); - if (eventType != NetworkEvent.Type.Empty) - break; - } - - Assert.AreEqual(NetworkEvent.Type.Connect, eventType); - } - } - } - - [Test] - public void DisconnectFromARemoteEndPoint() - { - using (var host = new LocalDriverHelper(default(NetworkEndPoint))) - using (var driver = new NetworkDriver(new IPCNetworkInterface())) - { - host.Host(); - - // Need to be connected in order to be able to send a disconnect packet. - NetworkConnection connectionId = driver.Connect(host.EndPoint); - Assert.True(connectionId != default(NetworkConnection)); - driver.ScheduleUpdate().Complete(); - - var local = driver.LocalEndPoint(); - host.Assert_GotConnectionRequest(local, true); - - NetworkConnection con; - DataStreamReader slice; - // Pump so we get the accept message back. - driver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Connect, driver.PopEvent(out con, out slice)); - driver.Disconnect(connectionId); - driver.ScheduleUpdate().Complete(); - - host.Assert_GotDisconnectionRequest(local); - } - } - - [Test] - public void DisconnectTimeoutOnServer() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters(disconnectTimeoutMS: 40, fixedFrameTimeMS: 10); - using (var host = new LocalDriverHelper(default(NetworkEndPoint), settings)) - using (var client = new NetworkDriver(new IPCNetworkInterface(), settings)) - { - NetworkConnection id; - NetworkEvent.Type popEvent = NetworkEvent.Type.Empty; - DataStreamReader reader; - byte reason = 0; - - host.Host(); - - client.Connect(host.EndPoint); - client.ScheduleUpdate().Complete(); - host.Assert_GotConnectionRequest(client.LocalEndPoint(), true); - - // Host sends stuff but gets nothing back, until disconnect timeout happens - for (int frm = 0; frm < 10; ++frm) - { - if (host.m_LocalDriver.BeginSend(NetworkPipeline.Null, host.ClientConnections[0], out var stream) == 0) - { - for (int i = 0; i < 100; i++) - stream.WriteByte((byte)i); - - host.m_LocalDriver.EndSend(stream); - } - if ((popEvent = host.m_LocalDriver.PopEvent(out id, out reader)) != NetworkEvent.Type.Empty) - { - reason = (reader.IsCreated && reader.Length > 0) ? reason = reader.ReadByte() : (byte)0; - break; - } - host.Update(); - } - Assert.AreEqual(NetworkEvent.Type.Disconnect, popEvent); - Assert.AreEqual((byte)DisconnectReason.Timeout, reason); - } - } - - [Test] - public void DisconnectByRemote() - { - using (var host = new LocalDriverHelper(default(NetworkEndPoint))) - using (var client = new NetworkDriver(new IPCNetworkInterface())) - { - host.Host(); - var popEvent = NetworkEvent.Type.Empty; - var c = client.Connect(host.EndPoint); - - client.ScheduleUpdate().Complete(); - host.Assert_GotConnectionRequest(client.LocalEndPoint(), true); - - byte reason = 0; - DataStreamReader reader; - for (int frm = 0; frm < 10; ++frm) - { - if (c.GetState(client) == NetworkConnection.State.Connected) c.Disconnect(client); - - if ((popEvent = host.m_LocalDriver.PopEvent(out var id, out reader)) != NetworkEvent.Type.Empty) - { - reason = (reader.IsCreated && reader.Length > 0) ? reason = reader.ReadByte() : (byte)0; - break; - } - host.Update(); - client.ScheduleUpdate().Complete(); - } - Assert.AreEqual(NetworkEvent.Type.Disconnect, popEvent); - Assert.AreEqual((byte)DisconnectReason.ClosedByRemote, reason); - } - } - - [Test] - public void DisconnectByMaxConnectionAttempts() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters(maxConnectAttempts: 1, fixedFrameTimeMS: 10, connectTimeoutMS: 25); - - using (var host = new LocalDriverHelper(default(NetworkEndPoint))) - using (var client = new NetworkDriver(new IPCNetworkInterface(), settings)) - { - host.Host(); - var popEvent = NetworkEvent.Type.Empty; - var c = client.Connect(host.EndPoint); - client.ScheduleUpdate().Complete(); - - byte reason = 0; - var reader = default(DataStreamReader); - for (int frm = 0; frm < 10; ++frm) - { - if ((popEvent = client.PopEvent(out var id, out reader)) != NetworkEvent.Type.Empty) - { - reason = (reader.IsCreated && reader.Length > 0) ? reason = reader.ReadByte() : (byte)0; - break; - } - client.ScheduleUpdate().Complete(); - } - Assert.AreEqual(NetworkEvent.Type.Disconnect, popEvent); - Assert.AreEqual((byte)DisconnectReason.MaxConnectionAttempts, reason); - } - } - - [Test] - public void SendDataToRemoteEndPoint() - { - using (var host = new LocalDriverHelper(default)) - { - host.Host(); - var driver = new NetworkDriver(new IPCNetworkInterface()); - - // Need to be connected in order to be able to send a disconnect packet. - NetworkConnection connectionId = driver.Connect(host.EndPoint); - Assert.True(connectionId != default(NetworkConnection)); - driver.ScheduleUpdate().Complete(); - var local = driver.LocalEndPoint(); - host.Assert_GotConnectionRequest(local, true); - - NetworkConnection con; - DataStreamReader slice; - // Pump so we get the accept message back. - driver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Connect, driver.PopEvent(out con, out slice)); - - var data = Encoding.ASCII.GetBytes("data to send"); - if (driver.BeginSend(NetworkPipeline.Null, connectionId, out var stream) == 0) - { - stream.WriteBytes(new NativeArray(data, Allocator.Temp)); - driver.EndSend(stream); - } - driver.ScheduleUpdate().Complete(); - - host.Assert_GotDataRequest(local, data); - - driver.Dispose(); - } - } - - [Test] - public void HandleEventsFromSpecificEndPoint() - { - using (var host = new LocalDriverHelper(default)) - using (var client0 = new LocalDriverHelper(default)) - using (var client1 = new LocalDriverHelper(default)) - { - host.Host(); - client0.Connect(host.EndPoint); - client1.Connect(host.EndPoint); - - host.Assert_PopEventForConnection(client0.Connection, NetworkEvent.Type.Empty); - host.Assert_PopEventForConnection(client1.Connection, NetworkEvent.Type.Empty); - - host.Update(); - - var clientConnectionId0 = host.Accept(); - Assert.True(clientConnectionId0 != default(NetworkConnection)); - var clientConnectionId1 = host.Accept(); - Assert.True(clientConnectionId1 != default(NetworkConnection)); - - client1.Update(); - client1.Assert_PopEventForConnection(client1.Connection, NetworkEvent.Type.Connect); - - client0.Update(); - client0.Assert_PopEventForConnection(client0.Connection, NetworkEvent.Type.Connect); - } - } - - [Test] - public void HandleEventsFromAnyEndPoint() - { - using (var host = new LocalDriverHelper(default)) - using (var client0 = new LocalDriverHelper(default)) - using (var client1 = new LocalDriverHelper(default)) - { - host.Host(); - client0.Connect(host.EndPoint); - client1.Connect(host.EndPoint); - - host.Assert_PopEventForConnection(client0.Connection, NetworkEvent.Type.Empty); - host.Assert_PopEventForConnection(client1.Connection, NetworkEvent.Type.Empty); - - host.Update(); - - var clientConnectionId0 = host.Accept(); - Assert.True(clientConnectionId0 != default(NetworkConnection)); - var clientConnectionId1 = host.Accept(); - Assert.True(clientConnectionId1 != default(NetworkConnection)); - - NetworkConnection id; - - client1.Update(); - client1.Assert_PopEvent(out id, NetworkEvent.Type.Connect); - Assert.True(id == client1.Connection); - - client0.Update(); - client0.Assert_PopEvent(out id, NetworkEvent.Type.Connect); - Assert.True(id == client0.Connection); - } - } - - [Test] - public void DiscardEventsForNotAcceptedConnections() - { - using (var host = new LocalDriverHelper(default)) - using (var client0 = new LocalDriverHelper(default)) - { - host.Host(); - client0.Connect(host.EndPoint); - - NetworkConnection clientsideConnection; - NetworkConnection serversideNetworkConnection; - - host.Assert_PopEvent(out serversideNetworkConnection, NetworkEvent.Type.Empty); - Assert.AreEqual(default(NetworkConnection), serversideNetworkConnection); - - host.Update(); - client0.Update(); - client0.Assert_PopEvent(out clientsideConnection, NetworkEvent.Type.Connect); - Assert.AreEqual(client0.Connection, clientsideConnection); - Assert.AreEqual(true, clientsideConnection.IsCreated); - Assert.AreEqual(NetworkConnection.State.Connected, clientsideConnection.GetState(client0.m_LocalDriver)); - - //Client has a connection from its perspective, host has sent back Connection Accept packet, but user-level code on host hasn't technically Accept()ed it yet - byte[] testBytesDiscarded = Encoding.ASCII.GetBytes("this DataRequest event should be dropped"); - if (client0.m_LocalDriver.BeginSend(NetworkPipeline.Null, clientsideConnection, out var writer) == 0) - { - writer.WriteBytes(new NativeArray(testBytesDiscarded, Allocator.Temp)); - client0.m_LocalDriver.EndSend(writer); - } - - client0.Update(); - host.Update(); - - //Host hasn't Accepted connection yet, PopEvent should discard ALL events from non-accepted connections - //in this scenario, this yields an empty event - - //Temporarily making a handle that'd be identical to what the first call to Accept() WOULD yield - var fakeFirstNetworkConnectionToBeAccepted = new NetworkConnection - { - m_NetworkId = 0, - m_NetworkVersion = 1 - }; - - //Internal serverside queue has the 1 Data event sitting in its queue - Assert.AreEqual(1, host.m_LocalDriver.GetEventQueueSizeForConnection(fakeFirstNetworkConnectionToBeAccepted)); - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(default(NetworkConnection))); - - //PopEvent discards that and moves onto the next item; in this case, it was the only item, so it's empty - host.Assert_PopEvent(out serversideNetworkConnection, NetworkEvent.Type.Empty); - - //Internal queue is now size=0 - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(fakeFirstNetworkConnectionToBeAccepted)); - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(default(NetworkConnection))); - - //The actual NetworkConnection handle returned by PopEvent is still the default invalid handle - Assert.AreEqual(default(NetworkConnection), serversideNetworkConnection); - - //Write more data - byte[] testBytes = Encoding.ASCII.GetBytes("this DataRequest event should be processed"); - if (client0.m_LocalDriver.BeginSend(NetworkPipeline.Null, clientsideConnection, out writer) == 0) - { - writer.WriteBytes(new NativeArray(testBytes, Allocator.Temp)); - client0.m_LocalDriver.EndSend(writer); - } - client0.Update(); - - //Finally have the host accept - //Verify our earlier fake networkConnection would be equivalent to what the PopEvent method returns - Assert.AreEqual(fakeFirstNetworkConnectionToBeAccepted, host.Accept()); - host.Update(); - - Assert.AreEqual(1, host.m_LocalDriver.GetEventQueueSizeForConnection(fakeFirstNetworkConnectionToBeAccepted)); - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(default(NetworkConnection))); - - //Host should see the second data request now, the non-discarded one that was added prior to the Accept() call - //This illustrates that discarding Data events on non-Accepted connections happens at PopEvent time, not at push time - host.Assert_PopEvent(out serversideNetworkConnection, NetworkEvent.Type.Data); - - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(fakeFirstNetworkConnectionToBeAccepted)); - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(serversideNetworkConnection)); - Assert.AreEqual(0, host.m_LocalDriver.GetEventQueueSizeForConnection(default(NetworkConnection))); - - //Verify our earlier fake networkConnection was equivalent to what the PopEvent method returns - Assert.AreEqual(fakeFirstNetworkConnectionToBeAccepted, serversideNetworkConnection); - } - } - - [Test] - public void ReceiverIgnoresSenderDataPacketsAfterDisconnect() - { - using (var host = new LocalDriverHelper(default)) - using (var client0 = new LocalDriverHelper(default)) - { - host.Host(); - client0.Connect(host.EndPoint); - - client0.Update(); - host.Update(); - client0.Update(); - client0.Assert_PopEvent(out var clientToHostConnection, NetworkEvent.Type.Connect); - - var hostToClientConnection = host.Accept(); - - //Disconnect client, but don't send disconnect packet yet - host.m_LocalDriver.Disconnect(hostToClientConnection); - - //Client still thinks it's connected and is able to send data - byte[] testBytesDiscarded = Encoding.ASCII.GetBytes("host should ignore this Data packet"); - if (client0.m_LocalDriver.BeginSend(NetworkPipeline.Null, clientToHostConnection, out var writer) == 0) - { - writer.WriteBytes(new NativeArray(testBytesDiscarded, Allocator.Temp)); - client0.m_LocalDriver.EndSend(writer); - } - - client0.Update(); - host.Update(); - - host.Assert_PopEventForConnection(hostToClientConnection, NetworkEvent.Type.Empty); - } - } - - [Test] - public void FillInternalBitStreamBuffer() - { - const int k_InternalBufferSize = 1000; - const int k_PacketCount = 21; // Exactly enough to fill the receive buffer + 1 too much - const int k_PacketSize = 50; - const int k_PacketHeaderSize = UdpCHeader.Length; // The header also goes to the buffer - const int k_PayloadSize = k_PacketSize - k_PacketHeaderSize; - - var hostSettings = new NetworkSettings(); - hostSettings.WithDataStreamParameters(size: k_InternalBufferSize); - - using (var host = new NetworkDriver(new IPCNetworkInterface(), hostSettings)) - using (var client = new NetworkDriver(new IPCNetworkInterface())) - { - host.Bind(NetworkEndPoint.LoopbackIpv4); - - host.Listen(); - - NetworkConnection connectionId = client.Connect(host.LocalEndPoint()); - - client.ScheduleUpdate().Complete(); - host.ScheduleUpdate().Complete(); - - NetworkConnection poppedId; - DataStreamReader reader; - host.Accept(); - - client.ScheduleUpdate().Complete(); - - var retval = client.PopEvent(out poppedId, out reader); - Assert.AreEqual(retval, NetworkEvent.Type.Connect); - - var dataBlob = new Dictionary(); - for (int i = 0; i < k_PacketCount; ++i) - { - // Scramble each packet contents so you can't match reading the same data twice as success - dataBlob.Add(i, Encoding.ASCII.GetBytes(Utilities.Random.String(k_PayloadSize))); - } - - for (int i = 0; i < k_PacketCount; ++i) - { - if (client.BeginSend(NetworkPipeline.Null, connectionId, out var stream) == 0) - { - stream.WriteBytes(new NativeArray(dataBlob[i], Allocator.Temp)); - client.EndSend(stream); - } - } - - // Process the pending events - client.ScheduleUpdate().Complete(); - host.ScheduleUpdate().Complete(); - - for (int i = 0; i < k_PacketCount; ++i) - { - retval = host.PopEvent(out poppedId, out reader); - - if (i == k_PacketCount - 1) - { - Assert.AreEqual(retval, NetworkEvent.Type.Empty); - Assert.IsFalse(reader.IsCreated); - host.ScheduleUpdate().Complete(); - retval = host.PopEvent(out poppedId, out reader); - } - - Assert.AreEqual(NetworkEvent.Type.Data, retval); - Assert.AreEqual(reader.Length, k_PayloadSize); - - for (int j = 0; j < k_PayloadSize; ++j) - { - Assert.AreEqual(dataBlob[i][j], reader.ReadByte()); - } - } - } - } - - static void SendAndReceiveMessage(NetworkDriver serverDriver, NetworkDriver clientDriver) - { - DataStreamReader stream; - - var serverEndpoint = NetworkEndPoint.Parse("127.0.0.1", (ushort)Random.Range(2000, 65000)); - serverDriver.Bind(serverEndpoint); - serverDriver.Listen(); - - var clientToServerId = clientDriver.Connect(serverEndpoint); - clientDriver.ScheduleFlushSend(default).Complete(); - - NetworkConnection serverToClientId = default(NetworkConnection); - // Retry a few times since the network might need some time to process - for (int i = 0; i < 10 && serverToClientId == default(NetworkConnection); ++i) - { - clientDriver.ScheduleUpdate().Complete(); - serverDriver.ScheduleUpdate().Complete(); - - serverToClientId = serverDriver.Accept(); - } - - Assert.That(serverToClientId != default(NetworkConnection)); - - clientDriver.ScheduleUpdate().Complete(); - - var eventId = clientDriver.PopEventForConnection(clientToServerId, out stream); - Assert.That(eventId == NetworkEvent.Type.Connect, $"Expected Connect but got {eventId} using {backend}"); - - //52 bytes of data below: - int testInt = 100; - float testFloat = 555.5f; - byte[] testByteArray = Encoding.ASCII.GetBytes("Some bytes blablabla 1111111111111111111"); - - var sentBytes = 0; - if (clientDriver.BeginSend(NetworkPipeline.Null, clientToServerId, out var clientSendData) == 0) - { - clientSendData.WriteInt(testInt); - clientSendData.WriteFloat(testFloat); - clientSendData.WriteInt(testByteArray.Length); - clientSendData.WriteBytes(new NativeArray(testByteArray, Allocator.Temp)); - sentBytes = clientDriver.EndSend(clientSendData); - } - - Assert.AreEqual(clientSendData.Length, sentBytes); - - clientDriver.ScheduleUpdate().Complete(); - serverDriver.ScheduleUpdate().Complete(); - - DataStreamReader serverReceiveStream; - eventId = serverDriver.PopEventForConnection(serverToClientId, out serverReceiveStream); - - Assert.True(eventId == NetworkEvent.Type.Data); - var receivedInt = serverReceiveStream.ReadInt(); - var receivedFloat = serverReceiveStream.ReadFloat(); - var byteArrayLength = serverReceiveStream.ReadInt(); - var receivedBytes = new NativeArray(byteArrayLength, Allocator.Temp); - serverReceiveStream.ReadBytes(receivedBytes); - - Assert.True(testInt == receivedInt); - Assert.That(Mathf.Approximately(testFloat, receivedFloat)); - Assert.AreEqual(testByteArray, receivedBytes); - } - - //Note for the below 3 tests: - //SendAndReceiveMessage() as currently written sends 52 + sizeof(UdpCHeader) bytes. - //If the size of UdpCHeader goes over 12 bytes in the future (currently 10 bytes at time of writing), - //NetworkDataStreamParameter.size in these below tests will need to be increased accordingly. - [Test] - public void SendAndReceiveMessage_RealNetwork() - { - using (var serverDriver = NetworkDriver.Create()) - using (var clientDriver = NetworkDriver.Create()) - { - SendAndReceiveMessage(serverDriver, clientDriver); - } - } - - [Test] - public void SendAndReceiveMessage() - { - using (var serverDriver = new NetworkDriver(new IPCNetworkInterface())) - using (var clientDriver = new NetworkDriver(new IPCNetworkInterface())) - { - SendAndReceiveMessage(serverDriver, clientDriver); - } - } - } -} diff --git a/Tests/Editor/NetworkDriverUnitTests.cs.meta b/Tests/Editor/NetworkDriverUnitTests.cs.meta deleted file mode 100644 index 38d18e6..0000000 --- a/Tests/Editor/NetworkDriverUnitTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6ba3f82bf87aec445a34b94ba9edbca6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkEventQueueTests.cs b/Tests/Editor/NetworkEventQueueTests.cs deleted file mode 100644 index ed4b726..0000000 --- a/Tests/Editor/NetworkEventQueueTests.cs +++ /dev/null @@ -1,213 +0,0 @@ -using NUnit.Framework; -namespace Unity.Networking.Transport.Tests -{ - public class NetworkEventQueueTests - { - [Test] - public void PopFromEmptyQueueReturnsEmpty() - { - var queue = new NetworkEventQueue(1); - int offset, size; - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PushToSingleConnectionCanBePoppedPerConnection() - { - var queue = new NetworkEventQueue(1); - int offset, size; - queue.PushEvent(new NetworkEvent {connectionId = 0, offset = 0, size = 0, type = NetworkEvent.Type.Data}); - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEventForConnection(0, out offset, out size)); - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PushToSingleConnectionCanBePoppedGlobally() - { - var queue = new NetworkEventQueue(1); - int offset, size; - int id; - queue.PushEvent(new NetworkEvent {connectionId = 0, offset = 0, size = 0, type = NetworkEvent.Type.Data}); - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEvent(out id, out offset, out size)); - Assert.AreEqual(0, id); - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEvent(out id, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PushToSingleConnectionCanGrowMaxEvents() - { - var queue = new NetworkEventQueue(1); - int offset, size; - for (int i = 0; i < 16; ++i) - queue.PushEvent(new NetworkEvent {connectionId = 0, offset = 0, size = 0, type = NetworkEvent.Type.Data}); - for (int i = 0; i < 16; ++i) - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEventForConnection(0, out offset, out size)); - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PushToMultipleConnectionsCanGrowMaxEvents() - { - var queue = new NetworkEventQueue(1); - int offset, size; - for (int con = 0; con < 16; ++con) - { - for (int i = 0; i < con; ++i) - { - queue.PushEvent(new NetworkEvent - {connectionId = con, offset = con, size = i, type = NetworkEvent.Type.Data}); - } - } - - for (int con = 0; con < 16; ++con) - { - for (int i = 0; i < con; ++i) - { - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEventForConnection(con, out offset, out size)); - Assert.AreEqual(con, offset); - Assert.AreEqual(i, size); - } - - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(con, out offset, out size)); - } - queue.Dispose(); - } - - [Test] - public void PopMixingMethodsOnSingleConnectionWorks() - { - var queue = new NetworkEventQueue(16); - int offset, size; - for (int i = 0; i < 16; ++i) - queue.PushEvent(new NetworkEvent {connectionId = 0, offset = i, size = 0, type = NetworkEvent.Type.Data}); - int id; - for (int i = 0; i < 16 / 2; ++i) - { - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEvent(out id, out offset, out size)); - Assert.AreEqual(0, id); - Assert.AreEqual(i * 2, offset); - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEventForConnection(0, out offset, out size)); - Assert.AreEqual(i * 2 + 1, offset); - } - - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEvent(out id, out offset, out size)); - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PopMixingMethodsOnMultipleConnectionsWorks() - { - var queue = new NetworkEventQueue(16); - int offset, size; - for (int i = 0; i < 16; ++i) - { - for (int con = 0; con < 16; ++con) - queue.PushEvent(new NetworkEvent - {connectionId = con, offset = con, size = i, type = NetworkEvent.Type.Data}); - } - - // Pop half the events from connection 10, make sure everything else is still in sync - - int id; - for (int i = 0; i < 8; ++i) - { - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEventForConnection(10, out offset, out size)); - Assert.AreEqual(10, offset); - Assert.AreEqual(i, size); - } - for (int i = 0; i < 16; ++i) - { - for (int con = 0; con < 16; ++con) - { - if (con == 10 && i < 8) - continue; - Assert.AreEqual(NetworkEvent.Type.Data, queue.PopEvent(out id, out offset, out size)); - Assert.AreEqual(con, id); - Assert.AreEqual(con, offset); - Assert.AreEqual(i, size); - } - } - - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEventForConnection(10, out offset, out size)); - Assert.AreEqual(NetworkEvent.Type.Empty, queue.PopEvent(out id, out offset, out size)); - queue.Dispose(); - } - } - - public class NetworkEventQueueConcurrentTests - { - [Test] - public void PopFromEmptyQueueReturnsEmpty() - { - var queue = new NetworkEventQueue(1); - var cq = queue.ToConcurrent(); - int offset, size; - Assert.AreEqual(NetworkEvent.Type.Empty, cq.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PopFromSingleConnectionWorks() - { - var queue = new NetworkEventQueue(1); - var cq = queue.ToConcurrent(); - int offset, size; - queue.PushEvent(new NetworkEvent {connectionId = 0, offset = 0, size = 0, type = NetworkEvent.Type.Data}); - Assert.AreEqual(NetworkEvent.Type.Data, cq.PopEventForConnection(0, out offset, out size)); - Assert.AreEqual(NetworkEvent.Type.Empty, cq.PopEventForConnection(0, out offset, out size)); - queue.Dispose(); - } - - [Test] - public void PopFromMultipleConnectionsWorks() - { - var queue = new NetworkEventQueue(1); - var cq = queue.ToConcurrent(); - int offset, size; - for (int i = 0; i < 16; ++i) - queue.PushEvent(new NetworkEvent {connectionId = i, offset = i, size = 0, type = NetworkEvent.Type.Data}); - for (int i = 0; i < 16; ++i) - { - Assert.AreEqual(NetworkEvent.Type.Data, cq.PopEventForConnection(i, out offset, out size)); - Assert.AreEqual(i, offset); - Assert.AreEqual(NetworkEvent.Type.Empty, cq.PopEventForConnection(i, out offset, out size)); - } - - queue.Dispose(); - } - - [Test] - public void PopFromMultipleConnectionsWithGrowingEventsWorks() - { - var queue = new NetworkEventQueue(1); - var cq = queue.ToConcurrent(); - int offset, size; - for (int con = 0; con < 16; ++con) - { - for (int i = 0; i < con; ++i) - { - queue.PushEvent(new NetworkEvent - {connectionId = con, offset = con, size = i, type = NetworkEvent.Type.Data}); - } - } - - for (int con = 0; con < 16; ++con) - { - for (int i = 0; i < con; ++i) - { - Assert.AreEqual(NetworkEvent.Type.Data, cq.PopEventForConnection(con, out offset, out size)); - Assert.AreEqual(con, offset); - Assert.AreEqual(i, size); - } - - Assert.AreEqual(NetworkEvent.Type.Empty, cq.PopEventForConnection(con, out offset, out size)); - } - queue.Dispose(); - } - } -} diff --git a/Tests/Editor/NetworkEventQueueTests.cs.meta b/Tests/Editor/NetworkEventQueueTests.cs.meta deleted file mode 100644 index 36806b0..0000000 --- a/Tests/Editor/NetworkEventQueueTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3cf7054b45ae6495a9f1ebcf1ac90cdb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkHostUnitTests.cs b/Tests/Editor/NetworkHostUnitTests.cs deleted file mode 100644 index 0a878e7..0000000 --- a/Tests/Editor/NetworkHostUnitTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using NUnit.Framework; - -namespace Unity.Networking.Transport.Tests -{ - public class NetworkHostUnitTests - { - private NetworkDriver Driver; - private NetworkDriver RemoteDriver; - - [SetUp] - public void IPC_Setup() - { - Driver = new NetworkDriver(new IPCNetworkInterface()); - RemoteDriver = new NetworkDriver(new IPCNetworkInterface()); - } - - [TearDown] - public void IPC_TearDown() - { - Driver.Dispose(); - RemoteDriver.Dispose(); - } - - [Test] - public void Listen() - { - Driver.Bind(NetworkEndPoint.LoopbackIpv4); - Driver.Listen(); - Assert.That(Driver.Listening); - } - - [Test] - public void Accept() - { - Driver.Bind(NetworkEndPoint.LoopbackIpv4); - Driver.Listen(); - Assert.That(Driver.Listening); - - // create connection to test to connect. - /*var remote =*/ RemoteDriver.Connect(Driver.LocalEndPoint()); - - NetworkConnection id; - DataStreamReader reader; - const int maximumIterations = 10; - int count = 0; - bool connected = false; - while (count++ < maximumIterations) - { - // Clear pending events - Driver.PopEvent(out id, out reader); - RemoteDriver.PopEvent(out id, out reader); - - Driver.ScheduleUpdate().Complete(); - RemoteDriver.ScheduleUpdate().Complete(); - var connection = Driver.Accept(); - if (connection != default(NetworkConnection)) - { - connected = true; - } - } - - Assert.That(connected); - } - } -} diff --git a/Tests/Editor/NetworkHostUnitTests.cs.meta b/Tests/Editor/NetworkHostUnitTests.cs.meta deleted file mode 100644 index a1ea160..0000000 --- a/Tests/Editor/NetworkHostUnitTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 18b1968e3d0921d4cbc2fca61d48dffa -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkJobTests.cs b/Tests/Editor/NetworkJobTests.cs deleted file mode 100644 index 115dcf2..0000000 --- a/Tests/Editor/NetworkJobTests.cs +++ /dev/null @@ -1,413 +0,0 @@ -using System; -using NUnit.Framework; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using System.Collections.Generic; - -namespace Unity.Networking.Transport.Tests -{ - public class NetworkJobTests - { - void WaitForConnected(NetworkDriver clientDriver, NetworkDriver serverDriver, - NetworkConnection clientToServer) - { - // Make sure connect message is sent - clientDriver.ScheduleFlushSend(default).Complete(); - // Make sure connection accept message is sent back - serverDriver.ScheduleUpdate().Complete(); - // Handle the connection accept message - clientDriver.ScheduleUpdate().Complete(); - DataStreamReader strmReader; - // Make sure the connected message was received - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(clientDriver, out strmReader)); - } - - [Test] - public void ScheduleUpdateWorks() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - var updateHandle = driver.ScheduleUpdate(); - updateHandle.Complete(); - driver.Dispose(); - } - - [Test] - public void ScheduleUpdateWithMissingDependencyThrowsException() - { - var driver = new NetworkDriver(new IPCNetworkInterface()); - var updateHandle = driver.ScheduleUpdate(); - Assert.Throws(() => { driver.ScheduleUpdate().Complete(); }); - updateHandle.Complete(); - driver.Dispose(); - } - - [Test] - public void DataStremReaderIsOnlyUsableUntilUpdate() - { - var serverDriver = new NetworkDriver(new IPCNetworkInterface()); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var clientDriver = new NetworkDriver(new IPCNetworkInterface()); - var clientToServer = clientDriver.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(clientDriver, serverDriver, clientToServer); - - if (clientDriver.BeginSend(clientToServer, out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - clientDriver.EndSend(strmWriter); - } - clientDriver.ScheduleUpdate().Complete(); - var serverToClient = serverDriver.Accept(); - serverDriver.ScheduleUpdate().Complete(); - DataStreamReader strmReader; - Assert.AreEqual(NetworkEvent.Type.Data, serverToClient.PopEvent(serverDriver, out strmReader)); - var ctx = strmReader; - Assert.AreEqual(42, strmReader.ReadInt()); - strmReader = ctx; - Assert.AreEqual(42, strmReader.ReadInt()); - serverDriver.ScheduleUpdate().Complete(); - strmReader = ctx; - Assert.Catch(() => { strmReader.ReadInt(); }); - clientDriver.Dispose(); - serverDriver.Dispose(); - } - - struct AcceptJob : IJob - { - public NetworkDriver driver; - public NativeArray connections; - public void Execute() - { - for (int i = 0; i < connections.Length; ++i) - connections[i] = driver.Accept(); - } - } - [Test] - public void AcceptInJobWorks() - { - var serverDriver = new NetworkDriver(new IPCNetworkInterface()); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var clientDriver = new NetworkDriver(new IPCNetworkInterface()); - /*var clientToServer =*/ clientDriver.Connect(serverDriver.LocalEndPoint()); - clientDriver.ScheduleUpdate().Complete(); - - var serverToClient = new NativeArray(1, Allocator.TempJob); - var acceptJob = new AcceptJob {driver = serverDriver, connections = serverToClient}; - Assert.IsFalse(serverToClient[0].IsCreated); - acceptJob.Schedule(serverDriver.ScheduleUpdate()).Complete(); - Assert.IsTrue(serverToClient[0].IsCreated); - - serverToClient.Dispose(); - clientDriver.Dispose(); - serverDriver.Dispose(); - } - - struct ReceiveJob : IJob - { - public NetworkDriver driver; - public NativeArray connections; - public NativeArray result; - public void Execute() - { - DataStreamReader strmReader; - // Data - connections[0].PopEvent(driver, out strmReader); - result[0] = strmReader.ReadInt(); - } - } - [Test] - public void ReceiveInJobWorks() - { - var serverDriver = new NetworkDriver(new IPCNetworkInterface()); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var clientDriver = new NetworkDriver(new IPCNetworkInterface()); - var clientToServer = clientDriver.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(clientDriver, serverDriver, clientToServer); - - if (clientDriver.BeginSend(clientToServer, out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - clientDriver.EndSend(strmWriter); - } - clientDriver.ScheduleUpdate().Complete(); - - var serverToClient = new NativeArray(1, Allocator.TempJob); - var result = new NativeArray(1, Allocator.TempJob); - var recvJob = new ReceiveJob {driver = serverDriver, connections = serverToClient, result = result}; - Assert.AreNotEqual(42, result[0]); - var acceptJob = new AcceptJob {driver = serverDriver, connections = serverToClient}; - recvJob.Schedule(serverDriver.ScheduleUpdate(acceptJob.Schedule())).Complete(); - Assert.AreEqual(42, result[0]); - - result.Dispose(); - serverToClient.Dispose(); - clientDriver.Dispose(); - serverDriver.Dispose(); - } - - struct SendJob : IJob - { - public NetworkDriver driver; - public NetworkConnection connection; - public void Execute() - { - if (driver.BeginSend(connection, out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - driver.EndSend(strmWriter); - } - } - } - [Test] - public void SendInJobWorks() - { - var serverDriver = new NetworkDriver(new IPCNetworkInterface()); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var clientDriver = new NetworkDriver(new IPCNetworkInterface()); - var clientToServer = clientDriver.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(clientDriver, serverDriver, clientToServer); - var sendJob = new SendJob {driver = clientDriver, connection = clientToServer}; - clientDriver.ScheduleUpdate(sendJob.Schedule()).Complete(); - var serverToClient = serverDriver.Accept(); - serverDriver.ScheduleUpdate().Complete(); - DataStreamReader strmReader; - Assert.AreEqual(NetworkEvent.Type.Data, serverToClient.PopEvent(serverDriver, out strmReader)); - Assert.AreEqual(42, strmReader.ReadInt()); - clientDriver.Dispose(); - serverDriver.Dispose(); - } - - struct SendReceiveParallelJob : IJobParallelFor - { - public NetworkDriver.Concurrent driver; - public NativeArray connections; - public void Execute(int i) - { - DataStreamReader strmReader; - // Data - if (driver.PopEventForConnection(connections[i], out strmReader) != NetworkEvent.Type.Data) - throw new InvalidOperationException("Expected data: " + i); - int result = strmReader.ReadInt(); - if (driver.BeginSend(connections[i], out var strmWriter) == 0) - { - strmWriter.WriteInt(result + 1); - driver.EndSend(strmWriter); - } - } - } - [Test] - public void SendReceiveInParallelJobWorks() - { - NativeArray serverToClient; - using (var serverDriver = new NetworkDriver(new IPCNetworkInterface())) - using (var clientDriver0 = new NetworkDriver(new IPCNetworkInterface())) - using (var clientDriver1 = new NetworkDriver(new IPCNetworkInterface())) - using (serverToClient = new NativeArray(2, Allocator.Persistent)) - { - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var clientToServer0 = clientDriver0.Connect(serverDriver.LocalEndPoint()); - var clientToServer1 = clientDriver1.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(clientDriver0, serverDriver, clientToServer0); - - if (clientDriver0.BeginSend(clientToServer0, out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - serverToClient[0] = serverDriver.Accept(); - Assert.IsTrue(serverToClient[0].IsCreated); - WaitForConnected(clientDriver1, serverDriver, clientToServer1); - serverToClient[1] = serverDriver.Accept(); - Assert.IsTrue(serverToClient[1].IsCreated); - - clientDriver0.EndSend(strmWriter); - } - - if (clientDriver1.BeginSend(clientToServer1, out var strmWriter2) == 0) - { - strmWriter2.WriteBytes(strmWriter.AsNativeArray()); - clientDriver1.EndSend(strmWriter2); - } - - clientDriver0.ScheduleUpdate().Complete(); - clientDriver1.ScheduleUpdate().Complete(); - - var sendRecvJob = new SendReceiveParallelJob {driver = serverDriver.ToConcurrent(), connections = serverToClient}; - var jobHandle = serverDriver.ScheduleUpdate(); - jobHandle = sendRecvJob.Schedule(serverToClient.Length, 1, jobHandle); - serverDriver.ScheduleUpdate(jobHandle).Complete(); - - AssertDataReceived(serverDriver, serverToClient, clientDriver0, clientToServer0, 43, true); - AssertDataReceived(serverDriver, serverToClient, clientDriver1, clientToServer1, 43, true); - } - } - - [BurstCompile /*(CompileSynchronously = true)*/] // FIXME: sync compilation makes tests timeout - struct SendReceiveWithPipelineParallelJob : IJobParallelFor - { - public NetworkDriver.Concurrent driver; - public NativeArray connections; - public NetworkPipeline pipeline; - public void Execute(int i) - { - DataStreamReader strmReader; - // Data - if (driver.PopEventForConnection(connections[i], out strmReader) != NetworkEvent.Type.Data) - throw new InvalidOperationException("Expected data: " + i); - int result = strmReader.ReadInt(); - if (driver.BeginSend(connections[i], out var strmWriter) == 0) - { - strmWriter.WriteInt(result + 1); - driver.EndSend(strmWriter); - } - } - } - [Test] - public void SendReceiveWithPipelineInParallelJobWorks() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, maxFrameTimeMS: 16); - - NativeArray serverToClient; - using (var serverDriver = new NetworkDriver(new IPCNetworkInterface(), settings)) - using (var clientDriver0 = new NetworkDriver(new IPCNetworkInterface(), settings)) - using (var clientDriver1 = new NetworkDriver(new IPCNetworkInterface(), settings)) - using (serverToClient = new NativeArray(2, Allocator.Persistent)) - { - var serverPipeline = serverDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - var client0Pipeline = clientDriver0.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var client1Pipeline = clientDriver1.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var clientToServer0 = clientDriver0.Connect(serverDriver.LocalEndPoint()); - var clientToServer1 = clientDriver1.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(clientDriver0, serverDriver, clientToServer0); - serverToClient[0] = serverDriver.Accept(); - Assert.IsTrue(serverToClient[0].IsCreated); - WaitForConnected(clientDriver1, serverDriver, clientToServer1); - serverToClient[1] = serverDriver.Accept(); - Assert.IsTrue(serverToClient[1].IsCreated); - - if (clientDriver0.BeginSend(clientToServer0, out var strmWriter0) == 0) - { - strmWriter0.WriteInt(42); - clientDriver0.EndSend(strmWriter0); - } - if (clientDriver1.BeginSend(clientToServer1, out var strmWriter1) == 0) - { - strmWriter1.WriteInt(42); - clientDriver1.EndSend(strmWriter1); - } - - clientDriver0.ScheduleUpdate().Complete(); - clientDriver1.ScheduleUpdate().Complete(); - - var sendRecvJob = new SendReceiveWithPipelineParallelJob - {driver = serverDriver.ToConcurrent(), connections = serverToClient, pipeline = serverPipeline}; - var jobHandle = serverDriver.ScheduleUpdate(); - jobHandle = sendRecvJob.Schedule(serverToClient.Length, 1, jobHandle); - serverDriver.ScheduleUpdate(jobHandle).Complete(); - - AssertDataReceived(serverDriver, serverToClient, clientDriver0, clientToServer0, 43, false); - AssertDataReceived(serverDriver, serverToClient, clientDriver1, clientToServer1, 43, false); - } - } - - [Test] - public void ParallelSendReceiveStressTest() - { - var clientSettings = new NetworkSettings(); - clientSettings.WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, maxFrameTimeMS: 16); - - var serverSettings = new NetworkSettings(); - serverSettings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, maxFrameTimeMS: 16) - .WithBaselibNetworkInterfaceParameters(maximumPayloadSize: 64, receiveQueueCapacity: 250, sendQueueCapacity: 150); - - NativeArray serverToClient; - var clientDrivers = new List(); - var clientPipelines = new List(); - var clientToServer = new List(); - try - { - for (int i = 0; i < 250; ++i) - { - clientDrivers.Add(new NetworkDriver(new IPCNetworkInterface(), clientSettings)); - clientPipelines.Add(clientDrivers[i].CreatePipeline(typeof(ReliableSequencedPipelineStage))); - } - using (var serverDriver = new NetworkDriver(new IPCNetworkInterface(), serverSettings)) - using (serverToClient = new NativeArray(clientDrivers.Count, Allocator.Persistent)) - { - var serverPipeline = serverDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - serverDriver.Bind(NetworkEndPoint.LoopbackIpv4); - serverDriver.Listen(); - for (var i = 0; i < clientDrivers.Count; ++i) - { - var drv = clientDrivers[i]; - var con = drv.Connect(serverDriver.LocalEndPoint()); - WaitForConnected(drv, serverDriver, con); - clientToServer.Add(con); - serverToClient[i] = serverDriver.Accept(); - Assert.IsTrue(serverToClient[i].IsCreated); - } - for (var i = 0; i < clientDrivers.Count; ++i) - { - if (clientDrivers[i].BeginSend(clientPipelines[i], clientToServer[i], out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - clientDrivers[i].EndSend(strmWriter); - } - clientDrivers[i].ScheduleFlushSend(default).Complete(); - } - - var sendRecvJob = new SendReceiveWithPipelineParallelJob - {driver = serverDriver.ToConcurrent(), connections = serverToClient, pipeline = serverPipeline}; - var jobHandle = serverDriver.ScheduleUpdate(); - jobHandle = sendRecvJob.Schedule(serverToClient.Length, 1, jobHandle); - serverDriver.ScheduleUpdate(jobHandle).Complete(); - - for (var i = 0; i < clientDrivers.Count; ++i) - AssertDataReceived(serverDriver, serverToClient, clientDrivers[i], clientToServer[i], 43, false); - } - } - finally - { - foreach (var drv in clientDrivers) - drv.Dispose(); - } - } - - void AssertDataReceived(NetworkDriver serverDriver, NativeArray serverConnections, NetworkDriver clientDriver, NetworkConnection clientToServerConnection, int assertValue, bool serverResend) - { - DataStreamReader strmReader; - clientDriver.ScheduleUpdate().Complete(); - var evnt = clientToServerConnection.PopEvent(clientDriver, out strmReader); - int counter = 0; - while (evnt == NetworkEvent.Type.Empty) - { - serverDriver.ScheduleUpdate().Complete(); - clientDriver.ScheduleUpdate().Complete(); - evnt = clientToServerConnection.PopEvent(clientDriver, out strmReader); - if (counter++ > 1000) - { - if (!serverResend) - break; - counter = 0; - for (int i = 0; i < serverConnections.Length; ++i) - { - if (serverDriver.BeginSend(serverConnections[i], out var strmWriter) == 0) - { - strmWriter.WriteInt(42); - serverDriver.EndSend(strmWriter); - } - } - } - } - Assert.AreEqual(NetworkEvent.Type.Data, evnt); - Assert.AreEqual(assertValue, strmReader.ReadInt()); - } - } -} diff --git a/Tests/Editor/NetworkJobTests.cs.meta b/Tests/Editor/NetworkJobTests.cs.meta deleted file mode 100644 index 9bf1e48..0000000 --- a/Tests/Editor/NetworkJobTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6c3ab283e669f4aa5b81fd8477f0f48a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkParamsTests.cs b/Tests/Editor/NetworkParamsTests.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/Editor/NetworkParamsTests.cs.meta b/Tests/Editor/NetworkParamsTests.cs.meta deleted file mode 100644 index 9f07997..0000000 --- a/Tests/Editor/NetworkParamsTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 47fafc55cd4a7b74c87066d6f440f4ef -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkPipelineTest.cs b/Tests/Editor/NetworkPipelineTest.cs deleted file mode 100644 index 3edb2c5..0000000 --- a/Tests/Editor/NetworkPipelineTest.cs +++ /dev/null @@ -1,918 +0,0 @@ -using System; -using AOT; -using NUnit.Framework; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Utilities; -using Unity.Burst; -using Unity.Networking.Transport.Protocols; - -namespace Unity.Networking.Transport.Tests -{ - [BurstCompile] - public unsafe struct TestPipelineStageWithHeader : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: 0, - HeaderCapacity: UdpCHeader.Length, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var headerData = (int*)inboundBuffer.buffer; - if (*headerData != 1) - throw new InvalidOperationException("Header data invalid, got " + *headerData); - inboundBuffer = inboundBuffer.Slice(4); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - ctx.header.WriteInt((int)1); - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - - [BurstCompile] - public unsafe struct TestPipelineStageWithHeaderTwo : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: 0, - HeaderCapacity: 4, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var headerData = (int*)inboundBuffer.buffer; - if (*headerData != 2) - throw new InvalidOperationException("Header data invalid, got " + *headerData); - - inboundBuffer = inboundBuffer.Slice(4); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - ctx.header.WriteInt((int)2); - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - - [BurstCompile] - public unsafe struct TestEncryptPipelineStage : INetworkPipelineStage - { - private const int k_MaxPacketSize = 64; - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: k_MaxPacketSize, - SendCapacity: k_MaxPacketSize, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - for (int i = 0; i < inboundBuffer.bufferLength; ++i) - ctx.internalProcessBuffer[i] = (byte)(inboundBuffer.buffer[i] ^ 0xff); - inboundBuffer.buffer = ctx.internalProcessBuffer; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var len = inboundBuffer.bufferLength; - for (int i = 0; i < len; ++i) - ctx.internalProcessBuffer[inboundBuffer.headerPadding + i] = (byte)(inboundBuffer.buffer[i] ^ 0xff); - var nextInbound = default(InboundSendBuffer); - nextInbound.bufferWithHeaders = ctx.internalProcessBuffer; - nextInbound.bufferWithHeadersLength = len + inboundBuffer.headerPadding; - nextInbound.SetBufferFrombufferWithHeaders(); - inboundBuffer = nextInbound; - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - [BurstCompile] - public unsafe struct TestEncryptInPlacePipelineStage : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: NetworkParameterConstants.MTU, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - for (int i = 0; i < inboundBuffer.bufferLength; ++i) - inboundBuffer.buffer[i] = (byte)(inboundBuffer.buffer[i] ^ 0xff); - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var len = inboundBuffer.bufferLength; - for (int i = 0; i < len; ++i) - ctx.internalProcessBuffer[inboundBuffer.headerPadding + i] = (byte)(inboundBuffer.buffer[i] ^ 0xff); - var nextInbound = default(InboundSendBuffer); - nextInbound.bufferWithHeaders = ctx.internalProcessBuffer; - nextInbound.bufferWithHeadersLength = len + inboundBuffer.headerPadding; - nextInbound.SetBufferFrombufferWithHeaders(); - inboundBuffer = nextInbound; - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - [BurstCompile] - public unsafe struct TestInvertPipelineStage : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: NetworkParameterConstants.MTU, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static unsafe int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var len = inboundBuffer.bufferLength; - for (int i = 0; i < len; ++i) - ctx.internalProcessBuffer[inboundBuffer.headerPadding + i] = (byte)(inboundBuffer.buffer[i] ^ 0xff); - var nextInbound = default(InboundSendBuffer); - nextInbound.bufferWithHeaders = ctx.internalProcessBuffer; - nextInbound.bufferWithHeadersLength = len + inboundBuffer.headerPadding; - nextInbound.SetBufferFrombufferWithHeaders(); - inboundBuffer = nextInbound; - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - - [BurstCompile] - public unsafe struct TestPipelineWithInitializers : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 3 * UnsafeUtility.SizeOf(), - SendCapacity: 3 * UnsafeUtility.SizeOf(), - HeaderCapacity: 0, - SharedStateCapacity: 3 * UnsafeUtility.SizeOf() - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var receiveData = (int*)ctx.internalProcessBuffer; - for (int i = 4; i <= 6; ++i) - { - Assert.AreEqual(*receiveData, i); - receiveData++; - } - var sharedData = (int*)ctx.internalSharedProcessBuffer; - for (int i = 7; i <= 8; ++i) - { - Assert.AreEqual(*sharedData, i); - sharedData++; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var sendData = (int*)ctx.internalProcessBuffer; - for (int i = 1; i <= 3; ++i) - { - Assert.AreEqual(*sendData, i); - sendData++; - } - var sharedData = (int*)ctx.internalSharedProcessBuffer; - for (int i = 7; i <= 8; ++i) - { - Assert.AreEqual(*sharedData, i); - sharedData++; - } - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - var sendData = (int*)sendProcessBuffer; - *sendData = 1; - sendData++; - *sendData = 2; - sendData++; - *sendData = 3; - var receiveData = (int*)recvProcessBuffer; - *receiveData = 4; - receiveData++; - *receiveData = 5; - receiveData++; - *receiveData = 6; - var sharedData = (int*)sharedProcessBuffer; - *sharedData = 7; - sharedData++; - *sharedData = 8; - sharedData++; - *sharedData = 9; - } - } - - [BurstCompile] - public unsafe struct TestPipelineWithInitializersTwo : INetworkPipelineStage - { - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 3 * UnsafeUtility.SizeOf(), - SendCapacity: 3 * UnsafeUtility.SizeOf(), - HeaderCapacity: 0, - SharedStateCapacity: 3 * UnsafeUtility.SizeOf() - ); - } - - public int StaticSize => 0; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var receiveData = (int*)ctx.internalProcessBuffer; - for (int i = 4; i <= 6; ++i) - { - Assert.AreEqual(*receiveData, i * 10); - receiveData++; - } - var sharedData = (int*)ctx.internalSharedProcessBuffer; - for (int i = 7; i <= 8; ++i) - { - Assert.AreEqual(*sharedData, i * 10); - sharedData++; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - var sendData = (int*)ctx.internalProcessBuffer; - for (int i = 1; i <= 3; ++i) - { - Assert.AreEqual(*sendData, i * 10); - sendData++; - } - var sharedData = (int*)ctx.internalSharedProcessBuffer; - for (int i = 7; i <= 8; ++i) - { - Assert.AreEqual(*sharedData, i * 10); - sharedData++; - } - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - var sendData = (int*)sendProcessBuffer; - *sendData = 10; - sendData++; - *sendData = 20; - sendData++; - *sendData = 30; - var receiveData = (int*)recvProcessBuffer; - *receiveData = 40; - receiveData++; - *receiveData = 50; - receiveData++; - *receiveData = 60; - var sharedData = (int*)sharedProcessBuffer; - *sharedData = 70; - sharedData++; - *sharedData = 80; - sharedData++; - *sharedData = 90; - } - } - - public struct TestNetworkPipelineStageCollection - { - public static void Register() - { - NetworkPipelineStageCollection.RegisterPipelineStage(new TestPipelineStageWithHeader()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestPipelineStageWithHeaderTwo()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestEncryptPipelineStage()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestEncryptInPlacePipelineStage()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestInvertPipelineStage()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestPipelineWithInitializers()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TestPipelineWithInitializersTwo()); - } - } - - public class NetworkPipelineTest - { - private NetworkDriver m_ServerDriver; - private NetworkDriver m_ClientDriver; - private NetworkDriver m_ClientDriver2; - - [SetUp] - public void IPC_Setup() - { - // NOTE: MaxPacketSize should be 64 for all the tests using simulator except needs to - // account for header size as well (one test has 2x UdpCHeader.Length headers) - var settings = new NetworkSettings(); - settings - .WithNetworkConfigParameters(fixedFrameTimeMS: 16) - .WithSimulatorStageParameters(maxPacketSize: 64 + (2 * UdpCHeader.Length), maxPacketCount: 30, packetDelayMs: 100); - - TestNetworkPipelineStageCollection.Register(); - - m_ServerDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - m_ServerDriver.Bind(NetworkEndPoint.LoopbackIpv4); - m_ServerDriver.Listen(); - m_ClientDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - m_ClientDriver2 = new NetworkDriver(new IPCNetworkInterface(), settings); - } - - [TearDown] - public void IPC_TearDown() - { - m_ClientDriver.Dispose(); - m_ClientDriver2.Dispose(); - m_ServerDriver.Dispose(); - } - - [Test] - public void NetworkPipeline_CreatePipelineIsSymetrical() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestPipelineStageWithHeader)); - Assert.AreEqual(clientPipe, serverPipe); - } - - [Test] - public void NetworkPipeline_CreatePipelineAfterConnectFails() - { - m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.Throws(() => { m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader)); }); - } - - [Test] - public void NetworkPipeline_CreatePipelineWithInvalidStageFails() - { - Assert.Throws(() => { m_ClientDriver.CreatePipeline(typeof(NetworkPipelineTest)); }); - } - - [Test] - public void NetworkPipeline_CreatePipelineWithFragmentationAfterReliabilityFails() - { - Assert.Throws(() => - { - m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(FragmentationPipelineStage)); - }); - } - - [Test] - public void NetworkPipeline_CanExtendHeader() - { - // Create pipeline - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestPipelineStageWithHeader)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_CanModifyAndRestoreData() - { - // Create pipeline - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestEncryptPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestEncryptPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_CanModifyAndRestoreDataInPlace() - { - // Create pipeline - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestEncryptInPlacePipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestEncryptInPlacePipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_CanModifyData() - { - // Create pipeline - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestInvertPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestInvertPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(-1 ^ 42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_MultiplePipelinesWork() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeaderTwo), typeof(TestEncryptPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestPipelineStageWithHeaderTwo), typeof(TestEncryptPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public void NetworkPipeline_CanStorePacketsForLaterDeliveryInReceiveLastStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStorePacketsForLaterDeliveryInReceiveFirstStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(SimulatorPipelineStage), typeof(TestEncryptPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(SimulatorPipelineStage), typeof(TestEncryptPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(SimulatorPipelineStage), typeof(TestEncryptPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStorePacketsForLaterDeliveryInSendLastStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStageInSend)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStageInSend)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestEncryptPipelineStage), typeof(SimulatorPipelineStageInSend)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStorePacketsForLaterDeliveryInSendFirstStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(TestEncryptPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(TestEncryptPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(TestEncryptPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStoreSequencedPacketsForLaterDeliveryInSendLastStage() - { - // Server needs the simulator as it's the only one sending - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(SimulatorPipelineStageInSend)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStoreSequencedPacketsForLaterDeliveryInSendFirstStage() - { - // Server needs the simulator as it's the only one sending - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(UnreliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStoreSequencedPacketsForLaterDeliveryInReceiveLastStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_CanStoreSequencedPacketsForLaterDeliveryInReceiveFirstStage() - { - var clientPipe1 = m_ClientDriver.CreatePipeline(typeof(SimulatorPipelineStage), typeof(UnreliableSequencedPipelineStage)); - var clientPipe2 = m_ClientDriver2.CreatePipeline(typeof(SimulatorPipelineStage), typeof(UnreliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe1, serverPipe); - Assert.AreEqual(clientPipe2, serverPipe); - - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_MultiplePipelinesWithHeadersWork() - { - m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - m_ClientDriver2.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - TestPipeline(30, serverPipe, 0); - } - - [Test] - public void NetworkPipeline_MultiplePipelinesWithHeadersWorkWithSimulator() - { - //m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(SimulatorPipelineStage), typeof(TestPipelineStageWithHeaderTwo)); - //m_ClientDriver2.CreatePipeline(typeof(SimulatorPipelineStage), typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - m_ClientDriver.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - m_ClientDriver2.CreatePipeline(typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(TestPipelineStageWithHeader), typeof(TestPipelineStageWithHeaderTwo)); - TestPipeline(30, serverPipe); - } - - [Test] - public void NetworkPipeline_MuliplePipelinesWithInitializers() - { - m_ClientDriver.CreatePipeline(typeof(TestPipelineWithInitializers), typeof(TestPipelineWithInitializersTwo)); - m_ClientDriver2.CreatePipeline(typeof(TestPipelineWithInitializers), typeof(TestPipelineWithInitializersTwo)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(TestPipelineWithInitializers), typeof(TestPipelineWithInitializersTwo)); - TestPipeline(30, serverPipe, 0); - } - - [Test] - public void NetworkPipeline_MuliplePipelinesWithInitializersAndSimulator() - { - m_ClientDriver.CreatePipeline(typeof(TestPipelineWithInitializers), typeof(SimulatorPipelineStage), typeof(TestPipelineWithInitializersTwo)); - m_ClientDriver2.CreatePipeline(typeof(TestPipelineWithInitializers), typeof(TestPipelineWithInitializersTwo), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(SimulatorPipelineStageInSend), typeof(TestPipelineWithInitializers), typeof(TestPipelineWithInitializersTwo)); - TestPipeline(30, serverPipe); - } - - private void TestPipeline(int packetCount, NetworkPipeline serverPipe, int packetDelay = 100) - { - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - var clientToServer2 = m_ClientDriver2.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - Assert.AreNotEqual(default(NetworkConnection), clientToServer2); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ClientDriver2.ScheduleUpdate().Complete(); - - // Driver only updates time in update, so must read start time before update - var startTime = m_ServerDriver.LastUpdateTime; - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - var serverToClient2 = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient2); - - // Send given packetCount number of packets in a row in one update - // Write 1's for packet 1, 2's for packet 2 and so on and verify they're received in same order - for (int i = 0; i < packetCount; i++) - { - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0 && - m_ServerDriver.BeginSend(serverPipe, serverToClient2, out var strm2) == 0) - { - for (int j = 0; j < 16; j++) - { - strm.WriteInt((int)i + 1); - strm2.WriteInt((int)i + 1); - } - m_ServerDriver.EndSend(strm); - m_ServerDriver.EndSend(strm2); - } - } - - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - m_ClientDriver2.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver2, out readStrm)); - - ClientReceivePackets(m_ClientDriver, packetCount, clientToServer, startTime, packetDelay); - ClientReceivePackets(m_ClientDriver2, packetCount, clientToServer2, startTime, packetDelay); - } - - private void ClientReceivePackets(NetworkDriver clientDriver, int packetCount, NetworkConnection clientToServer, long startTime, int minDelay) - { - DataStreamReader readStrm; - NetworkEvent.Type netEvent; - var abortFrame = 0; - while (true) - { - if (abortFrame++ > 125) - Assert.Fail("Did not receive first delayed packet"); - netEvent = clientToServer.PopEvent(clientDriver, out readStrm); - if (netEvent == NetworkEvent.Type.Data) - break; - m_ServerDriver.ScheduleUpdate().Complete(); - clientDriver.ScheduleUpdate().Complete(); - } - - // All delayed packets (from first patch) should be poppable now - for (int i = 0; i < packetCount; i++) - { - var delay = m_ServerDriver.LastUpdateTime - startTime; - Assert.AreEqual(NetworkEvent.Type.Data, netEvent); - Assert.GreaterOrEqual(delay, minDelay, $"Delay too low on packet {i}"); - Assert.AreEqual(64, readStrm.Length); - for (int j = 0; j < 16; j++) - { - var read = readStrm.ReadInt(); - Assert.AreEqual(i + 1, read); - Assert.True(read > 0 && read <= packetCount, "read incorrect value: " + read); - } - - // Test done when all packets have been verified - if (i == packetCount - 1) - break; - - // It could be not all patch of packets were processed in one update (depending on how the timers land) - abortFrame = 0; - while ((netEvent = clientToServer.PopEvent(clientDriver, out readStrm)) == NetworkEvent.Type.Empty) - { - if (abortFrame++ > 75) - Assert.Fail("Didn't receive all delayed packets"); - clientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - } - } - } - } -} diff --git a/Tests/Editor/NetworkPipelineTest.cs.meta b/Tests/Editor/NetworkPipelineTest.cs.meta deleted file mode 100644 index def93b1..0000000 --- a/Tests/Editor/NetworkPipelineTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f2413d56d3b014d468eb09e594d227ee -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/NetworkProtocolsTests.cs b/Tests/Editor/NetworkProtocolsTests.cs deleted file mode 100644 index c1cf0d3..0000000 --- a/Tests/Editor/NetworkProtocolsTests.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Unity.Networking.Transport.Tests -{ - class Protocols - { - } -} diff --git a/Tests/Editor/NetworkProtocolsTests.cs.meta b/Tests/Editor/NetworkProtocolsTests.cs.meta deleted file mode 100644 index 3dd6ec4..0000000 --- a/Tests/Editor/NetworkProtocolsTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 194e452ea4df2c6478ae34de7f8893ce -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/RelayNetworkDriverTests.cs b/Tests/Editor/RelayNetworkDriverTests.cs deleted file mode 100644 index e98ff15..0000000 --- a/Tests/Editor/RelayNetworkDriverTests.cs +++ /dev/null @@ -1,376 +0,0 @@ -using NUnit.Framework; -using System.Threading; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Relay; -using UnityEditor; -using UnityEngine; -using UnityEngine.TestTools; - -namespace Unity.Networking.Transport.Tests -{ - public class RelayNetworkDriverTests - { - private ushort m_port = 1234; - - [Test] - public void RelayCheckStructSizes() - { - Assert.AreEqual(RelayMessageHeader.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayAllocationId.k_Length, UnsafeUtility.SizeOf()); - - Assert.AreEqual(RelayMessageAccepted.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayMessageConnectRequest.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayMessageDisconnect.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayMessagePing.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayMessageRelay.Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayConnectionData.k_Length, UnsafeUtility.SizeOf()); - Assert.AreEqual(RelayHMACKey.k_Length, UnsafeUtility.SizeOf()); - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - [Ignore("Unstable in APVs. See MTT-4345.")] - public void RelayNetworkDriver_Bind_Succeed() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData = server.GetRelayConnectionData(0); - var settings = new NetworkSettings(); - settings.WithRelayParameters(serverData: ref serverData, relayConnectionTimeMS: 10000000); - - using (var driver = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings)) - { - Assert.AreEqual(RelayConnectionStatus.NotEstablished, driver.GetRelayConnectionStatus()); - Assert.True(server.CompleteBind(driver, 0)); - Assert.AreEqual(RelayConnectionStatus.Established, driver.GetRelayConnectionStatus()); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - [Ignore("Unstable in APVs. See MTT-4345.")] - public void RelayNetworkDriver_Bind_Retry() - { - const int k_RetryCount = 10; - - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData = server.GetRelayConnectionData(0); - var settings = new NetworkSettings(); - settings.WithRelayParameters(ref serverData) - .WithNetworkConfigParameters(connectTimeoutMS: 50); - - using (var driver = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings)) - { - Assert.AreEqual(RelayConnectionStatus.NotEstablished, driver.GetRelayConnectionStatus()); - - var retriesLeft = k_RetryCount; - server.SetupForBindRetry(k_RetryCount, () => -- retriesLeft, 0); - - Assert.Zero(driver.Bind(NetworkEndPoint.AnyIpv4)); - driver.ScheduleFlushSend(default).Complete(); - - RelayServerMock.WaitForCondition(() => - { - driver.ScheduleUpdate().Complete(); - return server.IsBound(0); - }); - - Assert.IsTrue(retriesLeft <= 0); - Assert.IsTrue(server.IsBound(0)); - Assert.AreEqual(RelayConnectionStatus.Established, driver.GetRelayConnectionStatus()); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Bind_Fail() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData = server.GetRelayConnectionData(0); - var settings = new NetworkSettings(); - settings.WithRelayParameters(serverData: ref serverData, relayConnectionTimeMS: 10000000); - - using (var driver = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings)) - { - Assert.AreEqual(RelayConnectionStatus.NotEstablished, driver.GetRelayConnectionStatus()); - server.SetupForBindFail(0); - - Assert.Zero(driver.Bind(NetworkEndPoint.AnyIpv4)); - - // One update to send the Bind message out. - driver.ScheduleUpdate().Complete(); - - // Wait long enough for our server to send its error. - Thread.Sleep(250); - - // One update to receive the Error message. - driver.ScheduleUpdate().Complete(); - - Assert.AreEqual(RelayConnectionStatus.AllocationInvalid, driver.GetRelayConnectionStatus()); - LogAssert.Expect(LogType.Error, "Received error message from Relay: allocation ID not found."); - LogAssert.Expect(LogType.Error, - "Relay allocation is invalid. See NetworkDriver.GetRelayConnectionStatus and " + - "RelayConnectionStatus.AllocationInvalid for details on how to handle this situation."); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Listen_Succeed() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData = server.GetRelayConnectionData(0); - var settings = new NetworkSettings(); - settings.WithRelayParameters(ref serverData, 10000000); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings)) - { - Assert.True(server.CompleteBind(host, 0)); - Assert.Zero(host.Listen()); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Connect_Succeed() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData0 = server.GetRelayConnectionData(0); - var serverData1 = server.GetRelayConnectionData(1); - var settings0 = new NetworkSettings(); - settings0.WithRelayParameters(ref serverData0, 10000000); - var settings1 = new NetworkSettings(); - settings1.WithRelayParameters(ref serverData1, 10000000); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings0)) - using (var client = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings1)) - { - Assert.True(server.CompleteBind(host, 0)); - Assert.True(server.CompleteBind(client, 1)); - - Assert.Zero(host.Listen()); - - server.SetupForConnect(1); - - var clientToHost = client.Connect(server.GetRelayConnectionData(0).Endpoint); - - Assert.AreNotEqual(default(NetworkConnection), clientToHost); - - RelayServerMock.WaitForCondition(() => - { - client.ScheduleUpdate(default).Complete(); - host.ScheduleUpdate(default).Complete(); - - return client.GetConnectionState(clientToHost) == NetworkConnection.State.Connected; - }); - - Assert.AreEqual(NetworkConnection.State.Connected, client.GetConnectionState(clientToHost)); - - var evt = client.PopEvent(out var clientToHostConnected, out var reader); - Assert.AreEqual(NetworkEvent.Type.Connect, evt); - Assert.AreEqual(clientToHost, clientToHostConnected); - - Assert.AreEqual(NetworkEvent.Type.Empty, host.PopEvent(out clientToHostConnected, out reader)); - - var hostToClient = host.Accept(); - Assert.AreNotEqual(default(NetworkConnection), hostToClient); - Assert.AreEqual(NetworkConnection.State.Connected, host.GetConnectionState(hostToClient)); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Connect_Retry() - { - const int k_RetryCount = 10; - - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData0 = server.GetRelayConnectionData(0); - var serverData1 = server.GetRelayConnectionData(1); - var settings0 = new NetworkSettings(); - var settings1 = new NetworkSettings(); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), - settings0.WithRelayParameters(ref serverData0, 10000000))) - using (var client = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), - settings1.WithRelayParameters(ref serverData1) - .WithNetworkConfigParameters(connectTimeoutMS: 50))) - { - Assert.True(server.CompleteBind(host, 0)); - Assert.True(server.CompleteBind(client, 1)); - - Assert.Zero(host.Listen()); - - var retriesLeft = k_RetryCount; - server.SetupForConnectRetry(1, k_RetryCount, () => -- retriesLeft); - - var clientToHost = client.Connect(server.GetRelayConnectionData(0).Endpoint); - - Assert.AreNotEqual(default(NetworkConnection), clientToHost); - - RelayServerMock.WaitForCondition(() => - { - client.ScheduleUpdate(default).Complete(); - host.ScheduleUpdate(default).Complete(); - - return client.GetConnectionState(clientToHost) == NetworkConnection.State.Connected; - }); - - Assert.LessOrEqual(0, retriesLeft); - - Assert.AreEqual(NetworkConnection.State.Connected, client.GetConnectionState(clientToHost)); - - var evt = client.PopEvent(out var clientToHostConnected, out var reader); - Assert.AreEqual(NetworkEvent.Type.Connect, evt); - Assert.AreEqual(clientToHost, clientToHostConnected); - - Assert.AreEqual(NetworkEvent.Type.Empty, host.PopEvent(out clientToHostConnected, out reader)); - - var hostToClient = host.Accept(); - Assert.AreNotEqual(default(NetworkConnection), hostToClient); - Assert.AreEqual(NetworkConnection.State.Connected, host.GetConnectionState(hostToClient)); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Disconnect_Succeed() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData0 = server.GetRelayConnectionData(0); - var serverData1 = server.GetRelayConnectionData(1); - var settings0 = new NetworkSettings(); - var settings1 = new NetworkSettings(); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings0.WithRelayParameters(ref serverData0))) - using (var client = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings1.WithRelayParameters(ref serverData1))) - { - Assert.True(server.CompleteConnect(host, out var connections, client)); - var connection = connections[0]; - - server.SetupForDisconnect(1, 0); - - Assert.AreEqual(NetworkConnection.State.Connected, client.GetConnectionState(connection.clientToHost)); - Assert.AreEqual(NetworkConnection.State.Connected, host.GetConnectionState(connection.hostToClient)); - - Assert.Zero(client.Disconnect(connection.clientToHost)); - - RelayServerMock.WaitForCondition(() => - { - client.ScheduleUpdate(default).Complete(); - host.ScheduleUpdate(default).Complete(); - - return host.GetConnectionState(connection.hostToClient) == NetworkConnection.State.Disconnected; - }); - - Assert.AreEqual(NetworkConnection.State.Disconnected, client.GetConnectionState(connection.clientToHost)); - Assert.AreEqual(NetworkConnection.State.Disconnected, host.GetConnectionState(connection.hostToClient)); - - client.ScheduleUpdate().Complete(); - host.ScheduleUpdate().Complete(); - - // That the drivers are both disconnected doesn't mean the relay server has received - // its Disconnect message yet, since it is sent by the client upon receiving the UTP - // Disconnect message. Most of the time this is not an issue since the message is - // delivered very quickly to the relay server mock. But on some slower CI machines, - // the test can end before that happens, so we wait a little to give enough time for - // the Disconnect message to make it. Yes, this is super ugly. - Thread.Sleep(100); - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_Send_Succeed() - { - const int k_PayloadSize = 100; - - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData0 = server.GetRelayConnectionData(0); - var serverData1 = server.GetRelayConnectionData(1); - var settings0 = new NetworkSettings(); - var settings1 = new NetworkSettings(); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings0.WithRelayParameters(ref serverData0, 10000000))) - using (var client = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings1.WithRelayParameters(ref serverData1, 10000000))) - { - Assert.True(server.CompleteConnect(host, out var connections, client)); - - var connection = connections[0]; - - server.SetupForRelay(1, 0, k_PayloadSize + UdpCHeader.Length); - - Assert.Zero(client.BeginSend(connection.clientToHost, out var writer, k_PayloadSize)); - for (int i = 0; i < k_PayloadSize; i++) - { - writer.WriteByte((byte)i); - } - Assert.AreEqual(k_PayloadSize, client.EndSend(writer)); - - client.ScheduleFlushSend(default).Complete(); - - RelayServerMock.WaitForCondition(() => - { - host.ScheduleUpdate(default).Complete(); - return host.GetEventQueueSizeForConnection(connection.hostToClient) > 0; - }); - - Assert.AreEqual(NetworkEvent.Type.Data, host.PopEventForConnection(connection.hostToClient, out var reader)); - - for (int i = 0; i < k_PayloadSize; i++) - { - Assert.AreEqual((byte)i, reader.ReadByte()); - } - } - } - - [Test] - [UnityPlatform(exclude = new[] { RuntimePlatform.OSXEditor, RuntimePlatform.OSXPlayer })] // MTT-3864 - public void RelayNetworkDriver_AllocationTimeOut() - { - using var server = new RelayServerMock("127.0.0.1", m_port++); - - var serverData0 = server.GetRelayConnectionData(0); - var serverData1 = server.GetRelayConnectionData(1); - var settings0 = new NetworkSettings(); - settings0.WithRelayParameters(ref serverData0, 10000000); - var settings1 = new NetworkSettings(); - settings1.WithRelayParameters(ref serverData1, 10000000); - - using (var host = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings0)) - using (var client = new NetworkDriver(new BaselibNetworkInterface(), new RelayNetworkProtocol(), settings1)) - { - Assert.True(server.CompleteBind(host, 0)); - Assert.True(server.CompleteBind(client, 1)); - - Assert.Zero(host.Listen()); - - server.SetupForConnectTimeout(1); - - var clientToHost = client.Connect(server.GetRelayConnectionData(0).Endpoint); - - RelayServerMock.WaitForCondition(() => - { - client.ScheduleUpdate(default).Complete(); - host.ScheduleUpdate(default).Complete(); - - return client.GetRelayConnectionStatus() == RelayConnectionStatus.AllocationInvalid; - }, timeout: 250); - - Assert.AreEqual(RelayConnectionStatus.AllocationInvalid, client.GetRelayConnectionStatus()); - LogAssert.Expect(LogType.Error, "Received error message from Relay: player timed out due to inactivity."); - LogAssert.Expect(LogType.Error, - "Relay allocation is invalid. See NetworkDriver.GetRelayConnectionStatus and " + - "RelayConnectionStatus.AllocationInvalid for details on how to handle this situation."); - } - } - } -} diff --git a/Tests/Editor/RelayNetworkDriverTests.cs.meta b/Tests/Editor/RelayNetworkDriverTests.cs.meta deleted file mode 100644 index cdf2a56..0000000 --- a/Tests/Editor/RelayNetworkDriverTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 275df17011d7eb845bf025820122e637 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/ReliablePipelineTests.cs b/Tests/Editor/ReliablePipelineTests.cs deleted file mode 100644 index 5dc09dd..0000000 --- a/Tests/Editor/ReliablePipelineTests.cs +++ /dev/null @@ -1,2176 +0,0 @@ -using System; -using AOT; -using NUnit.Framework; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Networking.Transport.Utilities; -using UnityEngine; -using Unity.Burst; - -namespace Unity.Networking.Transport.Tests -{ - [BurstCompile] - public unsafe struct TempDisconnectPipelineStage : INetworkPipelineStage - { - public static byte* s_StaticInstanceBuffer; - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - s_StaticInstanceBuffer = staticInstanceBuffer; - *staticInstanceBuffer = 1; - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: 0, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 1; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - if (*ctx.staticInstanceBuffer == 0) - { - inboundBuffer = default; - } - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - [BurstCompile] - public unsafe struct TempDisconnectSendPipelineStage : INetworkPipelineStage - { - public static byte* s_StaticInstanceBuffer; - static TransportFunctionPointer ReceiveFunctionPointer = new TransportFunctionPointer(Receive); - static TransportFunctionPointer SendFunctionPointer = new TransportFunctionPointer(Send); - static TransportFunctionPointer InitializeConnectionFunctionPointer = new TransportFunctionPointer(InitializeConnection); - public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, int staticInstanceBufferLength, NetworkSettings settings) - { - s_StaticInstanceBuffer = staticInstanceBuffer; - *staticInstanceBuffer = 1; - return new NetworkPipelineStage( - Receive: ReceiveFunctionPointer, - Send: SendFunctionPointer, - InitializeConnection: InitializeConnectionFunctionPointer, - ReceiveCapacity: 0, - SendCapacity: 0, - HeaderCapacity: 0, - SharedStateCapacity: 0 - ); - } - - public int StaticSize => 1; - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))] - private static void Receive(ref NetworkPipelineContext ctx, ref InboundRecvBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))] - private static int Send(ref NetworkPipelineContext ctx, ref InboundSendBuffer inboundBuffer, ref NetworkPipelineStage.Requests request, int systemHeaderSize) - { - if (*ctx.staticInstanceBuffer == 0) - { - inboundBuffer = default; - } - return (int)Error.StatusCode.Success; - } - - [BurstCompile(DisableDirectCall = true)] - [MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))] - private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength, - byte* sendProcessBuffer, int sendProcessBufferLength, byte* recvProcessBuffer, int recvProcessBufferLength, - byte* sharedProcessBuffer, int sharedProcessBufferLength) - { - } - } - - public struct TempDisconnectPipelineStageCollection - { - public static void Register() - { - NetworkPipelineStageCollection.RegisterPipelineStage(new TempDisconnectPipelineStage()); - NetworkPipelineStageCollection.RegisterPipelineStage(new TempDisconnectSendPipelineStage()); - } - } - public class ReliablePipelineTests - { - [Test] - public unsafe void ReliableUtility_ValidationScenarios() - { - // Receive a Packet Newer still gapped. [0, 1, Lost, 3, 4] - // Massage the resend flow using the Received Mask. [0, 1, Resend, 3, 4] - // Receive the missing packet '2' and massage the receive flow - - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 32 - }; - - var processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - var sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - - - // ep1 - var ep1SharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - var ep1SendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - var ep1RecvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - - // ep2 - var ep2SharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - var ep2SendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - var ep2RecvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - - // packet - var packet = new NativeArray(UnsafeUtility.SizeOf(), Allocator.Persistent); - packet[0] = 100; - - var header = new DataStreamWriter(UnsafeUtility.SizeOf(), Allocator.Temp); - - ReliableSequencedPipelineStage ep1Owner = new ReliableSequencedPipelineStage(); - ReliableSequencedPipelineStage ep2Owner = new ReliableSequencedPipelineStage(); - - var ep1Buffer = new NativeArray(ep1Owner.StaticSize, Allocator.Persistent); - var ep2Buffer = new NativeArray(ep2Owner.StaticSize, Allocator.Persistent); - - var paramList = new NetworkSettings(); - paramList.AddRawParameterStruct(ref parameters); - var ep1 = ep1Owner.StaticInitialize((byte*)ep1Buffer.GetUnsafePtr(), ep1Buffer.Length, paramList); - var ep2 = ep1Owner.StaticInitialize((byte*)ep2Buffer.GetUnsafePtr(), ep2Buffer.Length, paramList); - ep1.InitializeConnection.Ptr.Invoke((byte*)ep1Buffer.GetUnsafePtr(), ep1Buffer.Length, - (byte*)ep1SendBuffer.GetUnsafePtr(), ep1SendBuffer.Length, (byte*)ep1RecvBuffer.GetUnsafePtr(), ep1RecvBuffer.Length, - (byte*)ep1SharedBuffer.GetUnsafePtr(), ep1SharedBuffer.Length); - ep2.InitializeConnection.Ptr.Invoke((byte*)ep2Buffer.GetUnsafePtr(), ep2Buffer.Length, - (byte*)ep2SendBuffer.GetUnsafePtr(), ep2SendBuffer.Length, (byte*)ep2RecvBuffer.GetUnsafePtr(), ep2RecvBuffer.Length, - (byte*)ep2SharedBuffer.GetUnsafePtr(), ep2SharedBuffer.Length); - - var ep1sendContext = (ReliableUtility.Context*)ep1SendBuffer.GetUnsafePtr(); - //var ep1recvContext = (ReliableUtility.Context*) ep1RecvBuffer.GetUnsafePtr(); - //var ep1sharedContext = (ReliableUtility.SharedContext*) ep1SharedBuffer.GetUnsafePtr(); - - var ep2recvContext = (ReliableUtility.Context*)ep2RecvBuffer.GetUnsafePtr(); - //var ep2sendContext = (ReliableUtility.Context*) ep2SendBuffer.GetUnsafePtr(); - //var ep2sharedContext = (ReliableUtility.SharedContext*) ep2SharedBuffer.GetUnsafePtr(); - - // Send a Packet - Receive a Packet - var currentId = 0; - - var inboundSend = default(InboundSendBuffer); - inboundSend.buffer = (byte*)packet.GetUnsafePtr(); - inboundSend.bufferLength = packet.Length; - inboundSend.bufferWithHeaders = (byte*)packet.GetUnsafePtr(); - inboundSend.bufferWithHeadersLength = packet.Length; - - NetworkPipelineStage.Requests stageRequest = NetworkPipelineStage.Requests.None; - var slice = default(InboundRecvBuffer); - var output = default(InboundSendBuffer); - { - var ctx = new NetworkPipelineContext - { - header = header, - internalProcessBuffer = (byte*)ep1SendBuffer.GetUnsafePtr(), - internalProcessBufferLength = ep1SendBuffer.Length, - internalSharedProcessBuffer = (byte*)ep1SharedBuffer.GetUnsafePtr(), - internalSharedProcessBufferLength = ep1SharedBuffer.Length, - staticInstanceBuffer = (byte*)ep1Buffer.GetUnsafePtr(), - staticInstanceBufferLength = ep1Buffer.Length - }; - output = inboundSend; - ep1.Send.Ptr.Invoke(ref ctx, ref output, ref stageRequest, 0); - Assert.True(output.buffer[0] == packet[0]); - Assert.True((stageRequest& NetworkPipelineStage.Requests.Resume) == 0); - } - { - var info = ReliableUtility.GetPacketInformation((byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr(), currentId); - var offset = ep1sendContext->DataPtrOffset; // + (index * ctx->DataStride); - InboundRecvBuffer data; - data.buffer = (byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr() + offset; - data.bufferLength = info->Size; - - var ctx = new NetworkPipelineContext - { - internalProcessBuffer = (byte*)ep2RecvBuffer.GetUnsafePtr(), - internalProcessBufferLength = ep2RecvBuffer.Length, - internalSharedProcessBuffer = (byte*)ep2SharedBuffer.GetUnsafePtr(), - internalSharedProcessBufferLength = ep2SharedBuffer.Length, - staticInstanceBuffer = (byte*)ep2Buffer.GetUnsafePtr(), - staticInstanceBufferLength = ep2Buffer.Length - }; - slice = data; - ep2.Receive.Ptr.Invoke(ref ctx, ref slice, ref stageRequest, 0); - - if (slice.bufferLength > 0) - Assert.True(slice.buffer[0] == packet[0]); - } - Assert.True((stageRequest& NetworkPipelineStage.Requests.Resume) == 0); - Assert.True(ep2recvContext->Delivered == currentId); - - // Scenario: Receive a Packet Newer then expected [0, 1, Lost, 3] - - // Start by "sending" 1, 2, 3; - for (int seq = currentId + 1; seq < 4; seq++) - { - packet[0] = (byte)(100 + seq); - - header.Clear(); - var ctx = new NetworkPipelineContext - { - header = header, - internalProcessBuffer = (byte*)ep1SendBuffer.GetUnsafePtr(), - internalProcessBufferLength = ep1SendBuffer.Length, - internalSharedProcessBuffer = (byte*)ep1SharedBuffer.GetUnsafePtr(), - internalSharedProcessBufferLength = ep1SharedBuffer.Length, - staticInstanceBuffer = (byte*)ep1Buffer.GetUnsafePtr(), - staticInstanceBufferLength = ep1Buffer.Length - }; - output = inboundSend; - ep1.Send.Ptr.Invoke(ref ctx, ref output, ref stageRequest, 0); - - Assert.True((stageRequest& NetworkPipelineStage.Requests.Resume) == 0); - Assert.True(output.buffer[0] == packet[0]); - } - - for (int seq = currentId + 1; seq < 4; seq++) - { - if (seq == 2) - continue; - - var info = ReliableUtility.GetPacketInformation((byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr(), seq); - var offset = ep1sendContext->DataPtrOffset + ((seq % ep1sendContext->Capacity) * ep1sendContext->DataStride); - var inspectPacket = ReliableUtility.GetPacket((byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr(), seq); - - InboundRecvBuffer data; - data.buffer = (byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr() + offset; - data.bufferLength = info->Size; - Assert.True(inspectPacket->Header.SequenceId == info->SequenceId); - - header.Clear(); - var ctx = new NetworkPipelineContext - { - header = header, - internalProcessBuffer = (byte*)ep2RecvBuffer.GetUnsafePtr(), - internalProcessBufferLength = ep2RecvBuffer.Length, - internalSharedProcessBuffer = (byte*)ep2SharedBuffer.GetUnsafePtr(), - internalSharedProcessBufferLength = ep2SharedBuffer.Length, - staticInstanceBuffer = (byte*)ep2Buffer.GetUnsafePtr(), - staticInstanceBufferLength = ep2Buffer.Length - }; - stageRequest = NetworkPipelineStage.Requests.None; - slice = data; - ep2.Receive.Ptr.Invoke(ref ctx, ref slice, ref stageRequest, 0); - - if (slice.bufferLength > 0) - { - Assert.True(slice.buffer[0] == seq + 100); - } - } - - // Receive packet number 2 and resume received packets. - bool first = true; - do - { - var data = default(InboundRecvBuffer); - if (first) - { - var seq = 2; - var info = ReliableUtility.GetPacketInformation((byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr(), seq); - var offset = ep1sendContext->DataPtrOffset + - ((seq % ep1sendContext->Capacity) * ep1sendContext->DataStride); - var inspectPacket = ReliableUtility.GetPacket((byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr(), seq); - - data.buffer = (byte*)ep1SendBuffer.GetUnsafeReadOnlyPtr() + offset; - data.bufferLength = info->Size; - Assert.True(inspectPacket->Header.SequenceId == info->SequenceId); - - first = false; - } - - var ctx = new NetworkPipelineContext - { - internalProcessBuffer = (byte*)ep2RecvBuffer.GetUnsafeReadOnlyPtr(), - internalProcessBufferLength = ep2RecvBuffer.Length, - internalSharedProcessBuffer = (byte*)ep2SharedBuffer.GetUnsafeReadOnlyPtr(), - internalSharedProcessBufferLength = ep2SharedBuffer.Length - }; - slice = data; - ep2.Receive.Ptr.Invoke(ref ctx, ref slice, ref stageRequest, 0); - - if (slice.bufferLength > 0) - { - Assert.True(slice.buffer[0] == ep2recvContext->Delivered + 100); - } - } - while ((stageRequest & NetworkPipelineStage.Requests.Resume) != 0); - - - packet.Dispose(); - ep1SharedBuffer.Dispose(); - ep1SendBuffer.Dispose(); - ep1RecvBuffer.Dispose(); - ep2SharedBuffer.Dispose(); - ep2SendBuffer.Dispose(); - ep2RecvBuffer.Dispose(); - ep1Buffer.Dispose(); - ep2Buffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_Validation() - { - int capacity = 5; - NativeArray buffer = new NativeArray(1, Allocator.Persistent); - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = capacity - }; - - int result = ReliableUtility.ProcessCapacityNeeded(parameters); - NativeArray processBuffer = new NativeArray(result, Allocator.Persistent); - - var processBufferPtr = (byte*)processBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeProcessContext(processBufferPtr, processBuffer.Length, parameters); - - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 0)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 1)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 2)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 3)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 4)); - Assert.IsFalse(ReliableUtility.TryAquire(processBufferPtr, 5)); - - ReliableUtility.Release(processBufferPtr, 0, 5); - - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 0)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 1)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 2)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 3)); - Assert.IsTrue(ReliableUtility.TryAquire(processBufferPtr, 4)); - - buffer[0] = (byte)(1); - - ReliableUtility.SetPacket(processBufferPtr, 0, (byte*)buffer.GetUnsafeReadOnlyPtr(), buffer.Length); - - - var slice = ReliableUtility.GetPacket(processBufferPtr, 0); - Assert.IsTrue(slice->Buffer[0] == buffer[0]); - - for (int i = 0; i < capacity * 5; i++) - { - ReliableUtility.SetPacket(processBufferPtr, i, (byte*)buffer.GetUnsafeReadOnlyPtr(), buffer.Length); - slice = ReliableUtility.GetPacket(processBufferPtr, i); - Assert.IsTrue(slice->Buffer[0] == buffer[0]); - } - ReliableUtility.Release(processBufferPtr, 0, 5); - - processBuffer.Dispose(); - buffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_SeqIdBeginAt0() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length, timestamp = 1000 - }; - - // Sending seqId 3, last received ID 0 (1 is not yet acked, 2 was dropped) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 0; // Last sent is initialized to what you are sending next - sharedContext->SentPackets.Acked = -1; - sharedContext->SentPackets.AckMask = 0x1; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 65535, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 65535)->SendTime = 980; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 65535, 980); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 65535, 990, 16); - stream.Clear(); - stream.WriteInt((int)11); - ReliableUtility.SetPacket(sendBufferPtr, 0, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 0)->SendTime = 990; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 0, 990); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, 65535 should be released, 0 should still be there - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 65535)->SequenceId); - Assert.AreEqual(0, ReliableUtility.GetPacketInformation(sendBufferPtr, 0)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_SeqIdWrap1() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length, timestamp = 1000 - }; - - // Sending seqId 3, last received ID 2 (same as last sent packet) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 3; - sharedContext->SentPackets.Acked = 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 1, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 1)->SendTime = 980; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 1, 980); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 1, 990, 16); - stream.Clear(); - stream.WriteInt((int)11); - ReliableUtility.SetPacket(sendBufferPtr, 2, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 2)->SendTime = 990; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 2, 990); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 2, 1000, 16); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, both packets should be released - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 1)->SequenceId); - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 2)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_SeqIdWrap2() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - // Sending seqId 3, last received ID 65535 (same as last sent) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 0; - sharedContext->SentPackets.Acked = 65535; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 65535, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 65535)->SendTime = 980; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 65535, 980); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 65535, 990, 16); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, 65535 should be released - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 65535)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_SeqIdWrap3() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - // Sending seqId 3, last received ID 0 (1 is not yet acked, 2 was dropped) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 17; - sharedContext->SentPackets.Acked = 16; - sharedContext->SentPackets.AckMask = 0xFFFFDBB7; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 16, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, packet 16 should be released - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 16)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_ReleaseSlotWithWrappedSeqId() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - // Sending seqId 3, last received ID 0 (1 is not yet acked, 2 was dropped) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 1; - sharedContext->SentPackets.Acked = 0; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 0, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)11); - ReliableUtility.SetPacket(sendBufferPtr, 65535, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, slot with seqId 0 and 65535 should have been released - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 0)->SequenceId); - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 65535)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_AckMaskShiftsProperly1() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length, timestamp = 1000 - }; - - // Sending seqId 3, last received ID 0 (1 is not yet acked, 2 was dropped) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 4; - sharedContext->SentPackets.Acked = 3; - sharedContext->SentPackets.AckMask = 0xFFFFFFFD; // bit 0 = seqId 3 (1), bit 1 = seqId 2 (0) - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - // SeqId 3 is received and ready to be released - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 3, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 3)->SendTime = 990; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 3, 980); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 3, 990, 16); - stream.Clear(); - // SeqId 2 is not yet received so it should stick around - stream.WriteInt((int)11); - ReliableUtility.SetPacket(sendBufferPtr, 2, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 2)->SendTime = 1000; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 2, 1000); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, packet 3 should be released (has been acked), 2 should stick around - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 3)->SequenceId); - Assert.AreEqual(2, ReliableUtility.GetPacketInformation(sendBufferPtr, 2)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_AckPackets_AckMaskShiftsProperly2() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length, timestamp = 1000 - }; - - // Sending seqId 3, last received ID 0 (1 is not yet acked, 2 was dropped) - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 5; - sharedContext->SentPackets.Acked = 4; - sharedContext->SentPackets.AckMask = 0xFFFFFFFD; // bit 0 = seqId 4 (1), bit 1 = seqId 3 (0) - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers in resend queue - // SeqId 4 is received and ready to be released - stream.WriteInt((int)10); - ReliableUtility.SetPacket(sendBufferPtr, 4, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 4)->SendTime = 980; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 4, 980); - ReliableUtility.StoreReceiveTimestamp(pipelineContext.internalSharedProcessBuffer, 4, 990, 16); - stream.Clear(); - stream.WriteInt((int)11); - ReliableUtility.SetPacket(sendBufferPtr, 3, (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - ReliableUtility.GetPacketInformation(sendBufferPtr, 3)->SendTime = 1000; - ReliableUtility.StoreTimestamp(pipelineContext.internalSharedProcessBuffer, 3, 1000); - - ReliableUtility.ReleaseOrResumePackets(pipelineContext); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - - // Validate that packet tracking state is correct, packet 3 should be released (has been acked), 2 should stick around - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 4)->SequenceId); - Assert.AreEqual(3, ReliableUtility.GetPacketInformation(sendBufferPtr, 3)->SequenceId); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void ReliableUtility_TimestampHandling() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 3 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray ep1RecvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray ep1SendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray ep1SharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - NativeArray ep2RecvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray ep2SendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray ep2SharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - ReliableUtility.InitializeContext((byte*)ep1SharedBuffer.GetUnsafePtr(), ep1SharedBuffer.Length, - (byte*)ep1SendBuffer.GetUnsafePtr(), ep1SendBuffer.Length, - (byte*)ep1RecvBuffer.GetUnsafePtr(), ep1RecvBuffer.Length, parameters); - ReliableUtility.InitializeContext((byte*)ep2SharedBuffer.GetUnsafePtr(), ep2SharedBuffer.Length, - (byte*)ep2SendBuffer.GetUnsafePtr(), ep2SendBuffer.Length, - (byte*)ep2RecvBuffer.GetUnsafePtr(), ep2RecvBuffer.Length, parameters); - - // When sending we store the send timestamp of the sequence ID (EP1 -> EP2) - ushort ep1SeqId = 10; - ReliableUtility.StoreTimestamp((byte*)ep1SharedBuffer.GetUnsafePtr(), ep1SeqId, 900); - - // EP2 also sends something to EP1 - ushort ep2SeqId = 100; - ReliableUtility.StoreTimestamp((byte*)ep2SharedBuffer.GetUnsafePtr(), ep2SeqId, 910); - - // When EP2 receives the packet the receive time is stored - ReliableUtility.StoreRemoteReceiveTimestamp((byte*)ep2SharedBuffer.GetUnsafePtr(), ep1SeqId, 920); - - // EP2 also stores the timing information in the EP1 packet (processing time for the packet it sent earlier) - ReliableUtility.StoreReceiveTimestamp((byte*)ep2SharedBuffer.GetUnsafePtr(), ep2SeqId, 920, 10); - - // When EP2 sends another packet to EP1 it calculates ep1SeqId processing time - int processTime = ReliableUtility.CalculateProcessingTime((byte*)ep2SharedBuffer.GetUnsafePtr(), ep1SeqId, 930); - - // ep1SeqId processing time should be 10 ms (930 - 920) - Assert.AreEqual(10, processTime); - - // Verify information written so far (send/receive times + processing time) - var timerData = ReliableUtility.GetLocalPacketTimer((byte*)ep2SharedBuffer.GetUnsafePtr(), ep2SeqId); - Assert.IsTrue(timerData != null, "Packet timing data not found"); - Assert.AreEqual(ep2SeqId, timerData->SequenceId); - Assert.AreEqual(10, timerData->ProcessingTime); - Assert.AreEqual(910, timerData->SentTime); - Assert.AreEqual(920, timerData->ReceiveTime); - - var ep2SharedCtx = (ReliableUtility.SharedContext*)ep2SharedBuffer.GetUnsafePtr(); - Debug.Log("LastRtt=" + ep2SharedCtx->RttInfo.LastRtt); - Debug.Log("SmoothedRTT=" + ep2SharedCtx->RttInfo.SmoothedRtt); - Debug.Log("ResendTimeout=" + ep2SharedCtx->RttInfo.ResendTimeout); - Debug.Log("SmoothedVariance=" + ep2SharedCtx->RttInfo.SmoothedVariance); - - ep1RecvBuffer.Dispose(); - ep1SendBuffer.Dispose(); - ep1SharedBuffer.Dispose(); - ep2RecvBuffer.Dispose(); - ep2SendBuffer.Dispose(); - ep2SharedBuffer.Dispose(); - } - - [Test] - public unsafe void Receive_ResumesMultipleStoredPacketsAroundWrapPoint1() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var recvBufferPtr = (byte*)recvBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - (byte*)sendBuffer.GetUnsafePtr(), sendBuffer.Length, recvBufferPtr, recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = recvBufferPtr, internalProcessBufferLength = recvBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 3; // what was last sent doesn't matter here - sharedContext->SentPackets.Acked = 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFF7; // bit 0,1,2 maps to seqId 2,1,0 all delivered, bit 3 is seqId 65535 which is not yet delivered - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = 65534; // latest in sequence delivered packet, one less than what unclogs the packet jam - - var reliablePipelineStage = new ReliableSequencedPipelineStage(); - var staticBuffer = new NativeArray(reliablePipelineStage.StaticSize, Allocator.Temp); - pipelineContext.staticInstanceBuffer = (byte*)staticBuffer.GetUnsafePtr(); - pipelineContext.staticInstanceBufferLength = staticBuffer.Length; - var reliablePipeline = reliablePipelineStage.StaticInitialize((byte*)staticBuffer.GetUnsafePtr(), staticBuffer.Length, new NetworkSettings()); - - var stream = new DataStreamWriter(4, Allocator.Temp); - var inboundStream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers to receive queue, packets which should be resume received after packet jam is unclogged - stream.Clear(); - stream.WriteInt((int)100); - ReliableUtility.SetPacket(recvBufferPtr, 0, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)200); - ReliableUtility.SetPacket(recvBufferPtr, 1, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket(recvBufferPtr, 2, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - // Generate the packet which will be handled in receive - InboundRecvBuffer packet = default; - GeneratePacket(9000, 2, 0xFFFFFFFF, 65535, ref sendBuffer, out packet); - - // Process 65535, 0 should then be next in line on the resume field - var stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(0, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 0, after that 1 is up - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(1, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 1, after that 2 is up - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(2, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 2, and we are done - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(-1, receiveContext->Resume); - Assert.AreEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void Receive_ResumesMultipleStoredPacketsAroundWrapPoint2() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var recvBufferPtr = (byte*)recvBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - (byte*)sendBuffer.GetUnsafePtr(), sendBuffer.Length, recvBufferPtr, recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = recvBufferPtr, internalProcessBufferLength = recvBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 2; // what was last sent doesn't matter here - sharedContext->SentPackets.Acked = 1; - sharedContext->SentPackets.AckMask = 0xFFFFFFF7; // bit 0,1,2 maps to seqId 1,0,65535 all delivered, bit 3 is seqId 65534 which is not yet delivered - sharedContext->ReceivedPackets.Sequence = 1; - sharedContext->ReceivedPackets.AckMask = 0xFFFFFFF7; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = 65533; // latest in sequence delivered packet, one less than what unclogs the packet jam - - var reliablePipelineStage = new ReliableSequencedPipelineStage(); - var staticBuffer = new NativeArray(reliablePipelineStage.StaticSize, Allocator.Temp); - pipelineContext.staticInstanceBuffer = (byte*)staticBuffer.GetUnsafePtr(); - pipelineContext.staticInstanceBufferLength = staticBuffer.Length; - var reliablePipeline = reliablePipelineStage.StaticInitialize((byte*)staticBuffer.GetUnsafePtr(), staticBuffer.Length, new NetworkSettings()); - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers to receive queue, packets which should be resume received after packet jam is unclogged - stream.Clear(); - stream.WriteInt((int)100); - ReliableUtility.SetPacket(recvBufferPtr, 65535, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)200); - ReliableUtility.SetPacket(recvBufferPtr, 0, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket(recvBufferPtr, 1, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - // Generate the packet which will be handled in receive - InboundRecvBuffer packet = default; - GeneratePacket(9000, 65533, 0xFFFFFFFF, 65534, ref sendBuffer, out packet); - - // Process 65534, 65535 should then be next in line on the resume field - var stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(65535, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 65535, after that 0 is up - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(0, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 0, after that 1 is up - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(1, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - // Process 1, and we are done - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(-1, receiveContext->Resume); - Assert.AreEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void Receive_ResumesMultipleStoredPacketsAndSetsAckedAckMaskProperly() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 10 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var recvBufferPtr = (byte*)recvBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - (byte*)sendBuffer.GetUnsafePtr(), sendBuffer.Length, recvBufferPtr, recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = recvBufferPtr, internalProcessBufferLength = recvBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 99; // what was last sent doesn't matter here - sharedContext->SentPackets.Acked = 97; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = 98; - sharedContext->ReceivedPackets.AckMask = 0xFFFFFFF7; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = 94; // latest in sequence delivered packet, one less than what unclogs the packet jam - - var reliablePipelineStage = new ReliableSequencedPipelineStage(); - var staticBuffer = new NativeArray(reliablePipelineStage.StaticSize, Allocator.Temp); - pipelineContext.staticInstanceBuffer = (byte*)staticBuffer.GetUnsafePtr(); - pipelineContext.staticInstanceBufferLength = staticBuffer.Length; - var reliablePipeline = reliablePipelineStage.StaticInitialize((byte*)staticBuffer.GetUnsafePtr(), staticBuffer.Length, new NetworkSettings()); - - var stream = new DataStreamWriter(4, Allocator.Temp); - { - // Add buffers to receive queue, packets which should be resume received after packet jam is unclogged - stream.Clear(); - stream.WriteInt((int)200); - ReliableUtility.SetPacket(recvBufferPtr, 96, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket(recvBufferPtr, 97, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket(recvBufferPtr, 98, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - InboundRecvBuffer packet = default; - GeneratePacket(9000, 98, 0xFFFFFFFF, 99, ref sendBuffer, out packet); - - // Receive 99, it's out of order so should be queued for later (waiting for 95) - var stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(-1, receiveContext->Resume); - Assert.AreEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - - GeneratePacket(10000, 98, 0xFFFFFFFF, 95, ref sendBuffer, out packet); - - // First 95 is received and then receive resume runs up to 99 - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(96, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(97, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(98, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(99, receiveContext->Resume); - Assert.AreNotEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Receive.Ptr.Invoke(ref pipelineContext, ref packet, ref stageRequest, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, sharedContext->errorCode); - Assert.AreEqual(-1, receiveContext->Resume); - Assert.AreEqual(NetworkPipelineStage.Requests.None, stageRequest& NetworkPipelineStage.Requests.Resume); - - // Verify that the ReceivePackets state is correct, 99 should be latest received and ackmask 0xFFFFF - Assert.AreEqual(99, sharedContext->ReceivedPackets.Sequence); - Assert.AreEqual(0xFFFFFFFF, sharedContext->ReceivedPackets.AckMask); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void Send_PacketsAreAcked_SendingPacket() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 3 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - var sendBufferPtr = (byte*)sendBuffer.GetUnsafePtr(); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - sendBufferPtr, sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = sendBufferPtr, internalProcessBufferLength = recvBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length - }; - - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 3; - sharedContext->SentPackets.Acked = 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = 2; - sharedContext->ReceivedPackets.AckMask = 0xFFFFFFFF; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = 1; - - var reliablePipelineStage = new ReliableSequencedPipelineStage(); - var staticBuffer = new NativeArray(reliablePipelineStage.StaticSize, Allocator.Temp); - pipelineContext.staticInstanceBuffer = (byte*)staticBuffer.GetUnsafePtr(); - pipelineContext.staticInstanceBufferLength = staticBuffer.Length; - var reliablePipeline = reliablePipelineStage.StaticInitialize((byte*)staticBuffer.GetUnsafePtr(), staticBuffer.Length, new NetworkSettings()); - - var stream = new DataStreamWriter(4, Allocator.Temp); - pipelineContext.header = new DataStreamWriter(UnsafeUtility.SizeOf(), Allocator.Temp); - { - // Fill window capacity, next send should then clear everything - stream.Clear(); - stream.WriteInt((int)100); - ReliableUtility.SetPacket(sendBufferPtr, 0, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)200); - ReliableUtility.SetPacket(sendBufferPtr, 1, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket(sendBufferPtr, 2, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - // Set input buffer and send, this will be seqId 3 - stream.Clear(); - stream.WriteInt((int)9000); - var inboundBuffer = new InboundSendBuffer(); - inboundBuffer.bufferWithHeaders = (byte*)stream.AsNativeArray().GetUnsafeReadOnlyPtr(); - inboundBuffer.bufferWithHeadersLength = stream.Length; - inboundBuffer.SetBufferFrombufferWithHeaders(); - - var stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Send.Ptr.Invoke(ref pipelineContext, ref inboundBuffer, ref stageRequest, 0); - - // seqId 3 should now be stored in slot 0 - Assert.AreEqual(3, ReliableUtility.GetPacketInformation(sendBufferPtr, 3)->SequenceId); - - // slots 1 and 2 should be cleared - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 1)->SequenceId); - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation(sendBufferPtr, 2)->SequenceId); - - Assert.AreEqual(NetworkPipelineStage.Requests.Update, stageRequest); - - // Verify ack packet is written correctly - ReliableUtility.PacketHeader header = default; - ReliableUtility.WriteAckPacket(pipelineContext, ref header); - Assert.AreEqual(header.AckedSequenceId, 2); - Assert.AreEqual(header.AckMask, 0xFFFFFFFF); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - [Test] - public unsafe void Send_PacketsAreAcked_UpdateAckState() - { - ReliableUtility.Parameters parameters = new ReliableUtility.Parameters - { - WindowSize = 3 - }; - - int processCapacity = ReliableUtility.ProcessCapacityNeeded(parameters); - int sharedCapacity = ReliableUtility.SharedCapacityNeeded(parameters); - NativeArray recvBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sendBuffer = new NativeArray(processCapacity, Allocator.Persistent); - NativeArray sharedBuffer = new NativeArray(sharedCapacity, Allocator.Persistent); - - ReliableUtility.InitializeContext((byte*)sharedBuffer.GetUnsafePtr(), sharedBuffer.Length, - (byte*)sendBuffer.GetUnsafePtr(), sendBuffer.Length, (byte*)recvBuffer.GetUnsafePtr(), recvBuffer.Length, parameters); - - var pipelineContext = new NetworkPipelineContext - { - internalProcessBuffer = (byte*)sendBuffer.GetUnsafePtr(), internalProcessBufferLength = sendBuffer.Length, - internalSharedProcessBuffer = (byte*)sharedBuffer.GetUnsafePtr(), internalSharedProcessBufferLength = sharedBuffer.Length, timestamp = 1000 - }; - - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = 3; - sharedContext->SentPackets.Acked = 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = 2; - sharedContext->ReceivedPackets.AckMask = 0xFFFFFFFF; - var receiveContext = (ReliableUtility.Context*)recvBuffer.GetUnsafePtr(); - receiveContext->Delivered = 1; - - // Set last send time to something a long time ago so the ack state is sent in Send - var sendContext = (ReliableUtility.Context*)sendBuffer.GetUnsafePtr(); - sendContext->LastSentTime = 500; - sendContext->PreviousTimestamp = 980; // 20 ms ago - - var reliablePipelineStage = new ReliableSequencedPipelineStage(); - var staticBuffer = new NativeArray(reliablePipelineStage.StaticSize, Allocator.Temp); - pipelineContext.staticInstanceBuffer = (byte*)staticBuffer.GetUnsafeReadOnlyPtr(); - pipelineContext.staticInstanceBufferLength = staticBuffer.Length; - var reliablePipeline = reliablePipelineStage.StaticInitialize((byte*)staticBuffer.GetUnsafeReadOnlyPtr(), staticBuffer.Length, new NetworkSettings()); - - var stream = new DataStreamWriter(4, Allocator.Temp); - pipelineContext.header = new DataStreamWriter(UnsafeUtility.SizeOf(), Allocator.Temp); - { - // Fill window capacity, next send should then clear everything - stream.Clear(); - stream.WriteInt((int)100); - ReliableUtility.SetPacket((byte*)sendBuffer.GetUnsafePtr(), 0, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)200); - ReliableUtility.SetPacket((byte*)sendBuffer.GetUnsafePtr(), 1, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - stream.Clear(); - stream.WriteInt((int)300); - ReliableUtility.SetPacket((byte*)sendBuffer.GetUnsafePtr(), 2, stream.AsNativeArray().GetUnsafeReadOnlyPtr(), stream.Length); - - var inboundBuffer = new InboundSendBuffer(); - - var stageRequest = NetworkPipelineStage.Requests.None; - reliablePipeline.Send.Ptr.Invoke(ref pipelineContext, ref inboundBuffer, ref stageRequest, 0); - - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation((byte*)sendBuffer.GetUnsafeReadOnlyPtr(), 0)->SequenceId); - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation((byte*)sendBuffer.GetUnsafeReadOnlyPtr(), 1)->SequenceId); - Assert.AreEqual(-1, ReliableUtility.GetPacketInformation((byte*)sendBuffer.GetUnsafeReadOnlyPtr(), 2)->SequenceId); - - Assert.AreEqual(NetworkPipelineStage.Requests.Update, stageRequest); - } - recvBuffer.Dispose(); - sendBuffer.Dispose(); - sharedBuffer.Dispose(); - } - - unsafe void GeneratePacket(int payload, ushort headerAckedId, uint headerAckMask, ushort headerSeqId, ref NativeArray sendBuffer, out InboundRecvBuffer packet) - { - DataStreamWriter inboundStream = new DataStreamWriter(4, Allocator.Temp); - - inboundStream.WriteInt((int)payload); - InboundSendBuffer data = default; - data.bufferWithHeaders = (byte*)inboundStream.AsNativeArray().GetUnsafePtr(); - data.bufferWithHeadersLength = inboundStream.Length; - data.SetBufferFrombufferWithHeaders(); - ReliableUtility.PacketHeader header = new ReliableUtility.PacketHeader() - { - AckedSequenceId = headerAckedId, - AckMask = headerAckMask, - SequenceId = headerSeqId - }; - ReliableUtility.SetHeaderAndPacket((byte*)sendBuffer.GetUnsafePtr(), headerSeqId, header, data, 1000); - - // Extract raw packet from the send buffer so it can be passed directly to receive - var sendCtx = (ReliableUtility.Context*)sendBuffer.GetUnsafePtr(); - var index = headerSeqId % sendCtx->Capacity; - var offset = sendCtx->DataPtrOffset + (index * sendCtx->DataStride); - packet.buffer = (byte*)sendBuffer.GetUnsafeReadOnlyPtr() + offset; - packet.bufferLength = sendCtx->DataStride; - } - } - - public class QoSNetworkPipelineTest - { - private NetworkDriver m_ServerDriver; - private NetworkDriver m_ClientDriver; - private NetworkPipelineStageId m_ReliableStageId; - private NetworkPipelineStageId m_SimulatorStageId; - - [SetUp] - public void IPC_Setup() - { - TempDisconnectPipelineStageCollection.Register(); - - var serverSettings = new NetworkSettings(); - serverSettings.WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16); - - var clientSettings = new NetworkSettings(); - clientSettings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16) - .WithSimulatorStageParameters(maxPacketCount: 30, maxPacketSize: 16, packetDelayMs: 0, packetDropPercentage: 10); - - m_ServerDriver = new NetworkDriver(new IPCNetworkInterface(), serverSettings); - m_ServerDriver.Bind(NetworkEndPoint.LoopbackIpv4); - m_ServerDriver.Listen(); - - m_ClientDriver = new NetworkDriver(new IPCNetworkInterface(), clientSettings); - m_ReliableStageId = NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)); - m_SimulatorStageId = NetworkPipelineStageCollection.GetStageId(typeof(SimulatorPipelineStage)); - } - - [TearDown] - public void IPC_TearDown() - { - m_ClientDriver.Dispose(); - m_ServerDriver.Dispose(); - } - - [Test] - public void NetworkPipeline_ReliableSequenced_SendRecvOnce() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_SendRecvWithRTTCalculation() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - - m_ServerDriver.GetPipelineBuffers(serverPipe, m_ReliableStageId, serverToClient, out var serverReceiveBuffer, out var serverSendBuffer, out var serverSharedBuffer); - var sharedContext = (ReliableUtility.SharedContext*)serverSharedBuffer.GetUnsafePtr(); - - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out var clientReceiveBuffer, out var clientSendBuffer, out var clientSharedBuffer); - - // First the server sends a packet to the client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Server sent time for the packet with seqId=0 is set - m_ServerDriver.GetPipelineBuffers(serverPipe, m_ReliableStageId, serverToClient, out serverReceiveBuffer, out serverSendBuffer, out serverSharedBuffer); - var serverPacketTimer = ReliableUtility.GetLocalPacketTimer((byte*)serverSharedBuffer.GetUnsafeReadOnlyPtr(), 0); - Assert.IsTrue(serverPacketTimer->SentTime > 0); - - m_ClientDriver.ScheduleUpdate().Complete(); - - // Client received seqId=0 from server and sets the receive time - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out clientReceiveBuffer, out clientSendBuffer, out clientSharedBuffer); - var clientPacketTimer = ReliableUtility.GetRemotePacketTimer((byte*)clientSharedBuffer.GetUnsafeReadOnlyPtr(), 0); - Assert.IsTrue(clientPacketTimer->ReceiveTime >= serverPacketTimer->SentTime); - - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // Now update client, if it's updated in the while loop it will automatically send ack packets to the server - // so processing time will actually be recorded as almost 0 - m_ClientDriver.ScheduleUpdate().Complete(); - - // Now client sends packet to the server, this should contain the ackedSeqId=0 for the servers initial packet - if (m_ClientDriver.BeginSend(clientPipe, clientToServer, out strm) == 0) - { - strm.WriteInt((int)9000); - m_ClientDriver.EndSend(strm); - } - m_ClientDriver.ScheduleUpdate().Complete(); - - // Receive time for the server packet is 0 at this point - Assert.AreEqual(serverPacketTimer->ReceiveTime, 0); - - // Packet is now processed, receive+processing time recorded - m_ServerDriver.ScheduleUpdate().Complete(); - - // Server has now received a packet from the client with ackedSeqId=0 in the header and timing info for that - Assert.GreaterOrEqual(serverPacketTimer->ReceiveTime, clientPacketTimer->ReceiveTime); - Assert.GreaterOrEqual(serverPacketTimer->ProcessingTime, 16); - } - - [Test] - public void NetworkPipeline_ReliableSequenced_SendRecvMany() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - for (int i = 0; i < 30; ++i) - { - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)i); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - - var result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - - Assert.AreEqual(NetworkEvent.Type.Data, result); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(i, readStrm.ReadInt()); - - // Send back a message to server - if (m_ClientDriver.BeginSend(clientPipe, clientToServer, out strm) == 0) - { - strm.WriteInt((int)i * 100); - m_ClientDriver.EndSend(strm); - } - m_ClientDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from client - // 100 frames = 1600ms - for (int frame = 0; frame < 100; ++frame) - { - m_ServerDriver.ScheduleUpdate().Complete(); - result = serverToClient.PopEvent(m_ServerDriver, out readStrm); - if (result != NetworkEvent.Type.Empty) - break; - } - Assert.AreEqual(NetworkEvent.Type.Data, result); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(i * 100, readStrm.ReadInt()); - } - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_SendRecvManyWithPacketDropHighSeqId() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Set sequence ID to a value just below wrapping over 0, also need to set last received seqId value to one - // less or the first packet will be considered out of order and stored for later use - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out var receiveBuffer, out var sendBuffer, out var sharedBuffer); - var sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = ushort.MaxValue - 1; - sharedContext->SentPackets.Acked = ushort.MaxValue - 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - var receiveContext = (ReliableUtility.Context*)receiveBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // This test runs fast so the minimum resend times needs to be lower (assumes 1 ms update rate) - ReliableUtility.SetMinimumResendTime(4, m_ClientDriver, clientPipe, clientToServer); - ReliableUtility.SetMinimumResendTime(4, m_ServerDriver, serverPipe, serverToClient); - - m_ServerDriver.GetPipelineBuffers(serverPipe, m_ReliableStageId, serverToClient, out receiveBuffer, out sendBuffer, out sharedBuffer); - sharedContext = (ReliableUtility.SharedContext*)sharedBuffer.GetUnsafePtr(); - sharedContext->SentPackets.Sequence = ushort.MaxValue - 1; - sharedContext->SentPackets.Acked = ushort.MaxValue - 2; - sharedContext->SentPackets.AckMask = 0xFFFFFFFF; - sharedContext->ReceivedPackets.Sequence = sharedContext->SentPackets.Acked; - sharedContext->ReceivedPackets.AckMask = sharedContext->SentPackets.AckMask; - receiveContext = (ReliableUtility.Context*)receiveBuffer.GetUnsafePtr(); - receiveContext->Delivered = sharedContext->SentPackets.Acked; - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - - SendAndReceiveMessages(clientToServer, serverToClient, clientPipe, serverPipe); - } - - [Test] - public void NetworkPipeline_ReliableSequenced_SendRecvManyWithPacketDrop() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // This test runs fast so the minimum resend times needs to be lower (assumes 1 ms update rate) - ReliableUtility.SetMinimumResendTime(4, m_ClientDriver, clientPipe, clientToServer); - ReliableUtility.SetMinimumResendTime(4, m_ServerDriver, serverPipe, serverToClient); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - - SendAndReceiveMessages(clientToServer, serverToClient, clientPipe, serverPipe); - } - - unsafe void SendAndReceiveMessages(NetworkConnection clientToServer, NetworkConnection serverToClient, NetworkPipeline clientPipe, NetworkPipeline serverPipe) - { - DataStreamReader readStrm; - - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - // Next packet should be Empty and not Data as the packet was dropped - Assert.AreEqual(NetworkEvent.Type.Empty, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - var totalMessageCount = 100; - var sendMessageCount = 0; - var lastClientReceivedNumber = 0; - var lastServerReceivedNumber = 0; - int frame = 0; - m_ServerDriver.GetPipelineBuffers(serverPipe, m_ReliableStageId, serverToClient, out var tmpReceiveBuffer, out var tmpSendBuffer, out var serverReliableBuffer); - var serverReliableCtx = (ReliableUtility.SharedContext*)serverReliableBuffer.GetUnsafePtr(); - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out tmpReceiveBuffer, out tmpSendBuffer, out var clientReliableBuffer); - var clientReliableCtx = (ReliableUtility.SharedContext*)clientReliableBuffer.GetUnsafePtr(); - m_ClientDriver.GetPipelineBuffers(clientPipe, m_SimulatorStageId, clientToServer, out tmpReceiveBuffer, out tmpSendBuffer, out var clientSimulatorBuffer); - var clientSimulatorCtx = (SimulatorUtility.Context*)clientSimulatorBuffer.GetUnsafePtr(); - // Client is the one dropping packets, so wait for that count to reach total, server receive count will be higher - while (lastClientReceivedNumber < totalMessageCount) - { - // Send message to client - sendMessageCount++; - - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)sendMessageCount); - m_ServerDriver.EndSend(strm); - } - - if (serverReliableCtx->errorCode != 0) - { - UnityEngine.Debug.Log("Reliability stats\nPacketsDropped: " + serverReliableCtx->stats.PacketsDropped + "\n" + - "PacketsDuplicated: " + serverReliableCtx->stats.PacketsDuplicated + "\n" + - "PacketsOutOfOrder: " + serverReliableCtx->stats.PacketsOutOfOrder + "\n" + - "PacketsReceived: " + serverReliableCtx->stats.PacketsReceived + "\n" + - "PacketsResent: " + serverReliableCtx->stats.PacketsResent + "\n" + - "PacketsSent: " + serverReliableCtx->stats.PacketsSent + "\n" + - "PacketsStale: " + serverReliableCtx->stats.PacketsStale + "\n"); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, serverReliableCtx->errorCode); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - NetworkEvent.Type result; - // Receive incoming message from server, might be empty but we still need to keep - // sending or else a resend for a dropped packet will not happen - m_ClientDriver.ScheduleUpdate().Complete(); - result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual(m_ClientDriver.ReceiveErrorCode, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, clientReliableCtx->errorCode); - while (result != NetworkEvent.Type.Empty) - { - Assert.AreEqual(4, readStrm.Length); - var read = readStrm.ReadInt(); - // We should be receiving in order, so last payload should be one more than the current receive count - Assert.AreEqual(lastClientReceivedNumber + 1, read); - lastClientReceivedNumber = read; - // Pop all events which might be pending (in case of dropped packet it should contain all the other packets already up to latest) - result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, clientReliableCtx->errorCode); - } - - // Send back a message to server - if (m_ClientDriver.BeginSend(clientPipe, clientToServer, out strm) == 0) - { - strm.WriteInt((int)sendMessageCount * 100); - m_ClientDriver.EndSend(strm); - } - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, clientReliableCtx->errorCode); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from client - m_ServerDriver.ScheduleUpdate().Complete(); - result = serverToClient.PopEvent(m_ServerDriver, out readStrm); - Assert.AreEqual(m_ServerDriver.ReceiveErrorCode, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, serverReliableCtx->errorCode); - while (result != NetworkEvent.Type.Empty) - { - Assert.AreEqual(4, readStrm.Length); - var read = readStrm.ReadInt(); - Assert.AreEqual(lastServerReceivedNumber + 100, read); - lastServerReceivedNumber = read; - result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, serverReliableCtx->errorCode); - } - - //Assert.AreEqual(0, serverReliableCtx->stats.PacketsDuplicated); - Assert.AreEqual(0, serverReliableCtx->stats.PacketsStale); - //Assert.AreEqual(0, clientReliableCtx->stats.PacketsDuplicated); - Assert.AreEqual(0, clientReliableCtx->stats.PacketsStale); - - if (frame > 100) - Assert.Fail("Test timeout, didn't receive all messages (" + totalMessageCount + ")"); - ++frame; - } - - var stats = serverReliableCtx->stats; - // You can get legtimate duplicated packets in the test, if the ack was just not received in time for the resend timer expired - //Assert.AreEqual(stats.PacketsResent, clientSimulatorCtx->PacketDropCount); - //Assert.AreEqual(stats.PacketsDuplicated, 0); - Assert.AreEqual(stats.PacketsStale, 0); - UnityEngine.Debug.Log("Server Reliability stats\nPacketsDropped: " + serverReliableCtx->stats.PacketsDropped + "\n" + - "PacketsDuplicated: " + serverReliableCtx->stats.PacketsDuplicated + "\n" + - "PacketsOutOfOrder: " + serverReliableCtx->stats.PacketsOutOfOrder + "\n" + - "PacketsReceived: " + serverReliableCtx->stats.PacketsReceived + "\n" + - "PacketsResent: " + serverReliableCtx->stats.PacketsResent + "\n" + - "PacketsSent: " + serverReliableCtx->stats.PacketsSent + "\n" + - "PacketsStale: " + serverReliableCtx->stats.PacketsStale + "\n"); - UnityEngine.Debug.Log("Client Reliability stats\nPacketsDropped: " + clientReliableCtx->stats.PacketsDropped + "\n" + - "PacketsDuplicated: " + clientReliableCtx->stats.PacketsDuplicated + "\n" + - "PacketsOutOfOrder: " + clientReliableCtx->stats.PacketsOutOfOrder + "\n" + - "PacketsReceived: " + clientReliableCtx->stats.PacketsReceived + "\n" + - "PacketsResent: " + clientReliableCtx->stats.PacketsResent + "\n" + - "PacketsSent: " + clientReliableCtx->stats.PacketsSent + "\n" + - "PacketsStale: " + clientReliableCtx->stats.PacketsStale + "\n"); - UnityEngine.Debug.Log("Client Simulator stats\n" + - "PacketDropCount: " + clientSimulatorCtx->PacketDropCount + "\n" + - "PacketCount: " + clientSimulatorCtx->PacketCount); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequencedStatistics_NoDrop() - { - const int packetsCount = 32; - - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out _)); - - for (int i = 0; i < packetsCount; i++) - { - Assert.Zero(m_ServerDriver.BeginSend(serverPipe, serverToClient, out var writer)); - writer.WriteInt(i); - Assert.AreEqual(4, m_ServerDriver.EndSend(writer)); - } - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - - for (int i = 0; i < packetsCount; i++) - { - Assert.AreEqual(NetworkEvent.Type.Data, m_ClientDriver.PopEventForConnection(clientToServer, out var reader)); - Assert.AreEqual(i, reader.ReadInt()); - } - - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out var tmpReceiveBuffer, out var tmpSendBuffer, out var clientReliableBuffer); - var clientReliableCtx = (ReliableUtility.SharedContext*)clientReliableBuffer.GetUnsafePtr(); - - UnityEngine.Debug.Log("Client Reliability stats\nPacketsDropped: " + clientReliableCtx->stats.PacketsDropped + "\n" + - "PacketsDuplicated: " + clientReliableCtx->stats.PacketsDuplicated + "\n" + - "PacketsOutOfOrder: " + clientReliableCtx->stats.PacketsOutOfOrder + "\n" + - "PacketsReceived: " + clientReliableCtx->stats.PacketsReceived + "\n" + - "PacketsResent: " + clientReliableCtx->stats.PacketsResent + "\n" + - "PacketsSent: " + clientReliableCtx->stats.PacketsSent + "\n" + - "PacketsStale: " + clientReliableCtx->stats.PacketsStale + "\n"); - - Assert.AreEqual(packetsCount, clientReliableCtx->stats.PacketsReceived); - Assert.Zero(clientReliableCtx->stats.PacketsDropped); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequencedStatistics_Drop([Values(10, 100)] int packetsToSkip) - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out _)); - - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out _, out _, out var clientReliableBuffer); - var clientReliableCtx = (ReliableUtility.SharedContext*)clientReliableBuffer.GetUnsafePtr(); - - m_ServerDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, serverToClient, out _, out _, out var serverReliableBuffer); - var serverReliableCtx = (ReliableUtility.SharedContext*)serverReliableBuffer.GetUnsafePtr(); - - ReliableUtility.SetMinimumResendTime(10000, m_ServerDriver, serverPipe, serverToClient); - - for (int i = 1; i <= 10; i++) - { - Assert.Zero(m_ServerDriver.BeginSend(serverPipe, serverToClient, out var writer)); - writer.WriteInt(i); - Assert.AreEqual(4, m_ServerDriver.EndSend(writer)); - - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - - Assert.AreEqual(NetworkEvent.Type.Data, m_ClientDriver.PopEventForConnection(clientToServer, out var reader)); - Assert.AreEqual(i, reader.ReadInt()); - - m_ClientDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - } - - Assert.AreEqual(10, serverReliableCtx->SentPackets.Sequence); - serverReliableCtx->SentPackets.Sequence = 10 + packetsToSkip; // skip some packets - - { - Assert.Zero(m_ServerDriver.BeginSend(serverPipe, serverToClient, out var writer)); - writer.WriteInt(1234); - Assert.AreEqual(4, m_ServerDriver.EndSend(writer)); - } - - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - - Assert.AreEqual(NetworkEvent.Type.Empty, m_ClientDriver.PopEventForConnection(clientToServer, out _)); - - Assert.AreEqual(11, clientReliableCtx->stats.PacketsReceived); - Assert.AreEqual(packetsToSkip, clientReliableCtx->stats.PacketsDropped); - - UnityEngine.Debug.Log("Client Reliability stats\nPacketsDropped: " + clientReliableCtx->stats.PacketsDropped + "\n" + - "PacketsDuplicated: " + clientReliableCtx->stats.PacketsDuplicated + "\n" + - "PacketsOutOfOrder: " + clientReliableCtx->stats.PacketsOutOfOrder + "\n" + - "PacketsReceived: " + clientReliableCtx->stats.PacketsReceived + "\n" + - "PacketsResent: " + clientReliableCtx->stats.PacketsResent + "\n" + - "PacketsSent: " + clientReliableCtx->stats.PacketsSent + "\n" + - "PacketsStale: " + clientReliableCtx->stats.PacketsStale + "\n"); - } - - [Test] - public void NetworkPipeline_UnreliableSequenced_SendRecvOnce() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Send message to client - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)42); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - Assert.AreEqual(4, readStrm.Length); - Assert.AreEqual(42, readStrm.ReadInt()); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_ClientSendsNothing() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - Assert.AreEqual(clientPipe, serverPipe); - - // Connect to server - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - Assert.AreNotEqual(default(NetworkConnection), clientToServer); - m_ClientDriver.ScheduleUpdate().Complete(); - - // Handle incoming connection from client - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - Assert.AreNotEqual(default(NetworkConnection), serverToClient); - - // Receive incoming message from server - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // Do a loop where server sends to client but client sends nothing back, it should send empty ack packets back - // so the servers queue will not get full - var totalMessageCount = 100; - var sendMessageCount = 0; - var lastClientReceivedNumber = 0; - int frame = 0; - - m_ServerDriver.GetPipelineBuffers(serverPipe, m_ReliableStageId, serverToClient, out var tmpReceiveBuffer, out var tmpSendBuffer, out var serverReliableBuffer); - var serverReliableCtx = (ReliableUtility.SharedContext*)serverReliableBuffer.GetUnsafePtr(); - m_ClientDriver.GetPipelineBuffers(clientPipe, m_ReliableStageId, clientToServer, out tmpReceiveBuffer, out tmpSendBuffer, out var clientReliableBuffer); - var clientReliableCtx = (ReliableUtility.SharedContext*)clientReliableBuffer.GetUnsafePtr(); - - // Finish when client has received all messages from server without errors - while (lastClientReceivedNumber < totalMessageCount) - { - // Send message to client - sendMessageCount++; - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)sendMessageCount); - m_ServerDriver.EndSend(strm); - } - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, serverReliableCtx->errorCode); - m_ServerDriver.ScheduleUpdate().Complete(); - - NetworkEvent.Type result; - // Receive incoming message from server, might be empty or might be more than one message - m_ClientDriver.ScheduleUpdate().Complete(); - result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual(m_ClientDriver.ReceiveErrorCode, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, clientReliableCtx->errorCode); - while (result != NetworkEvent.Type.Empty) - { - Assert.AreEqual(4, readStrm.Length); - var read = readStrm.ReadInt(); - // We should be receiving in order, so last payload should be one more than the current receive count - Assert.AreEqual(lastClientReceivedNumber + 1, read); - lastClientReceivedNumber = read; - // Pop all events which might be pending (in case of dropped packet it should contain all the other packets already up to latest) - result = clientToServer.PopEvent(m_ClientDriver, out readStrm); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, clientReliableCtx->errorCode); - } - - // no-op - m_ClientDriver.ScheduleUpdate().Complete(); - - // Make sure no event has arrived on server and no errors seen - m_ServerDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(serverToClient.PopEvent(m_ServerDriver, out readStrm), NetworkEvent.Type.Empty); - Assert.AreEqual(m_ServerDriver.ReceiveErrorCode, 0); - Assert.AreEqual((ReliableUtility.ErrorCodes) 0, serverReliableCtx->errorCode); - - if (frame > 100) - Assert.Fail("Test timeout, didn't receive all messages (" + totalMessageCount + ")"); - ++frame; - } - - // The empty ack packets will bump the PacketsSent count, also in this test it can happen that a duplicate - // packet is sent because the timers are tight - //Assert.AreEqual(totalMessageCount, serverReliableCtx->stats.PacketsSent); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_NothingIsSentAfterPingPong() - { - // Use simulator pipeline here just to count packets, need to reset the drivers for this setup - m_ServerDriver.Dispose(); - m_ClientDriver.Dispose(); - var timeoutParam = new NetworkConfigParameter - { - connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS, - maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts, - disconnectTimeoutMS = 90 * 1000, - fixedFrameTimeMS = 16 - }; - var settings = new NetworkSettings(); - settings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16) - .WithSimulatorStageParameters(maxPacketCount: 30, maxPacketSize: 16, packetDelayMs: 0, packetDropPercentage: 0); - m_ServerDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - m_ServerDriver.Bind(NetworkEndPoint.LoopbackIpv4); - m_ServerDriver.Listen(); - m_ClientDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // Perform ping pong transmision - - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)100); - Console.WriteLine("Server send"); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - Console.WriteLine("Client update"); - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Data, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - if (m_ClientDriver.BeginSend(clientPipe, clientToServer, out strm) == 0) - { - strm.WriteInt((int)200); - Console.WriteLine("Client send"); - m_ClientDriver.EndSend(strm); - } - m_ClientDriver.ScheduleUpdate().Complete(); - Console.WriteLine("Server update"); - m_ServerDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Data, serverToClient.PopEvent(m_ServerDriver, out readStrm)); - - // Check how many packets have been sent so far - m_ClientDriver.GetPipelineBuffers(clientPipe, m_SimulatorStageId, clientToServer, out var tmpReceiveBuffer, out var tmpSendBuffer, out var simulatorBuffer); - var simulatorCtx = (SimulatorUtility.Context*)simulatorBuffer.GetUnsafePtr(); - - // Do a loop and make sure nothing is being sent between client and server - 100 frames at 16ms = 1600ms - for (int iter = 0; iter < 100; ++iter) - { - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Empty, serverToClient.PopEvent(m_ServerDriver, out readStrm)); - Assert.AreEqual(NetworkEvent.Type.Empty, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - } - - // The client simulator counts all packets which pass through the pipeline so will catch anything the - // reliability pipeline might send, only 2 packets (data + ack packet) should have been received on client - Assert.AreEqual(2, simulatorCtx->PacketCount); - - // Check server side as well, server only has one packet as the client included it's ack in the pong packet it sent - m_ServerDriver.GetPipelineBuffers(serverPipe, m_SimulatorStageId, serverToClient, out tmpReceiveBuffer, out tmpSendBuffer, out simulatorBuffer); - simulatorCtx = (SimulatorUtility.Context*)simulatorBuffer.GetUnsafePtr(); - Assert.AreEqual(1, simulatorCtx->PacketCount); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_IdleAfterPacketDrop() - { - // Use simulator drop interval, then first packet will be dropped - m_ClientDriver.Dispose(); - var timeoutParam = new NetworkConfigParameter - { - connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS, - maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts, - disconnectTimeoutMS = 90 * 1000, - fixedFrameTimeMS = 16 - }; - var settings = new NetworkSettings(); - settings - .WithNetworkConfigParameters(disconnectTimeoutMS: 90 * 1000, fixedFrameTimeMS: 16) - .WithSimulatorStageParameters(maxPacketCount: 30, maxPacketSize: 16, packetDelayMs: 0, packetDropInterval: 10); - m_ClientDriver = new NetworkDriver(new IPCNetworkInterface(), settings); - - var clientPipe = m_ClientDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // Server sends one packet, this will be dropped, client has empty event - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)100); - m_ServerDriver.EndSend(strm); - } - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Empty, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // Wait until client receives the server packet resend - var clientEvent = NetworkEvent.Type.Empty; - // 100 frames = 1600ms - for (int frame = 0; frame < 100; ++frame) - { - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - clientEvent = clientToServer.PopEvent(m_ClientDriver, out readStrm); - if (clientEvent != NetworkEvent.Type.Empty) - break; - } - Assert.AreEqual(NetworkEvent.Type.Data, clientEvent); - - // Verify exactly one packet has been dropped - m_ClientDriver.GetPipelineBuffers(clientPipe, m_SimulatorStageId, clientToServer, out var tmpReceiveBuffer, out var tmpSendBuffer, out var simulatorBuffer); - var simulatorCtx = (SimulatorUtility.Context*)simulatorBuffer.GetUnsafePtr(); - Assert.AreEqual(simulatorCtx->PacketDropCount, 1); - } - - [Test] - public unsafe void NetworkPipeline_ReliableSequenced_CanRecoverFromPause() - { - var clientPipe = m_ClientDriver.CreatePipeline(typeof(TempDisconnectSendPipelineStage), typeof(ReliableSequencedPipelineStage), typeof(TempDisconnectPipelineStage)); - var serverPipe = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var clientToServer = m_ClientDriver.Connect(m_ServerDriver.LocalEndPoint()); - m_ClientDriver.ScheduleUpdate().Complete(); - m_ServerDriver.ScheduleUpdate().Complete(); - var serverToClient = m_ServerDriver.Accept(); - - m_ClientDriver.ScheduleUpdate().Complete(); - DataStreamReader readStrm; - Assert.AreEqual(NetworkEvent.Type.Connect, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - Assert.AreEqual(NetworkEvent.Type.Empty, clientToServer.PopEvent(m_ClientDriver, out readStrm)); - - // 100 frames = 1600ms - int firstFailed = 0; - int numFailed = 0; - int nextRecv = 0; - for (int frame = 0; frame < 300; ++frame) - { - if (frame == 100) - { - // Ignore all send and receive calls on the client after 100 frames - *TempDisconnectPipelineStage.s_StaticInstanceBuffer = 0; - *TempDisconnectSendPipelineStage.s_StaticInstanceBuffer = 0; - } - else if (frame == 200) - { - // Resume send and receive calls again after 200 frames - *TempDisconnectPipelineStage.s_StaticInstanceBuffer = 1; - *TempDisconnectSendPipelineStage.s_StaticInstanceBuffer = 1; - } - int sendStatus = -1; - if (m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm) == 0) - { - strm.WriteInt((int)frame); - sendStatus = m_ServerDriver.EndSend(strm); - } - if (sendStatus != 4) - { - if (numFailed == 0) - firstFailed = frame; - ++numFailed; - } - - m_ServerDriver.ScheduleUpdate().Complete(); - m_ClientDriver.ScheduleUpdate().Complete(); - bool gotData = true; - while (gotData) - { - var clientEvent = clientToServer.PopEvent(m_ClientDriver, out readStrm); - if (clientEvent == NetworkEvent.Type.Data) - { - if (nextRecv == firstFailed) - nextRecv += numFailed; - var recv = readStrm.ReadInt(); - Assert.AreEqual(nextRecv, recv); - nextRecv = recv + 1; - } - else - gotData = false; - } - } - Assert.Greater(numFailed, 0); - Assert.AreEqual(300, nextRecv); - } - } -} diff --git a/Tests/Editor/ReliablePipelineTests.cs.meta b/Tests/Editor/ReliablePipelineTests.cs.meta deleted file mode 100644 index b02e033..0000000 --- a/Tests/Editor/ReliablePipelineTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a957151bc97f94967b3f7ed66cdc7b71 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/TransportErrorValidation.cs b/Tests/Editor/TransportErrorValidation.cs deleted file mode 100644 index ec4cdcb..0000000 --- a/Tests/Editor/TransportErrorValidation.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using NUnit.Framework; -using NUnit.Framework.Constraints; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using UnityEngine; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; -using UnityEngine.TestTools; -using Random = UnityEngine.Random; - -namespace Unity.Networking.Transport.Tests -{ - public class TransportErrorValidation - { - // -- NetworkDriver ---------------------------------------------------- - - // - NullReferenceException : If the NetworkInterface is invalid for some reason - [Test] - public void Given_InvalidNetworkInterface_SystemThrows_NullReferenceException() - { - Assert.Throws(() => { var driver = new NetworkDriver(default(INetworkInterface)); }); - } - - // - ArgumentException : If the NetworkParameters are outside their given range. - [Test] - public void Given_ParametersOutsideSpecifiedRange_Throws_ArgumentException() - { - Assert.Throws(() => - { - var settings = new NetworkSettings(); - settings.WithDataStreamParameters(size: -1); - var driver = new NetworkDriver(new BaselibNetworkInterface(), settings); - }); - - LogAssert.Expect(LogType.Error, "size value (-1) must be greater or equal to 0"); - } - - // -- NetworkPipeline -------------------------------------------------- - - // - ArgumentException : If the NetworkParameters are outside their given range. - [Test] - public void Given_PiplineParametersOutsideSpecifiedRange_Throws_ArgumentException() - { - Assert.Throws(() => - { - var settings = new NetworkSettings(); - settings.WithPipelineParameters(initialCapacity: -1); - var driver = new NetworkDriver(new BaselibNetworkInterface(), settings); - }); - - LogAssert.Expect(LogType.Error, "initialCapacity value (-1) must be greater or equal to 0"); - } - - // -- BaselibNetworkInterface ------------------------------------------ - - [Test] - public void Given_BaselibReceiveParametersOutsideSpecifiedRange_Throws_ArgumentException() - { - Assert.Throws(() => - { - var settings = new NetworkSettings(); - settings.WithBaselibNetworkInterfaceParameters(receiveQueueCapacity: -1, sendQueueCapacity: 1); - var driver = new NetworkDriver(new BaselibNetworkInterface(), settings); - }); - - LogAssert.Expect(LogType.Error, "receiveQueueCapacity value (-1) must be greater than 0"); - } - - [Test] - public void Given_BaselibSendParametersOutsideSpecifiedRange_Throws_ArgumentException() - { - Assert.Throws(() => - { - var settings = new NetworkSettings(); - settings.WithBaselibNetworkInterfaceParameters(receiveQueueCapacity: 1, sendQueueCapacity: -1); - var driver = new NetworkDriver(new BaselibNetworkInterface(), settings); - }); - - LogAssert.Expect(LogType.Error, "sendQueueCapacity value (-1) must be greater than 0"); - } - } -} diff --git a/Tests/Editor/TransportErrorValidation.cs.meta b/Tests/Editor/TransportErrorValidation.cs.meta deleted file mode 100644 index e750129..0000000 --- a/Tests/Editor/TransportErrorValidation.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c23e1e63336846d45ac0ff1991265d7b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef b/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef deleted file mode 100644 index 00da37b..0000000 --- a/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "Unity.Networking.Transport.EditorTests", - "references": [ - "Unity.Burst", - "Unity.Collections", - "Unity.Mathematics", - "Unity.Networking.Transport" - ], - "optionalUnityReferences": [ - "TestAssemblies" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": true -} \ No newline at end of file diff --git a/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef.meta b/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef.meta deleted file mode 100644 index 6a1adac..0000000 --- a/Tests/Editor/Unity.Networking.Transport.EditorTests.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 28209ba5cfb3c804291cf373a2a4d034 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/Utilities/RelayServerMock.cs b/Tests/Editor/Utilities/RelayServerMock.cs deleted file mode 100644 index 1086a8c..0000000 --- a/Tests/Editor/Utilities/RelayServerMock.cs +++ /dev/null @@ -1,420 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Net; -using System.Threading; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Relay; - -namespace Unity.Networking.Transport.Tests -{ - public class RelayServerMock : UDPSocketMock - { - private string m_Address; - private ushort m_Port; - - private ConcurrentDictionary m_ClientsAddresses = - new ConcurrentDictionary(); - - public RelayServerMock(string address, ushort port) : base(address, port) - { - m_Address = address; - m_Port = port; - } - - // host is clientKey = 0 - public RelayServerData GetRelayConnectionData(int clientKey) - { - var endpoint = NetworkEndPoint.Parse(m_Address, m_Port); - return new RelayServerData( - endpoint: ref endpoint, - nonce: 23, - allocationId: GetAllocationIdForClient(clientKey), - connectionData: "sGsP0X8REiXn+PS51+lQDPBjowDlvV5zPgh15puL5YXFd3XdQ/oHkXh0a+m8A3riVvtUdZvZloYNpsi19flB6cvfpXNvib9SQ5UUMH0V+hNVExAog21jLA7PlBPp2eLHtKeCRflhR2pSq6FmRplmXLfdu4fV3eCgruvpr/pr6lVxeATN5k13OiY4lnEv5otyUsfkKFyIO+Sann97MsEklKgoAtVmlw6QurBVb+W3GDIyAHYF15UrQkkHE46fBkCbgFdry2hDQjn+6uXnxC3LVPfJw0jS4FpdbLfiUik/qATkZyX9eu97PNLw9lL1aogDLRG/ztmpi4Slwpl3awXr", - hostConnectionData: "sGsP0X8REiXn+PS51+lQDPBjowDlvV5zPgh15puL5YXFd3XdQ/oHkXh0a+m8A3riVvtUdZvZloYNpsi19flB6cvfpXNvib9SQ5UUMH0V+hNVExAog21jLA7PlBPp2eLHtKeCRflhR2pSq6FmRplmXLfdu4fV3eCgruvpr/pr6lVxeATN5k13OiY4lnEv5otyUsfkKFyIO+Sann97MsEklKgoAtVmlw6QurBVb+W3GDIyAHYF15UrQkkHE46fBkCbgFdry2hDQjn+6uXnxC3LVPfJw0jS4FpdbLfiUik/qATkZyX9eu97PNLw9lL1aogDLRG/ztmpi4Slwpl3awXr", - key: "cSzQ2I5ZCQ7vnHMt9fDB2/+xDkL2VUKoUT7AVNYhe+kaTQLptQ0gUbco/Qgiicow89VtxOXcw92IozbdDG848w==", - false - ); - } - - public bool IsBound(int clientKey) - { - return m_ClientsAddresses.ContainsKey(GetAllocationIdForClient(clientKey)); - } - - public bool CompleteBind(NetworkDriver driver, int clientKey) - { - SetupForBind(clientKey); - - if (driver.Bind(NetworkEndPoint.AnyIpv4) != 0) - return false; - - WaitForCondition(() => - { - driver.ScheduleUpdate().Complete(); - return IsBound(clientKey); - }); - - return IsBound(clientKey); - } - - public bool CompleteConnect(NetworkDriver host, out (NetworkConnection hostToClient, NetworkConnection clientToHost)[] resultConnections, params NetworkDriver[] clients) - { - resultConnections = new(NetworkConnection hostToClient, NetworkConnection clientToHost)[clients.Length]; - - if (!CompleteBind(host, 0)) - return false; - - if (host.Listen() != 0) - return false; - - var clientId = 0; - foreach (var client in clients) - { - if (!CompleteBind(client, clientId + 1)) - return false; - - SetupForConnect(clientId + 1); - - var clientToHost = client.Connect(GetRelayConnectionData(0).Endpoint); - - if (default(NetworkConnection) == clientToHost) - return false; - - RelayServerMock.WaitForCondition(() => - { - client.ScheduleUpdate(default).Complete(); - host.ScheduleUpdate(default).Complete(); - - return client.GetConnectionState(clientToHost) == NetworkConnection.State.Connected; - }); - - if (client.GetConnectionState(clientToHost) != NetworkConnection.State.Connected) - return false; - - if (client.PopEvent(out resultConnections[clientId].clientToHost, out var _) != NetworkEvent.Type.Connect) - return false; - - if ((resultConnections[clientId].hostToClient = host.Accept()) == default) - return false; - - ++clientId; - } - - return true; - } - - public void RegisterBoundClient(RelayAllocationId allocationId, EndPoint endpoint) - { - m_ClientsAddresses[allocationId] = endpoint; - } - - public void SetupForBind(int clientKey) - { - ExpectPacket(BindPacket, (endpoint, data) => - { - RegisterBoundClient(GetAllocationIdForClient(clientKey), endpoint); - Send(BindReceivedPacket, endpoint); - }); - } - - public void ExpectOptionalRepeatedPacket(byte[] packet) - { - Action callback = null; - - callback = (endpoint, data) => - { - ExpectPacket(packet, callback, optional: true); - }; - - callback(null, null); - } - - public void SetupForBindRetry(int retryCount, Action onBindReceived, int clientKey) - { - var retriesLeft = retryCount; - Action bindReceiveMethod = null; - - bindReceiveMethod = (endpoint, data) => - { - onBindReceived?.Invoke(); - - if (--retriesLeft > 0) - ExpectPacket(BindPacket, bindReceiveMethod); - else - { - ExpectOptionalRepeatedPacket(BindPacket); - RegisterBoundClient(GetAllocationIdForClient(clientKey), endpoint); - Send(BindReceivedPacket, endpoint); - } - }; - - ExpectPacket(BindPacket, bindReceiveMethod); - } - - public unsafe void SetupForBindFail(int key) - { - var errorNotFoundPacket = ErrorNotFoundPacket; - fixed(byte* ptr = &errorNotFoundPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(key); - } - - ExpectPacket(BindPacket, (endpoint, data) => - { - Send(errorNotFoundPacket, endpoint); - }); - } - - public unsafe void SetupForConnect(int clientKey) - { - var connectRequestPacket = ConnectRequestPacket; - fixed(byte* ptr = &connectRequestPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(clientKey); - } - - var acceptedPacket = AcceptedPacket; - fixed(byte* ptr = &acceptedPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(0); - *(RelayAllocationId*)(ptr + 20) = GetAllocationIdForClient(clientKey); - } - - ExpectPacket(connectRequestPacket, (endpoint, data) => - { - Send(acceptedPacket, endpoint); - - SetupForRelay(clientKey, 0, UdpCHeader.Length); - SetupForRelay(0, clientKey, UdpCHeader.Length + SessionIdToken.k_Length); - }); - } - - public unsafe void SetupForConnectRetry(int clientKey, int retryCount, Action onConnectReceived) - { - var connectRequestPacket = ConnectRequestPacket; - fixed(byte* ptr = &connectRequestPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(clientKey); - } - - var acceptedPacket = AcceptedPacket; - fixed(byte* ptr = &acceptedPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(0); - *(RelayAllocationId*)(ptr + 20) = GetAllocationIdForClient(clientKey); - } - - var retriesLeft = retryCount; - Action connectReceiveMethod = null; - - connectReceiveMethod = (endpoint, data) => - { - onConnectReceived?.Invoke(); - - if (--retriesLeft > 0) - ExpectPacket(connectRequestPacket, connectReceiveMethod); - else - { - ExpectOptionalRepeatedPacket(connectRequestPacket); - - Send(acceptedPacket, endpoint); - - SetupForRelay(clientKey, 0, UdpCHeader.Length); - SetupForRelay(0, clientKey, UdpCHeader.Length + SessionIdToken.k_Length); - - // Expect to possibly get an extra set of handshake messages. For the - // connection retry tests, the connect timeout is set pretty low. It's not - // unusual for it to expire once while the handshake is in progress, which - // causes an extra set of handshake messages to be exchanged. - SetupForRelay(clientKey, 0, UdpCHeader.Length, true); - SetupForRelay(0, clientKey, UdpCHeader.Length + SessionIdToken.k_Length, true); - } - }; - - ExpectPacket(connectRequestPacket, connectReceiveMethod); - } - - public unsafe void SetupForConnectTimeout(int clientKey) - { - var connectRequestPacket = ConnectRequestPacket; - fixed(byte* ptr = &connectRequestPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(clientKey); - } - - var errorTimedOutPacket = ErrorTimedOutPacket; - fixed(byte* ptr = &errorTimedOutPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(clientKey); - } - - ExpectPacket(connectRequestPacket, (endpoint, data) => - { - Send(errorTimedOutPacket, endpoint); - }); - } - - public unsafe void SetupForRelay(int from, int to, ushort dataLength, bool optional = false) - { - var relayMessage = new byte[RelayPacket.Length + dataLength]; - Array.Copy(RelayPacket, relayMessage, RelayPacket.Length); - - fixed(byte* ptr = &relayMessage[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(from); - *(RelayAllocationId*)(ptr + 20) = GetAllocationIdForClient(to); - *(ushort*)(ptr + 36) = RelayNetworkProtocol.SwitchEndianness(dataLength); - } - - var parameters = new[] - { - new PacketParameter {Offset = 38, Size = dataLength} - }; - - ExpectPacket(relayMessage, (endpoint, data) => - { - if (m_ClientsAddresses.TryGetValue(GetAllocationIdForClient(to), out var toEndpoint)) - Send(data, toEndpoint); - else - throw new Exception("Relay was not sent because destination client was not bound"); - }, parameters, optional); - } - - public unsafe void SetupForDisconnect(int from, int to) - { - var disconnectPacket = DisconnectPacket; - fixed(byte* ptr = &disconnectPacket[0]) - { - *(RelayAllocationId*)(ptr + 4) = GetAllocationIdForClient(to); - *(RelayAllocationId*)(ptr + 20) = GetAllocationIdForClient(from); - } - - SetupForRelay(from, to, UdpCHeader.Length); - ExpectPacket(disconnectPacket, null); - } - - static private byte[] BindPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x00, // Header - 0x00, // Accept Mode - 0x17, 0x00, // Nonce - 0xff, // ConnectionData Size - 0xb0, 0x6b, 0x0f, 0xd1, 0x7f, 0x11, 0x12, 0x25, 0xe7, 0xf8, 0xf4, 0xb9, 0xd7, 0xe9, 0x50, 0x0c, // ConnectionData - 0xf0, 0x63, 0xa3, 0x00, 0xe5, 0xbd, 0x5e, 0x73, 0x3e, 0x08, 0x75, 0xe6, 0x9b, 0x8b, 0xe5, 0x85, - 0xc5, 0x77, 0x75, 0xdd, 0x43, 0xfa, 0x07, 0x91, 0x78, 0x74, 0x6b, 0xe9, 0xbc, 0x03, 0x7a, 0xe2, - 0x56, 0xfb, 0x54, 0x75, 0x9b, 0xd9, 0x96, 0x86, 0x0d, 0xa6, 0xc8, 0xb5, 0xf5, 0xf9, 0x41, 0xe9, - 0xcb, 0xdf, 0xa5, 0x73, 0x6f, 0x89, 0xbf, 0x52, 0x43, 0x95, 0x14, 0x30, 0x7d, 0x15, 0xfa, 0x13, - 0x55, 0x13, 0x10, 0x28, 0x83, 0x6d, 0x63, 0x2c, 0x0e, 0xcf, 0x94, 0x13, 0xe9, 0xd9, 0xe2, 0xc7, - 0xb4, 0xa7, 0x82, 0x45, 0xf9, 0x61, 0x47, 0x6a, 0x52, 0xab, 0xa1, 0x66, 0x46, 0x99, 0x66, 0x5c, - 0xb7, 0xdd, 0xbb, 0x87, 0xd5, 0xdd, 0xe0, 0xa0, 0xae, 0xeb, 0xe9, 0xaf, 0xfa, 0x6b, 0xea, 0x55, - 0x71, 0x78, 0x04, 0xcd, 0xe6, 0x4d, 0x77, 0x3a, 0x26, 0x38, 0x96, 0x71, 0x2f, 0xe6, 0x8b, 0x72, - 0x52, 0xc7, 0xe4, 0x28, 0x5c, 0x88, 0x3b, 0xe4, 0x9a, 0x9e, 0x7f, 0x7b, 0x32, 0xc1, 0x24, 0x94, - 0xa8, 0x28, 0x02, 0xd5, 0x66, 0x97, 0x0e, 0x90, 0xba, 0xb0, 0x55, 0x6f, 0xe5, 0xb7, 0x18, 0x32, - 0x32, 0x00, 0x76, 0x05, 0xd7, 0x95, 0x2b, 0x42, 0x49, 0x07, 0x13, 0x8e, 0x9f, 0x06, 0x40, 0x9b, - 0x80, 0x57, 0x6b, 0xcb, 0x68, 0x43, 0x42, 0x39, 0xfe, 0xea, 0xe5, 0xe7, 0xc4, 0x2d, 0xcb, 0x54, - 0xf7, 0xc9, 0xc3, 0x48, 0xd2, 0xe0, 0x5a, 0x5d, 0x6c, 0xb7, 0xe2, 0x52, 0x29, 0x3f, 0xa8, 0x04, - 0xe4, 0x67, 0x25, 0xfd, 0x7a, 0xef, 0x7b, 0x3c, 0xd2, 0xf0, 0xf6, 0x52, 0xf5, 0x6a, 0x88, 0x03, - 0x2d, 0x11, 0xbf, 0xce, 0xd9, 0xa9, 0x8b, 0x84, 0xa5, 0xc2, 0x99, 0x77, 0x6b, 0x05, 0xeb, - 0x51, 0x5f, 0x83, 0x86, 0x30, 0x58, 0x5f, 0x99, 0xfc, 0x95, 0x93, 0x76, 0x28, 0x41, 0x09, 0xf2, // HMAC - 0x6b, 0x62, 0x43, 0x3f, 0x70, 0x29, 0x23, 0x38, 0xc0, 0xcf, 0xd5, 0xdd, 0x74, 0x78, 0x7f, 0x09 - }; - - static private byte[] BindReceivedPacket => new byte[] { 0xda, 0x72, 0x00, 0x01 }; - - static private byte[] ConnectRequestPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x03, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Allocation Id - 0xFF, // ToConnectionData Size - 0xb0, 0x6b, 0x0f, 0xd1, 0x7f, 0x11, 0x12, 0x25, 0xe7, 0xf8, 0xf4, 0xb9, 0xd7, 0xe9, 0x50, 0x0c, // ToConnectionData - 0xf0, 0x63, 0xa3, 0x00, 0xe5, 0xbd, 0x5e, 0x73, 0x3e, 0x08, 0x75, 0xe6, 0x9b, 0x8b, 0xe5, 0x85, - 0xc5, 0x77, 0x75, 0xdd, 0x43, 0xfa, 0x07, 0x91, 0x78, 0x74, 0x6b, 0xe9, 0xbc, 0x03, 0x7a, 0xe2, - 0x56, 0xfb, 0x54, 0x75, 0x9b, 0xd9, 0x96, 0x86, 0x0d, 0xa6, 0xc8, 0xb5, 0xf5, 0xf9, 0x41, 0xe9, - 0xcb, 0xdf, 0xa5, 0x73, 0x6f, 0x89, 0xbf, 0x52, 0x43, 0x95, 0x14, 0x30, 0x7d, 0x15, 0xfa, 0x13, - 0x55, 0x13, 0x10, 0x28, 0x83, 0x6d, 0x63, 0x2c, 0x0e, 0xcf, 0x94, 0x13, 0xe9, 0xd9, 0xe2, 0xc7, - 0xb4, 0xa7, 0x82, 0x45, 0xf9, 0x61, 0x47, 0x6a, 0x52, 0xab, 0xa1, 0x66, 0x46, 0x99, 0x66, 0x5c, - 0xb7, 0xdd, 0xbb, 0x87, 0xd5, 0xdd, 0xe0, 0xa0, 0xae, 0xeb, 0xe9, 0xaf, 0xfa, 0x6b, 0xea, 0x55, - 0x71, 0x78, 0x04, 0xcd, 0xe6, 0x4d, 0x77, 0x3a, 0x26, 0x38, 0x96, 0x71, 0x2f, 0xe6, 0x8b, 0x72, - 0x52, 0xc7, 0xe4, 0x28, 0x5c, 0x88, 0x3b, 0xe4, 0x9a, 0x9e, 0x7f, 0x7b, 0x32, 0xc1, 0x24, 0x94, - 0xa8, 0x28, 0x02, 0xd5, 0x66, 0x97, 0x0e, 0x90, 0xba, 0xb0, 0x55, 0x6f, 0xe5, 0xb7, 0x18, 0x32, - 0x32, 0x00, 0x76, 0x05, 0xd7, 0x95, 0x2b, 0x42, 0x49, 0x07, 0x13, 0x8e, 0x9f, 0x06, 0x40, 0x9b, - 0x80, 0x57, 0x6b, 0xcb, 0x68, 0x43, 0x42, 0x39, 0xfe, 0xea, 0xe5, 0xe7, 0xc4, 0x2d, 0xcb, 0x54, - 0xf7, 0xc9, 0xc3, 0x48, 0xd2, 0xe0, 0x5a, 0x5d, 0x6c, 0xb7, 0xe2, 0x52, 0x29, 0x3f, 0xa8, 0x04, - 0xe4, 0x67, 0x25, 0xfd, 0x7a, 0xef, 0x7b, 0x3c, 0xd2, 0xf0, 0xf6, 0x52, 0xf5, 0x6a, 0x88, 0x03, - 0x2d, 0x11, 0xbf, 0xce, 0xd9, 0xa9, 0x8b, 0x84, 0xa5, 0xc2, 0x99, 0x77, 0x6b, 0x05, 0xeb, - }; - - static private byte[] AcceptedPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x06, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // From Allocation Id - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // To Allocation Id - }; - - static private byte[] RelayPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x0a, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // From Allocation Id - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // To Allocation Id - 0x00, 0x00, // Data Length - // Content... - }; - - static private byte[] DisconnectPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x09, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // From Allocation Id - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // To Allocation Id - }; - - static private byte[] PingPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x02, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // From Allocation Id - }; - - static private byte[] ErrorNotFoundPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x0c, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Allocation Id - 0x04, // Error Code - }; - - static private byte[] ErrorTimedOutPacket => new byte[] - { - 0xda, 0x72, 0x00, 0x0c, // Header - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Allocation Id - 0x01, // Error Code - }; - - static private RelayAllocationId GetAllocationIdForClient(int clientKey) - { - var allocationId = new RelayAllocationId(); - - if (clientKey == 0) - clientKey = -1; - - unsafe - { - *(int*)allocationId.Value = clientKey; - } - - return allocationId; - } - - static public void WaitForCondition(Func condition, long timeout = 2000) - { - var stopwatch = Stopwatch.StartNew(); - - while (stopwatch.ElapsedMilliseconds <= timeout) - { - if (condition()) - break; - - Thread.Sleep(5); - } - } - } -} diff --git a/Tests/Editor/Utilities/RelayServerMock.cs.meta b/Tests/Editor/Utilities/RelayServerMock.cs.meta deleted file mode 100644 index 3adffa6..0000000 --- a/Tests/Editor/Utilities/RelayServerMock.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b9aade700b66c2c42b24d7cca46ce3c4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/Utilities/UDPSocketMock.cs b/Tests/Editor/Utilities/UDPSocketMock.cs deleted file mode 100644 index b5d9f66..0000000 --- a/Tests/Editor/Utilities/UDPSocketMock.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using NUnit.Framework; -using Unity.Collections.LowLevel.Unsafe; - -namespace Unity.Networking.Transport.Tests -{ - public class UDPSocketMock : IDisposable - { - private struct ExpectedPacket - { - public byte[] Packet; - public Action Callback; - public PacketParameter[] Parameters; - public bool Optional; - } - - private const int k_BufferSize = 1024; - private const int k_ExpectedPacketsBufferSize = 1024; - private Socket m_Socket; - private byte[] m_Buffer; - private EndPoint m_LocalEndpoint = new IPEndPoint(IPAddress.Loopback, 0); - private ExpectedPacket[] m_ExpectedPackets; - private int m_ExpectedPacketsCount; - private List m_Exceptions = new List(); - - private bool m_disposed = false; - - - public UDPSocketMock(string address, ushort port) - { - m_Buffer = new byte[k_BufferSize]; - m_ExpectedPackets = new ExpectedPacket[k_ExpectedPacketsBufferSize]; - m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - m_Socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); - m_Socket.BeginReceiveFrom(m_Buffer, 0, k_BufferSize, SocketFlags.None, ref m_LocalEndpoint, Receive, null); - } - - public void Dispose() - { - if (m_disposed) - return; - - Close(); - m_Socket.Dispose(); - - foreach (var e in m_Exceptions) - { - UnityEngine.Debug.LogException(e); - Assert.Fail($"UDP socket exception: {e.Message}"); - } - - for (int i = 0; i < m_ExpectedPacketsCount; i++) - { - if (!m_ExpectedPackets[i].Optional) - Assert.Fail($"Expected messages not received"); - } - - m_disposed = true; - } - - public void Close() - { - try - { - m_Socket.Shutdown(SocketShutdown.Both); - } - catch (SocketException) {} - - m_Socket.Close(1); - } - - private void Receive(IAsyncResult result) - { - try - { - int count = m_Socket.EndReceiveFrom(result, ref m_LocalEndpoint); - lock (m_ExpectedPackets) - { - ProcessPacket(m_Buffer, count, m_LocalEndpoint); - } - m_Socket.BeginReceiveFrom(m_Buffer, 0, k_BufferSize, SocketFlags.None, ref m_LocalEndpoint, Receive, null); - } - catch (ObjectDisposedException) {} - catch (SocketException) {} - catch (Exception e) - { - m_Exceptions.Add(e); - } - } - - private void ProcessPacket(byte[] buffer, int count, EndPoint endpoint) - { - for (var p = 0; p < m_ExpectedPacketsCount; p++) - { - ref var packet = ref m_ExpectedPackets[p]; - if (IsExpectedPacket(ref packet, buffer, count)) - { - try - { - packet.Callback?.Invoke(endpoint, buffer.Take(count).ToArray()); - } - catch (Exception e) - { - m_Exceptions.Add(e); - } - - // remove the packet from the list - --m_ExpectedPacketsCount; - var newExpected = new List(m_ExpectedPackets); - newExpected.RemoveAt(p); - m_ExpectedPackets = newExpected.ToArray(); - - return; - } - } - - var samePackets = new List(32); - for (var p = m_ExpectedPacketsCount - 1; p >= 0; --p) - { - ref var packet = ref m_ExpectedPackets[p]; - - if (SameHeaders(ref packet, ref buffer)) - { - samePackets.Add(p); - } - } - - if (samePackets.Count > 0) - { - var message = $"A unexpected packet was received: \n{ByteBufferToString(buffer, count)} \nProbably were expecting one of these ({samePackets.Count} candidates): \n"; - - for (int i = 0; i < samePackets.Count; i++) - { - message += $"{ByteBufferToString(m_ExpectedPackets[i].Packet, count)}\n"; - } - - m_Exceptions.Add(new Exception(message)); - } - else - m_Exceptions.Add(new Exception($"A completely unexpected (didn't even expect this header) packet was received: \n{ByteBufferToString(buffer, count)}")); - } - - private static string ByteBufferToString(byte[] buffer, int count) - { - const string separator = ",\t"; - var sb = new StringBuilder(count * 4 + separator.Length * (count * 4 - 1) + (count / 16 - 1)); - - for (var i = 0; i < count; i++) - { - sb.Append("0x"); - sb.Append(buffer[i].ToString("X2").ToLowerInvariant()); - if (i != count - 1) - { - sb.Append(separator); - - if (i == 3 || (i - 3) % 16 == 0) - sb.Append('\n'); - } - } - - return sb.ToString(); - } - - private static bool SameHeaders(ref ExpectedPacket packet, ref byte[] buffer) - { - for (var i = 0; i < 4; i++) - { - if (packet.Packet[i] != buffer[i]) - return false; - } - - return true; - } - - private static bool IsExpectedPacket(ref ExpectedPacket packet, byte[] buffer, int count) - { - if (count != packet.Packet.Length) - return false; - - for (var i = 0; i < count; ++i) - { - if (buffer[i] != packet.Packet[i]) - { - var isParamByte = false; - - for (var j = 0; j < packet.Parameters?.Length; ++j) - { - var parameter = packet.Parameters[j]; - if (i >= parameter.Offset && i < (parameter.Offset + parameter.Size)) - { - isParamByte = true; - break; - } - } - - if (isParamByte) - continue; - - return false; - } - } - - return true; - } - - public void Send(byte[] packet, EndPoint endpoint) - { - Send(packet, packet.Length, endpoint); - } - - public void Send(byte[] packet, int length, EndPoint endpoint) - { - m_Socket.SendTo(packet, 0, length, SocketFlags.None, endpoint); - } - - public void ExpectPacket(byte[] packet, Action callback, PacketParameter[] parameters = null, bool optional = false) - { - lock (m_ExpectedPackets) - { - m_ExpectedPackets[m_ExpectedPacketsCount++] = new ExpectedPacket - { - Packet = packet, - Callback = callback, - Parameters = parameters, - Optional = optional, - }; - } - } - } - - public struct PacketParameter - { - public int Offset; - public int Size; - public Type Type; - - public PacketParameter(int offset) - { - Offset = offset; - Size = default; - Type = default; - } - } -} diff --git a/Tests/Editor/Utilities/UDPSocketMock.cs.meta b/Tests/Editor/Utilities/UDPSocketMock.cs.meta deleted file mode 100644 index 26b891c..0000000 --- a/Tests/Editor/Utilities/UDPSocketMock.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: db62057a066e7164faeeac7386b3e09b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Editor/UtilitiesTests.cs b/Tests/Editor/UtilitiesTests.cs deleted file mode 100644 index eb07f5a..0000000 --- a/Tests/Editor/UtilitiesTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using NUnit.Framework; -using Unity.Networking.Transport.Utilities; -using Unity.Networking.Transport.Utilities.LowLevel.Unsafe; -using Unity.Jobs; -using Unity.Collections; -using System.Collections.Generic; - -namespace Unity.Networking.Transport.Tests -{ - public class NetworkUtilities_Tests - { - [Test] - public void NativeMultiQueue_SimpleScenarios() - { - using (NativeMultiQueue eventQ = new NativeMultiQueue(5)) - { - for (int connection = 0; connection < 5; connection++) - { - // Test Add - int item = 0; - - eventQ.Enqueue(connection, 1); - eventQ.Enqueue(connection, 1); - eventQ.Enqueue(connection, 1); - eventQ.Enqueue(connection, 1); - eventQ.Enqueue(connection, 1); - - // Add grows capacity - eventQ.Enqueue(connection, 1); - - // Test Rem - Assert.True(eventQ.Dequeue(connection, out item)); - Assert.True(eventQ.Dequeue(connection, out item)); - Assert.True(eventQ.Dequeue(connection, out item)); - Assert.True(eventQ.Dequeue(connection, out item)); - Assert.True(eventQ.Dequeue(connection, out item)); - - // Remove with grown capacity - Assert.True(eventQ.Dequeue(connection, out item)); - } - } - } - - struct FreeJob : IJobParallelFor - { - public UnsafeAtomicFreeList freeList; - public void Execute(int i) - { - var indices = new NativeArray(100, Allocator.Temp); - for (int asd = 0; asd < indices.Length; ++asd) - indices[asd] = freeList.Pop(); - for (int asd = 0; asd < indices.Length; ++asd) - { - if (indices[asd] >= 0) - freeList.Push(indices[asd]); - } - } - } - - [Test] - //[Repeat( 25 )] - public void AtomicFreeList() - { - using (var freeList = new UnsafeAtomicFreeList(1024, Allocator.Persistent)) - { - var job = new FreeJob {freeList = freeList}; - job.Schedule(1024 * 100, 1).Complete(); - var foo = new HashSet(); - for (int i = 0; i < freeList.Capacity; ++i) - { - var idx = freeList.Pop(); - Assert.IsTrue(idx < 1024 && idx >= 0); - Assert.IsFalse(foo.Contains(idx)); - } - Assert.AreEqual(-1, freeList.Pop()); - } - } - } -} diff --git a/Tests/Editor/UtilitiesTests.cs.meta b/Tests/Editor/UtilitiesTests.cs.meta deleted file mode 100644 index 334b698..0000000 --- a/Tests/Editor/UtilitiesTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 22ba6cf875ab6cf4f974a7acd99ef2a1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta deleted file mode 100644 index 295cacf..0000000 --- a/Tests/Runtime.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7c2097333eb179342b536f3596f09f60 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/BaselibNetworkInterfaceTests.cs b/Tests/Runtime/BaselibNetworkInterfaceTests.cs deleted file mode 100644 index 31dcd42..0000000 --- a/Tests/Runtime/BaselibNetworkInterfaceTests.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System.Threading; -using NUnit.Framework; -using Unity.Networking.Transport; -using UnityEngine; -using UnityEngine.TestTools; -using System.Linq; - -using static Unity.Networking.Transport.Tests.CommonUtilites; - -namespace Unity.Networking.Transport.Tests -{ - public class BaselibNetworkInterfaceTests - { - [Test] - public unsafe void Baselib_Send_WaitForCompletion() - { - var settings = new NetworkSettings(); - settings.WithBaselibNetworkInterfaceParameters(sendQueueCapacity: 2000); - - using (var baselibInterface = new BaselibNetworkInterface()) - { - baselibInterface.Initialize(settings); - baselibInterface.CreateInterfaceEndPoint(NetworkEndPoint.AnyIpv4, out var endpoint); - Assert.Zero(baselibInterface.Bind(endpoint)); - - // This tests is only valid when sending packets to a public IP. - // So we use an invalid one: https://stackoverflow.com/questions/10456044/what-is-a-good-invalid-ip-address-to-use-for-unit-tests/ - baselibInterface.CreateInterfaceEndPoint(NetworkEndPoint.Parse("192.0.2.0", 1234), out var destination); - var queueHandle = default(NetworkSendQueueHandle); - - var sendInterface = baselibInterface.CreateSendInterface(); - - for (int i = 0; i < settings.GetBaselibNetworkInterfaceParameters().sendQueueCapacity; i++) - { - sendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, sendInterface.UserData, NetworkParameterConstants.MTU); - sendHandle.size = sendHandle.capacity; - var data = (byte*)sendHandle.data; - for (int j = 0; j < sendHandle.size; j++) - { - data[j] = (byte)j; - } - Assert.AreEqual(sendHandle.capacity, sendInterface.EndSendMessage.Ptr.Invoke(ref sendHandle, ref destination, sendInterface.UserData, ref queueHandle)); - } - - baselibInterface.ScheduleSend(default, default).Complete(); - - LogAssert.NoUnexpectedReceived(); - } - } - - private void FakeSocketFailure(BaselibNetworkInterface baselibInterface) - { - var baselib = baselibInterface.m_Baselib[0]; - baselib.m_SocketStatus = BaselibNetworkInterface.SocketStatus.SocketNeedsRecreate; - baselibInterface.m_Baselib[0] = baselib; - } - - [Test] - public void Baselib_AfterSocketFailure_SocketIsRecreated() - { - using (var baselibInterface = new BaselibNetworkInterface()) - using (var dummyDriver = NetworkDriver.Create()) - { - var settings = new NetworkSettings(); - baselibInterface.Initialize(settings); - baselibInterface.CreateInterfaceEndPoint(NetworkEndPoint.AnyIpv4, out var endpoint); - Assert.Zero(baselibInterface.Bind(endpoint)); - - var socket = baselibInterface.m_Baselib[0].m_Socket; - - var packetReceiver = new NetworkPacketReceiver(); - - dummyDriver.ScheduleUpdate().Complete(); - packetReceiver.m_Driver = dummyDriver; - baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - - // Sleep to ensure different update times. - Thread.Sleep(2); - - FakeSocketFailure(baselibInterface); - - dummyDriver.ScheduleUpdate().Complete(); - packetReceiver.m_Driver = dummyDriver; - baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - - Assert.AreNotEqual(socket, baselibInterface.m_Baselib[0].m_Socket); - - LogAssert.Expect(LogType.Warning, "Socket error encountered; attempting recovery by creating a new one."); - } - } - - [Test] - public void Baselib_AfterBackToBackSocketFailures_SocketIsFailed() - { - using (var baselibInterface = new BaselibNetworkInterface()) - using (var dummyDriver = NetworkDriver.Create()) - { - var settings = new NetworkSettings(); - baselibInterface.Initialize(settings); - baselibInterface.CreateInterfaceEndPoint(NetworkEndPoint.AnyIpv4, out var endpoint); - Assert.Zero(baselibInterface.Bind(endpoint)); - - var packetReceiver = new NetworkPacketReceiver(); - - dummyDriver.ScheduleUpdate().Complete(); - packetReceiver.m_Driver = dummyDriver; - baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - - // Sleep to ensure different update times. - Thread.Sleep(2); - - FakeSocketFailure(baselibInterface); - - dummyDriver.ScheduleUpdate().Complete(); - packetReceiver.m_Driver = dummyDriver; - baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - - LogAssert.Expect(LogType.Warning, "Socket error encountered; attempting recovery by creating a new one."); - - // Sleep to ensure different update times. - Thread.Sleep(2); - - FakeSocketFailure(baselibInterface); - - dummyDriver.ScheduleUpdate().Complete(); - packetReceiver.m_Driver = dummyDriver; - baselibInterface.ScheduleReceive(packetReceiver, default).Complete(); - - Assert.AreEqual((int)Error.StatusCode.NetworkSocketError, dummyDriver.ReceiveErrorCode); - - LogAssert.Expect(LogType.Error, "Unrecoverable socket failure. An unknown condition is preventing the application from reliably creating sockets."); - LogAssert.Expect(LogType.Error, "Error on receive, errorCode = -10"); - } - } - - [Test] - public void Baselib_AfterSocketRecreation_CanSendReceive() - { - using (var server = NetworkDriver.Create()) - using (var client = NetworkDriver.Create()) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - var clientBaselibInterface = (BaselibNetworkInterface)client.NetworkInterface; - FakeSocketFailure(clientBaselibInterface); - - // Let the server and client recreate their sockets. - client.ScheduleUpdate().Complete(); - server.ScheduleUpdate().Complete(); - - client.BeginSend(connection, out var writer); - writer.WriteInt(42); - client.EndSend(writer); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out var reader); - - Assert.AreEqual(42, reader.ReadInt()); - } - } - } -} diff --git a/Tests/Runtime/BaselibNetworkInterfaceTests.cs.meta b/Tests/Runtime/BaselibNetworkInterfaceTests.cs.meta deleted file mode 100644 index c54f65a..0000000 --- a/Tests/Runtime/BaselibNetworkInterfaceTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9013f07d195ccf747b06caf865a26a3f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/ConnectDisconnectTests.cs b/Tests/Runtime/ConnectDisconnectTests.cs deleted file mode 100644 index 94304dd..0000000 --- a/Tests/Runtime/ConnectDisconnectTests.cs +++ /dev/null @@ -1,199 +0,0 @@ -using NUnit.Framework; -using Unity.Networking.Transport; - -using static Unity.Networking.Transport.Tests.CommonUtilites; - -namespace Unity.Networking.Transport.Tests -{ - public class ConnectDisconnectTests - { - private static readonly NetworkEndPoint[] s_EndpointParameters = - { - NetworkEndPoint.LoopbackIpv4, -#if !(UNITY_SWITCH || UNITY_PS4 || UNITY_PS5) - NetworkEndPoint.LoopbackIpv6 -#endif - }; - - private static readonly SecureProtocolMode[] s_SecureModeParameters = - { -#if ENABLE_MANAGED_UNITYTLS - SecureProtocolMode.SecureProtocolServerAuthOnly, - SecureProtocolMode.SecureProtocolClientAndServerAuth, -#endif - SecureProtocolMode.SecureProtocolDisabled - }; - - [Test] - public void ConnectDisconnect_SingleClient( - [ValueSource("s_EndpointParameters")] NetworkEndPoint endpoint, - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode)) - { - ConnectServerAndClient(endpoint, server, client, out _, out var connection); - - connection.Close(client); - client.ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Disconnect, server); - } - } - - [Test] - public void ConnectDisconnect_MultipleClients( - [ValueSource("s_EndpointParameters")] NetworkEndPoint endpoint, - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using (var server = CreateServer(secureMode)) - using (var client1 = CreateClient(secureMode)) - using (var client2 = CreateClient(secureMode)) - using (var client3 = CreateClient(secureMode)) - using (var client4 = CreateClient(secureMode)) - using (var client5 = CreateClient(secureMode)) - { - var clients = new NetworkDriver[] { client1, client2, client3, client4, client5 }; - var connections = new NetworkConnection[clients.Length]; - - var nep = SetupServer(endpoint, server); - - for (int i = 0; i < clients.Length; i++) - connections[i] = clients[i].Connect(nep); - - int clientsConnected = 0; - int connectionsAccepted = 0; - - WaitForCondition(() => - { - server.ScheduleUpdate().Complete(); - - var connection = server.Accept(); - while (connection.IsCreated) - { - connectionsAccepted++; - connection = server.Accept(); - } - - for (int i = 0; i < clients.Length; i++) - { - clients[i].ScheduleUpdate().Complete(); - - var ev = clients[i].PopEvent(out _, out _); - if (ev == NetworkEvent.Type.Connect) - clientsConnected++; - } - - return clientsConnected == clients.Length && connectionsAccepted == clients.Length; - }, "Timed out while waiting to accept all connections."); - - for (int i = 0; i < clients.Length; i++) - { - connections[i].Close(clients[i]); - clients[i].ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Disconnect, server); - } - } - } - - [Test] - [Ignore("Unstable in APVs. See MTT-4345.")] - public void ConnectDisconnect_ConnectSucceedsAfterRetrying( - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - const int ConnectTimeoutMS = 100; - const int MaxConnectAttempts = 10; - - var clientSettings = new NetworkSettings(); - clientSettings.WithNetworkConfigParameters( - connectTimeoutMS: ConnectTimeoutMS, - maxConnectAttempts: MaxConnectAttempts - ); - - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode, clientSettings)) - { - var nep = SetupServer(NetworkEndPoint.LoopbackIpv4, server); - - // We test the server not answering by simply killing it and creating a new one later. - server.Dispose(); - - client.Connect(nep); - - // We don't attempt to go to the very limit of the connection attempts, because with - // such a short connection timeout, it's very possible we wouldn't have time afterwards - // to complete the connection (especially with DTLS on slower CI machines). - var retryDuration = (MaxConnectAttempts / 2) * ConnectTimeoutMS; - - RunPeriodicallyFor(retryDuration, () => client.ScheduleUpdate().Complete()); - - using (var newServer = CreateServer(secureMode)) - { - newServer.Bind(nep); - Assert.IsTrue(newServer.Bound, "Failed to bind new server driver."); - - Assert.AreEqual(0, newServer.Listen(), "Failed to listen on new server driver."); - - WaitForAcceptedConnection(newServer, client); - - WaitForEvent(NetworkEvent.Type.Connect, client); - } - } - } - - [Test] - public void ConnectDisconnect_ConnectFailsAfterTooManyAttempts( - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - var clientSettings = new NetworkSettings(); - clientSettings.WithNetworkConfigParameters( - connectTimeoutMS: 100, - maxConnectAttempts: 3 - ); - - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode, clientSettings)) - { - var nep = SetupServer(NetworkEndPoint.LoopbackIpv4, server); - - // We test the server never answering by simply killing it. - server.Dispose(); - - client.Connect(nep); - - WaitForEvent(NetworkEvent.Type.Disconnect, client); - } - } - - [Test] - public void ConnectDisconnect_ConnectAfterDisconnect( - [ValueSource("s_EndpointParameters")] NetworkEndPoint endpoint, - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using var server = CreateServer(secureMode); - using var client = CreateClient(secureMode); - - var nep = SetupServer(endpoint, server); - - var connection = client.Connect(nep); - - WaitForAcceptedConnection(server, client); - server.ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Connect, client); - - client.Disconnect(connection); - client.ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Disconnect, server); - - client.Connect(nep); - - WaitForAcceptedConnection(server, client); - server.ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Connect, client); - } - } -} diff --git a/Tests/Runtime/ConnectDisconnectTests.cs.meta b/Tests/Runtime/ConnectDisconnectTests.cs.meta deleted file mode 100644 index a7cf4c2..0000000 --- a/Tests/Runtime/ConnectDisconnectTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3f0cad301966f2b43ba1846014f71b2a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/DisconnectTimeoutTests.cs b/Tests/Runtime/DisconnectTimeoutTests.cs deleted file mode 100644 index 98b2b42..0000000 --- a/Tests/Runtime/DisconnectTimeoutTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -using NUnit.Framework; -using Unity.Networking.Transport; - -using static Unity.Networking.Transport.Tests.CommonUtilites; - -namespace Unity.Networking.Transport.Tests -{ - public class DisconnectTimeoutTests - { - // By default it's 30 seconds. We want tests to go a bit quicker... - private const int DisconnectTimeoutMS = 200; - - // Time to wait for a disconnection. It's significantly more than the disconnect timeout - // because lag spikes on slower CI machines can lead to the event being generated late. - private const long MaxDisconnectTimeMS = DisconnectTimeoutMS + 150; - - [Test] - public void DisconnectTimeout_ReachedOnCommLoss() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters(disconnectTimeoutMS: DisconnectTimeoutMS); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out _); - - // Make it seem to the server like there's a communication loss. - client.Dispose(); - - WaitForEvent(NetworkEvent.Type.Disconnect, server, MaxDisconnectTimeMS); - } - } - - [Test] - public void DisconnectTimeout_ReachedWithDisabledHeartbeats() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters( - disconnectTimeoutMS: DisconnectTimeoutMS, - heartbeatTimeoutMS: 0 - ); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out _); - - WaitForCondition(() => - { - server.ScheduleUpdate().Complete(); - client.ScheduleUpdate().Complete(); - - var ev1 = server.PopEvent(out _, out _); - var ev2 = client.PopEvent(out _, out _); - return ev1 == NetworkEvent.Type.Disconnect || ev2 == NetworkEvent.Type.Disconnect; - }, "Timed out while waiting for Disconnect event.", MaxDisconnectTimeMS); - } - } - - [Test] - public void DisconnectTimeout_ReachedWithInfrequentHeartbeats() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters( - disconnectTimeoutMS: DisconnectTimeoutMS, - heartbeatTimeoutMS: DisconnectTimeoutMS * 2 - ); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out _); - - WaitForCondition(() => - { - server.ScheduleUpdate().Complete(); - client.ScheduleUpdate().Complete(); - - var ev1 = server.PopEvent(out _, out _); - var ev2 = client.PopEvent(out _, out _); - return ev1 == NetworkEvent.Type.Disconnect || ev2 == NetworkEvent.Type.Disconnect; - }, "Timed out while waiting for Disconnect event.", MaxDisconnectTimeMS); - } - } - - [Test] - [Ignore("Unstable in APVs. See MTT-4345.")] - public void DisconnectTimeout_NotReachedWithFrequentHeartbeats() - { - var settings = new NetworkSettings(); - settings.WithNetworkConfigParameters( - disconnectTimeoutMS: DisconnectTimeoutMS, - heartbeatTimeoutMS: DisconnectTimeoutMS / 2 - ); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out _); - - RunPeriodicallyFor(MaxDisconnectTimeMS, () => - { - server.ScheduleUpdate().Complete(); - client.ScheduleUpdate().Complete(); - - var ev1 = server.PopEvent(out _, out _); - var ev2 = client.PopEvent(out _, out _); - - if (ev1 == NetworkEvent.Type.Disconnect || ev2 == NetworkEvent.Type.Disconnect) - Assert.Fail("Unexpected Disconnect event."); - }); - } - } - } -} diff --git a/Tests/Runtime/DisconnectTimeoutTests.cs.meta b/Tests/Runtime/DisconnectTimeoutTests.cs.meta deleted file mode 100644 index 2b34d2a..0000000 --- a/Tests/Runtime/DisconnectTimeoutTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 43a6373020f56624cb7cd4f75ca1b8e7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/NetworkSettingsTests.cs b/Tests/Runtime/NetworkSettingsTests.cs deleted file mode 100644 index d9b12b3..0000000 --- a/Tests/Runtime/NetworkSettingsTests.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; -using NUnit.Framework; -using Unity.Collections; -using UnityEngine.TestTools; - -namespace Unity.Networking.Transport.Tests -{ - public class NetworkSettingsTests - { -#pragma warning disable UTP0002 - private unsafe struct TestParameter32 : INetworkParameter - { - public fixed byte Data[32]; - - public bool Validate() => true; - } - - private unsafe struct TestParameter64 : INetworkParameter - { - public fixed byte Data[64]; - - public bool Validate() => true; - } - - public struct TestParameterValidatable : INetworkParameter - { - public int Valid; - - public bool Validate() - { - return Valid > 0; - } - } -#pragma warning restore UTP0002 - - [Test] - public unsafe void NetworkSettings_WithParameter_AddsParameters() - { - var parameter32 = new TestParameter32(); - var parameter64 = new TestParameter64(); - - CommonUtilites.FillBuffer(parameter32.Data, 32); - CommonUtilites.FillBuffer(parameter64.Data, 64); - - var settings = new NetworkSettings(); - settings.AddRawParameterStruct(ref parameter32); - settings.AddRawParameterStruct(ref parameter64); - - Assert.IsTrue(settings.TryGet(out var returnedParameter32)); - Assert.IsTrue(settings.TryGet(out var returnedParameter64)); - - Assert.IsTrue(CommonUtilites.CheckBuffer(returnedParameter32.Data, 32)); - Assert.IsTrue(CommonUtilites.CheckBuffer(returnedParameter64.Data, 64)); - } - - [Test] - public void NetworkSettings_WithParameter_ValidatesParameter() - { - var settingsValid = new NetworkSettings(); - var parameter = new TestParameterValidatable { Valid = 1 }; - settingsValid.AddRawParameterStruct(ref parameter); - - TestDelegate funct = () => - { - var settingsInvalid = new NetworkSettings(); - parameter = new TestParameterValidatable { Valid = 0 }; - settingsInvalid.AddRawParameterStruct(ref parameter); - }; - - var expectedMessage = $"The provided network parameter ({nameof(TestParameterValidatable)}) is not valid"; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var exception = Assert.Throws(funct); - Assert.AreEqual(expectedMessage, exception.Message); -#else - funct(); - LogAssert.Expect(UnityEngine.LogType.Error, expectedMessage); -#endif - } - - [Test] - public void NetworkSettings_TryGet_ReturnsCorrectly() - { - var settings = new NetworkSettings(); - var parameter = new TestParameter32(); - settings.AddRawParameterStruct(ref parameter); - - Assert.IsTrue(settings.TryGet(out _)); - Assert.IsFalse(settings.TryGet(out _)); - } - - [Test] - public unsafe void NetworkSettings_TryGet_SetsParameter() - { - var parameter32 = new TestParameter32(); - CommonUtilites.FillBuffer(parameter32.Data, 32); - - var settings = new NetworkSettings(); - settings.AddRawParameterStruct(ref parameter32); - - Assert.IsTrue(settings.TryGet(out var returnedParameter32)); - Assert.IsTrue(CommonUtilites.CheckBuffer(returnedParameter32.Data, 32)); - - Assert.IsFalse(settings.TryGet(out _)); - } - - [Test] - public void NetworkSettings_WithParameter_ThrowsIfDisposed() - { - var settings = new NetworkSettings(); - settings.Dispose(); - - TestDelegate funct = () => - { - var parameter = new TestParameter32(); - settings.AddRawParameterStruct(ref parameter); - }; - - var expectedMessage = $"The {nameof(NetworkSettings)} has been deallocated, it is not allowed to access it."; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var exception = Assert.Throws(funct); - Assert.AreEqual(expectedMessage, exception.Message); -#else - funct(); - LogAssert.Expect(UnityEngine.LogType.Error, expectedMessage); -#endif - } - - [Test] - public void NetworkSettings_TryGet_ThrowsIfDisposed() - { - var settings = new NetworkSettings(); - settings.Dispose(); - - TestDelegate funct = () => - { - settings.TryGet(out _); - }; - - var expectedMessage = $"The {nameof(NetworkSettings)} has been deallocated, it is not allowed to access it."; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var exception = Assert.Throws(funct); - Assert.AreEqual(expectedMessage, exception.Message); -#else - funct(); - LogAssert.Expect(UnityEngine.LogType.Error, expectedMessage); -#endif - } - - [UnityTest] - public IEnumerator NetworkSettings_TempAllocator_ThrowsIfAccessedAfterDisposed() - { - var settings = new NetworkSettings(); - var parameter = new TestParameter32(); - settings.AddRawParameterStruct(ref parameter); - - // wait one frame - yield return null; - - TestDelegate funct = () => - { - settings.TryGet(out _); - }; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var exception = Assert.Throws(funct); -#else - // Collections deallocated message is disabled if checks are not active - // funct(); - // LogAssert.Expect(UnityEngine.LogType.Error, $"The {nameof(NetworkSettings)} has been deallocated, it is not allowed to access it."); -#endif - } - - [Test] - public void NetworkSettings_ExtensionPlusNoAllocator_StoresValue() - { - // This will not initialize the internal native container - var settings = new NetworkSettings(); - - // This will initialize the internal native container, and so the new returned setting - // could return a copy of the setting. Then the returned settings and the var settings - // will reference two different instances. To fix it all extension methods must be - // defined with _ref this_, for that we use Roslyn analysers to make sure users follow - // the rules, and keep this test as a reminder and quick validator. - settings.WithNetworkConfigParameters(heartbeatTimeoutMS: 123454321); - - Assert.AreEqual(123454321, settings.GetNetworkConfigParameters().heartbeatTimeoutMS); - } - - private static IEnumerable GetNetworkSettingsExtensions() - { - var methods = new List(); - // Uncomment the following line to test also user assemblies - // var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var assemblies = new[] { typeof(INetworkParameter).Assembly }; - - foreach (var assembly in assemblies) - { - var query = from type in assembly.GetTypes() - where type.IsSealed && !type.IsGenericType && !type.IsNested - from method in type.GetMethods(BindingFlags.Static - | BindingFlags.Public | BindingFlags.NonPublic) - where method.IsDefined(typeof(ExtensionAttribute), false) - where (method.GetParameters()[0].ParameterType == typeof(NetworkSettings) || method.GetParameters()[0].ParameterType == typeof(NetworkSettings).MakeByRefType()) - select method; - - methods.AddRange(query); - } - - return methods; - } - - [Test] - public void NetworkSettings_ExtensionMethods_RefThis() - { - var extensionMethods = GetNetworkSettingsExtensions(); - - foreach (var method in extensionMethods) - { - var thisParameter = method.GetParameters()[0]; - Assert.IsTrue(thisParameter.ParameterType.IsByRef, $"NetworkSettings extension method {method.Name} must define a _ref this_ parameter"); - - if (method.ReturnType == typeof(NetworkSettings)) - { - var returnParameter = method.ReturnParameter; - Assert.IsTrue(returnParameter.ParameterType.IsByRef, $"NetworkSettings extension method {method.Name} must return a NetworkSettings by reference"); - } - } - } - - [Test] - public void NetworkSettings_ExtensionMethods_Naming() - { - var extensionMethods = GetNetworkSettingsExtensions(); - - foreach (var method in extensionMethods) - { - var setRegex = new Regex(@"^With.*Parameters$"); - var getRegex = new Regex(@"^Get.*Parameters$"); - Assert.IsTrue(setRegex.IsMatch(method.Name) || getRegex.IsMatch(method.Name), $"The method '{method.Name}' must follow the rule 'With[...]Parameters' or 'Get[...]Parameters'"); - } - } - - [Test] - public void NetworkSettings_ExtensionMethods_RefParameters() - { - var extensionMethods = GetNetworkSettingsExtensions(); - - foreach (var method in extensionMethods) - { - var parameters = method.GetParameters(); - foreach (var parameter in parameters) - { - var parameterType = parameter.ParameterType; - - if (!parameterType.IsPrimitive && !parameterType.IsEnum) - Assert.IsTrue(parameterType.IsByRef, $"The parameter '{parameter.Name}' of the method '{method.Name}' should be passed by reference"); - } - } - } - - // TODO: Remove when deprecation is complete -#if !UNITY_DOTSRUNTIME - [Test] - public unsafe void LEGACY_NetworkSettings_FromArray_ConvertsToSettings() - { - var parameter32 = new TestParameter32(); - var parameter64 = new TestParameter64(); - - CommonUtilites.FillBuffer(parameter32.Data, 32); - CommonUtilites.FillBuffer(parameter64.Data, 64); - - var settings = NetworkSettings.FromArray(parameter32, parameter64); - - Assert.IsTrue(settings.TryGet(out var returnedParameter32)); - Assert.IsTrue(settings.TryGet(out var returnedParameter64)); - - Assert.IsTrue(CommonUtilites.CheckBuffer(returnedParameter32.Data, 32)); - Assert.IsTrue(CommonUtilites.CheckBuffer(returnedParameter64.Data, 64)); - } - - [Test] - public unsafe void LEGACY_NetworkSettings_FromArray_ValidatesParameter() - { - TestDelegate funct = () => - { - var settings = NetworkSettings.FromArray(new TestParameterValidatable { Valid = 0 }); - }; - - var expectedMessage = $"The provided network parameter ({typeof(TestParameterValidatable).Name}) is not valid"; - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var exception = Assert.Throws(funct); - Assert.AreEqual(expectedMessage, exception.Message); -#else - funct(); - LogAssert.Expect(UnityEngine.LogType.Error, expectedMessage); -#endif - } - -#endif // !UNITY_DOTSRUNTIME - } -} diff --git a/Tests/Runtime/NetworkSettingsTests.cs.meta b/Tests/Runtime/NetworkSettingsTests.cs.meta deleted file mode 100644 index 811d49b..0000000 --- a/Tests/Runtime/NetworkSettingsTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e99bd5928c83c4c4890e60c3d60ec6dd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/SecureProtocolTests.cs b/Tests/Runtime/SecureProtocolTests.cs deleted file mode 100644 index 444cdfd..0000000 --- a/Tests/Runtime/SecureProtocolTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -#if ENABLE_MANAGED_UNITYTLS - -using NUnit.Framework; -using Unity.Networking.Transport; -using UnityEngine; -using UnityEngine.TestTools; - -using static Unity.Networking.Transport.Tests.CommonUtilites; - -namespace Unity.Networking.Transport.Tests -{ - public class SecureProtocolTests - { - private static readonly SecureProtocolMode[] s_SecureModeParameters = - { - SecureProtocolMode.SecureProtocolServerAuthOnly, - SecureProtocolMode.SecureProtocolClientAndServerAuth - }; - - [Test] - [Ignore("Unstable because of Burst bug. See MTT-2511.")] - public void SecureProtocol_HalfOpenConnectionsPruning( - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode)) - { - var nep = SetupServer(NetworkEndPoint.LoopbackIpv4, server); - - var connection = client.Connect(nep); - - // Let the client begin the handshake... - client.ScheduleUpdate().Complete(); - - // ...but kill it before it can complete it. - client.Dispose(); - - // We won't check pruning until SSLHandshakeTimeoutMin has elapsed, and then we don't - // prune anything until secureParams.SSLHandshakeTimeoutMax later, so that's the minimum - // time we should wait. We add 50 ms to avoid instabilities when timing is a bit tight. - var pruneWaitTime = SecureTestParameters.MinHandshakeTimeoutMS + SecureTestParameters.MaxHandshakeTimeoutMS + 50; - - RunPeriodicallyFor(pruneWaitTime, () => server.ScheduleUpdate().Complete()); - - LogAssert.Expect(LogType.Error, - "Had to prune half-open connections (clients with unfinished TLS handshakes)."); - } - } - } -} - -#endif diff --git a/Tests/Runtime/SecureProtocolTests.cs.meta b/Tests/Runtime/SecureProtocolTests.cs.meta deleted file mode 100644 index ced5104..0000000 --- a/Tests/Runtime/SecureProtocolTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 27bcc30cc621f1d45ab521fb2e867d67 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/SendMessageTests.cs b/Tests/Runtime/SendMessageTests.cs deleted file mode 100644 index 54c7ae9..0000000 --- a/Tests/Runtime/SendMessageTests.cs +++ /dev/null @@ -1,314 +0,0 @@ -using NUnit.Framework; -using System.Collections; -using Unity.Collections; -using Unity.Networking.Transport; -using Unity.Networking.Transport.Protocols; -using Unity.Networking.Transport.Utilities; -using UnityEngine; -using UnityEngine.TestTools; - -using static Unity.Networking.Transport.Tests.CommonUtilites; - -namespace Unity.Networking.Transport.Tests -{ - public class SendMessageTests - { - private static readonly NetworkEndPoint[] s_EndpointParameters = - { - NetworkEndPoint.LoopbackIpv4, -#if !(UNITY_SWITCH || UNITY_PS4 || UNITY_PS5) - NetworkEndPoint.LoopbackIpv6 -#endif - }; - - private static readonly SecureProtocolMode[] s_SecureModeParameters = - { -#if ENABLE_MANAGED_UNITYTLS - SecureProtocolMode.SecureProtocolServerAuthOnly, -#endif - SecureProtocolMode.SecureProtocolDisabled - }; - - [Test] - public void SendMessage_PingPong( - [ValueSource("s_EndpointParameters")] NetworkEndPoint endpoint, - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode)) - { - ConnectServerAndClient(endpoint, server, client, out var s2cConnection, out var c2sConnection); - - // Ping - - client.BeginSend(c2sConnection, out var writer); - writer.WriteInt(42); - client.EndSend(writer); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out var reader); - - Assert.AreEqual(42, reader.ReadInt()); - - // Pong - - server.BeginSend(s2cConnection, out writer); - writer.WriteInt(4242); - server.EndSend(writer); - - server.ScheduleUpdate().Complete(); - - WaitForDataEvent(client, out reader); - - Assert.AreEqual(4242, reader.ReadInt()); - } - } - - [Test] - public void SendMessage_PingPong_MaxLength( - [ValueSource("s_EndpointParameters")] NetworkEndPoint endpoint, - [ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode)) - { - var messageLength = NetworkParameterConstants.MTU - client.MaxHeaderSize(NetworkPipeline.Null); - - var sendBuffer = new NativeArray(messageLength, Allocator.Temp); - var receiveBuffer = new NativeArray(messageLength, Allocator.Temp); - - ConnectServerAndClient(endpoint, server, client, out var s2cConnection, out var c2sConnection); - - // Ping - - for (int i = 0; i < messageLength; i++) - sendBuffer[i] = 42; - - client.BeginSend(c2sConnection, out var writer); - writer.WriteBytes(sendBuffer); - client.EndSend(writer); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out var reader); - - reader.ReadBytes(receiveBuffer); - Assert.AreEqual(messageLength, receiveBuffer.Length); - for (int i = 0; i < messageLength; i++) - Assert.AreEqual(42, receiveBuffer[i]); - - // Pong - - for (int i = 0; i < sendBuffer.Length; i++) - sendBuffer[i] = 0x42; - - server.BeginSend(s2cConnection, out writer); - writer.WriteBytes(sendBuffer); - server.EndSend(writer); - - server.ScheduleUpdate().Complete(); - - WaitForDataEvent(client, out reader); - - reader.ReadBytes(receiveBuffer); - Assert.AreEqual(messageLength, receiveBuffer.Length); - for (int i = 0; i < messageLength; i++) - Assert.AreEqual(0x42, receiveBuffer[i]); - } - } - - [UnityTest, UnityPlatform(RuntimePlatform.LinuxEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)] - [Ignore("Unstable in APVs. See MTT-4345.")] - public IEnumerator SendMessage_OverflowedReceiveBuffer() - { - var settings = new NetworkSettings(); - settings.WithDataStreamParameters(size: UdpCHeader.Length + SessionIdToken.k_Length); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - client.BeginSend(connection, out var writer); - writer.WriteBytes(new NativeArray(100, Allocator.Temp)); - client.EndSend(writer); - - LogAssert.Expect(LogType.Error, "Error on receive, errorCode = 10040"); - - client.ScheduleUpdate().Complete(); - server.ScheduleUpdate().Complete(); - - Assert.AreEqual(10040, server.ReceiveErrorCode); - - yield return null; - } - } - - [UnityTest, UnityPlatform(RuntimePlatform.LinuxEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)] - public IEnumerator SendMessage_ErrorIfNotRead() - { - using (var server = NetworkDriver.Create()) - using (var client = NetworkDriver.Create()) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - client.BeginSend(connection, out var writer); - writer.WriteInt(42); - client.EndSend(writer); - - client.ScheduleUpdate().Complete(); - - LogAssert.Expect(LogType.Error, - "Resetting event queue with pending events (Count=1, ConnectionID=0) Listening: 1"); - - server.ScheduleUpdate().Complete(); - server.ScheduleUpdate().Complete(); - - yield return null; - } - } - - [Test] - public void SendMessage_FragmentationCloseToMTU() - { - using (var server = NetworkDriver.Create()) - using (var client = NetworkDriver.Create()) - { - server.CreatePipeline(typeof(FragmentationPipelineStage)); - var pipe = client.CreatePipeline(typeof(FragmentationPipelineStage)); - - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - const int MinSize = NetworkParameterConstants.MTU - 100; - const int MaxSize = NetworkParameterConstants.MTU + 100; - - for (int size = MinSize; size <= MaxSize; size++) - { - using var buffer = new NativeArray(size, Allocator.Temp); - - Assert.AreEqual((int)Error.StatusCode.Success, client.BeginSend(pipe, connection, out var writer)); - Assert.IsTrue(writer.WriteBytes(buffer)); - Assert.AreEqual(size, client.EndSend(writer)); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out _); - } - } - } - - [Test] - public void SendMessage_FragmentationCloseToMaximumPayloadCapacity() - { - const int PayloadCapacity = 4096; - - var settings = new NetworkSettings(); - settings.WithFragmentationStageParameters(PayloadCapacity); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - server.CreatePipeline(typeof(FragmentationPipelineStage)); - var pipe = client.CreatePipeline(typeof(FragmentationPipelineStage)); - - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - for (int size = PayloadCapacity - 200; size <= PayloadCapacity; size++) - { - using (var buffer = new NativeArray(size, Allocator.Temp)) - { - Assert.AreEqual((int)Error.StatusCode.Success, client.BeginSend(pipe, connection, out var writer)); - Assert.IsTrue(writer.WriteBytes(buffer)); - Assert.AreEqual(size, client.EndSend(writer)); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out _); - } - } - } - } - - [Test] - public void SendMessage_FragmentationOnReliabilityWindowBoundary() - { - var settings = new NetworkSettings(); - settings.WithReliableStageParameters(windowSize: 1); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - server.CreatePipeline(typeof(FragmentationPipelineStage), typeof(ReliableSequencedPipelineStage)); - var pipe = client.CreatePipeline(typeof(FragmentationPipelineStage), typeof(ReliableSequencedPipelineStage)); - - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - using (var buffer = new NativeArray(NetworkParameterConstants.MTU + 100, Allocator.Temp)) - { - client.BeginSend(pipe, connection, out var writer); - writer.WriteBytes(buffer); - Assert.AreEqual((int)Error.StatusCode.NetworkSendQueueFull, client.EndSend(writer)); - } - } - } - - [Test] - [Ignore("Test is unstable. Need to investigate why. See MTT-2587.")] - public void SendMessage_OverflowReliableSequenceNumber() - { - const int ReliableWindowSize = 32; - - var settings = new NetworkSettings(); - settings.WithReliableStageParameters(windowSize: ReliableWindowSize); - - using (var server = NetworkDriver.Create(settings)) - using (var client = NetworkDriver.Create(settings)) - { - server.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - var pipe = client.CreatePipeline(typeof(ReliableSequencedPipelineStage)); - - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out _, out var connection); - - for (int i = 0; i < ushort.MaxValue + (2 * ReliableWindowSize); i++) - { - client.BeginSend(pipe, connection, out var writer); - writer.WriteInt(i); - client.EndSend(writer); - - client.ScheduleUpdate().Complete(); - - WaitForDataEvent(server, out var reader); - - Assert.AreEqual(i, reader.ReadInt()); - } - } - } - - [Test] - public void SendMessage_ReceiveAfterConnectionClose([ValueSource("s_SecureModeParameters")] SecureProtocolMode secureMode) - { - // Test only checks that receiving a message on a closed connection doesn't generate errors. - - using (var server = CreateServer(secureMode)) - using (var client = CreateClient(secureMode)) - { - ConnectServerAndClient(NetworkEndPoint.LoopbackIpv4, server, client, out var s2cConnection, out var c2sConnection); - - client.Disconnect(c2sConnection); - - server.BeginSend(s2cConnection, out var writer); - writer.WriteInt(42); - server.EndSend(writer); - - RunPeriodicallyFor(500, () => - { - server.ScheduleFlushSend(default).Complete(); - client.ScheduleUpdate().Complete(); - - Assert.AreEqual(NetworkEvent.Type.Empty, client.PopEvent(out _, out _)); - }); - } - } - } -} diff --git a/Tests/Runtime/SendMessageTests.cs.meta b/Tests/Runtime/SendMessageTests.cs.meta deleted file mode 100644 index 2311034..0000000 --- a/Tests/Runtime/SendMessageTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 954de124b71a09a4fae638f482827344 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef b/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef deleted file mode 100644 index 98145f4..0000000 --- a/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Unity.Networking.Transport.RuntimeTests", - "references": [ - "Unity.Collections", - "Unity.Networking.Transport", - "Unity.Burst" - ], - "optionalUnityReferences": [ - "TestAssemblies" - ], - "includePlatforms": [], - "excludePlatforms": [], - "versionDefines": [ - { - "name": "com.unity.collections", - "expression": "2.0.0-pre.1", - "define": "DOTS_COLLECTIONS_MOVE" - } - ], - "allowUnsafeCode": true -} diff --git a/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef.meta b/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef.meta deleted file mode 100644 index 9c4542b..0000000 --- a/Tests/Runtime/Unity.Networking.Transport.RuntimeTests.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 870e9914f4f415c43ac5684087688554 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/Utilities.meta b/Tests/Runtime/Utilities.meta deleted file mode 100644 index 8eda24b..0000000 --- a/Tests/Runtime/Utilities.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 23524f04394540848b60b52b42313932 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/Utilities/CommonUtilities.cs b/Tests/Runtime/Utilities/CommonUtilities.cs deleted file mode 100644 index 52da1cd..0000000 --- a/Tests/Runtime/Utilities/CommonUtilities.cs +++ /dev/null @@ -1,228 +0,0 @@ -using NUnit.Framework; -using System; -using System.Diagnostics; -using System.Threading; -using Unity.Collections; -using Unity.Networking.Transport; -#if ENABLE_MANAGED_UNITYTLS -using Unity.Networking.Transport.TLS; -#endif - -namespace Unity.Networking.Transport.Tests -{ - public static class CommonUtilites - { - // Used as ValueSource to create DTLS versions of a test. - public enum SecureProtocolMode - { - SecureProtocolServerAuthOnly, - SecureProtocolClientAndServerAuth, - SecureProtocolDisabled - } - - // Maximum time to wait for a network event. This is purposely very high because we don't - // want spike lags (which are fairly frequent on CI machines) to cause tests to fail. Also - // there are cases where DTLS connections take unusually long in the editor (see DST-585). - public const long MaxEventWaitTimeMS = 2000; - - // Create a new server driver (possibly secure). - public static NetworkDriver CreateServer( - SecureProtocolMode secureMode, - NetworkSettings settings = new NetworkSettings(), - INetworkInterface netInterface = null) - { -#if ENABLE_MANAGED_UNITYTLS - if (secureMode == SecureProtocolMode.SecureProtocolServerAuthOnly) - { - settings.WithSecureServerParameters( - certificate: ref SecureTestParameters.Certificate1, - privateKey: ref SecureTestParameters.PrivateKey1, - handshakeTimeoutMin: SecureTestParameters.MinHandshakeTimeoutMS, - handshakeTimeoutMax: SecureTestParameters.MaxHandshakeTimeoutMS); - } - else if (secureMode == SecureProtocolMode.SecureProtocolClientAndServerAuth) - { - var clientName = new FixedString32Bytes("127.0.0.1"); - settings.WithSecureServerParameters( - certificate: ref SecureTestParameters.Certificate1, - privateKey: ref SecureTestParameters.PrivateKey1, - caCertificate: ref SecureTestParameters.CaCertificate, - clientName: ref clientName, - handshakeTimeoutMin: SecureTestParameters.MinHandshakeTimeoutMS, - handshakeTimeoutMax: SecureTestParameters.MaxHandshakeTimeoutMS); - } -#endif - var netif = netInterface == null ? new BaselibNetworkInterface() : netInterface; - return new NetworkDriver(netif, settings); - } - - // Create a new client driver (possibly secure). - public static NetworkDriver CreateClient( - SecureProtocolMode secureMode, - NetworkSettings settings = new NetworkSettings(), - INetworkInterface netInterface = null) - { -#if ENABLE_MANAGED_UNITYTLS - if (secureMode == SecureProtocolMode.SecureProtocolServerAuthOnly) - { - var serverName = new FixedString32Bytes("127.0.0.1"); - settings.WithSecureClientParameters( - caCertificate: ref SecureTestParameters.CaCertificate, - serverName: ref serverName, - handshakeTimeoutMin: SecureTestParameters.MinHandshakeTimeoutMS, - handshakeTimeoutMax: SecureTestParameters.MaxHandshakeTimeoutMS); - } - else if (secureMode == SecureProtocolMode.SecureProtocolClientAndServerAuth) - { - var serverName = new FixedString32Bytes("127.0.0.1"); - settings.WithSecureClientParameters( - certificate: ref SecureTestParameters.Certificate2, - privateKey: ref SecureTestParameters.PrivateKey2, - caCertificate: ref SecureTestParameters.CaCertificate, - serverName: ref serverName, - handshakeTimeoutMin: SecureTestParameters.MinHandshakeTimeoutMS, - handshakeTimeoutMax: SecureTestParameters.MaxHandshakeTimeoutMS); - } -#endif - var netif = netInterface == null ? new BaselibNetworkInterface() : netInterface; - return new NetworkDriver(netif, settings); - } - - // Setup (bind and listen) a server driver. Returns the endpoint that can be connected to. - public static NetworkEndPoint SetupServer(NetworkEndPoint endpoint, NetworkDriver driver) - { - var nep = endpoint; - nep.Port = 1337; - - // Bind the server. Retrying a few times makes tests more reliable (e.g. after a failed - // test if there's still a driver hanging around that's already bound to 1337). - for (int i = 0; i < 10; i++) - { - if (driver.Bind(nep) == 0) - break; - - nep.Port += 17; - } - Assert.IsTrue(driver.Bound, "Failed to bind server driver."); - - Assert.AreEqual(0, driver.Listen(), "Failed to listen on server driver."); - - return nep; - } - - // Connect a server and a client driver. Takes care of setting up the server. - public static void ConnectServerAndClient( - NetworkEndPoint endpoint, NetworkDriver server, NetworkDriver client, - out NetworkConnection s2cConnection, out NetworkConnection c2sConnection) - { - var nep = SetupServer(endpoint, server); - - c2sConnection = client.Connect(nep); - - s2cConnection = WaitForAcceptedConnection(server, client); - - // Have the server send the Connection Accept message. - server.ScheduleUpdate().Complete(); - - WaitForEvent(NetworkEvent.Type.Connect, client); - } - - // Wait for a connection to be accepted by the server. - public static NetworkConnection WaitForAcceptedConnection(NetworkDriver server, NetworkDriver client) - { - NetworkConnection connection = default; - - WaitForCondition(() => - { - client.ScheduleUpdate().Complete(); - server.ScheduleUpdate().Complete(); - - connection = server.Accept(); - return connection.IsCreated; - }, "Timed out while waiting to accept a connection."); - - return connection; - } - - // Wait for an event of the given type to happen on the given driver. Getting any other - // type of event (except Empty of course) results in an assertion failure. - public static void WaitForEvent(NetworkEvent.Type type, NetworkDriver driver, long timeout = MaxEventWaitTimeMS) - { - NetworkEvent.Type ev = NetworkEvent.Type.Empty; - - WaitForCondition(() => - { - driver.ScheduleUpdate().Complete(); - - ev = driver.PopEvent(out _, out _); - return ev != NetworkEvent.Type.Empty; - }, $"Timed out while waiting for network event {type}.", timeout); - - Assert.AreEqual(type, ev); - } - - // Wait for a data event on the given driver and return its reader stream as an output - // parameter. Getting any other type of event (except Empty of course) results in an - // assertion failure. - public static void WaitForDataEvent(NetworkDriver driver, out DataStreamReader stream) - { - DataStreamReader reader = default; - - WaitForCondition(() => - { - driver.ScheduleUpdate().Complete(); - - var ev = driver.PopEvent(out _, out reader); - return ev == NetworkEvent.Type.Data; - }, "Timed out while waiting for Data event."); - - stream = reader; - } - - // Wait for a condition to be true. Timing out results in an assertion failure. - public static void WaitForCondition(Func condition, - string message = "Timed out while waiting for a condition.", - long timeout = MaxEventWaitTimeMS) - { - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds <= timeout) - { - if (condition()) return; - Thread.Sleep(5); - } - - Assert.Fail(message); - } - - // Run the given function periodically for the given amount of time. Aside from failing an - // assertion, there is no way to stop early before the given timeout is elapsed. - public static void RunPeriodicallyFor(long timeout, Action function) - { - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds <= timeout) - { - function(); - Thread.Sleep(5); - } - } - - public unsafe static void FillBuffer(byte* bufferPtr, int size) - { - for (int i = 0; i < size; i++) - { - bufferPtr[i] = (byte)(i % 256); - } - } - - public unsafe static bool CheckBuffer(byte* bufferPtr, int size) - { - for (int i = 0; i < size; i++) - { - if (bufferPtr[i] != (byte)(i % 256)) - return false; - } - - return true; - } - } -} diff --git a/Tests/Runtime/Utilities/CommonUtilities.cs.meta b/Tests/Runtime/Utilities/CommonUtilities.cs.meta deleted file mode 100644 index d778099..0000000 --- a/Tests/Runtime/Utilities/CommonUtilities.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 348ff9ef30183254ba99b6c58ae6d782 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Tests/Runtime/Utilities/SecureTestParameters.cs b/Tests/Runtime/Utilities/SecureTestParameters.cs deleted file mode 100644 index 8fbbeb7..0000000 --- a/Tests/Runtime/Utilities/SecureTestParameters.cs +++ /dev/null @@ -1,135 +0,0 @@ -#if ENABLE_MANAGED_UNITYTLS -using Unity.Collections; -using Unity.Networking.Transport.TLS; - -namespace Unity.Networking.Transport.Tests -{ - public static class SecureTestParameters - { - public const int MinHandshakeTimeoutMS = 500; - public const int MaxHandshakeTimeoutMS = 2000; - - public static FixedString4096Bytes CaCertificate = new FixedString4096Bytes( -@"-----BEGIN CERTIFICATE----- -MIIDDzCCAfcCFBU2SZ+6r8bdx36uGkHroQpEmX+lMA0GCSqGSIb3DQEBCwUAMEQx -CzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFs -MREwDwYDVQQKDAhVbml0eSBDQTAeFw0yMjAzMTcwMzQyNDFaFw0zMjAzMTQwMzQy -NDFaMEQxCzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1v -bnRyZWFsMREwDwYDVQQKDAhVbml0eSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBALv/mWjDrxtKTqKRrNBqZ9an0m60tSSNaXX9BRSOyGuqFmdEdW5v -YnQDXsn9wGKFF6mgr2ATfgL273Im95aLRvHwhNmEP2c2T6WUq//Pq32nJ8kwiKly -2ctBdp6QyxgRuKMvFhTFAjzEdwH6GNWdmDjq3BgErKH8JhBnAzV5DAdbnr0pC4es -0ZAOVw8iyxKWW9U6/pb/Jed6R/ioV6OuGQbaAfGhFO2/lt3RYI4MkUr9pTWIqJwc -aDL9WxCoTggVNkckQmlMiLe0rYcCqEc+A0MdWblVNKds6HcBxyjMgxsELA4DmQ7C -4frNN8EtokxbaqjbM/cJNYfQ9IoBsATKaHkCAwEAATANBgkqhkiG9w0BAQsFAAOC -AQEAB7FwMBsB+pU6VBBGJPrHm70RitGffyDTefDtSOyrNXxdHyoMiSFhb26w/iin -/jubAZ5I3lvNFawRrDlzlJSxJDjaiHDd29W5UcV+6ij3Te/NJhck+9tXfuy6r95+ -jjgGpm1RvBQq5XhEJh5FMfzXUYZ6NFg+6fLfqbE/hHo2mq+S0AAwR6gwDpr/6UzU -bARuY+bmrEFjEVFXNkmv4iZDkMQTi8UbmiwsNX3zJBPmSCErKiIPLHXBpzJitmcG -VYgO3hp/EObkBLHheqUuqLIY6XDvDhVPiJq4VyNGHnhR6GSiXs4ixL6v+UWrCHbh -ud3r5a40pzFbEWb6Zzrb3+BQZQ== ------END CERTIFICATE-----"); - - public static FixedString4096Bytes Certificate1 = new FixedString4096Bytes( -@"-----BEGIN CERTIFICATE----- -MIIDJzCCAg8CFDp7tjqt5Wu9lLe3VHOnx8nIXcI8MA0GCSqGSIb3DQEBCwUAMEQx -CzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFs -MREwDwYDVQQKDAhVbml0eSBDQTAeFw0yMjAzMTcwMzQ0MjJaFw0yMzAzMTcwMzQ0 -MjJaMFwxCzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1v -bnRyZWFsMRUwEwYDVQQKDAxVbml0eSBTZXJ2ZXIxEjAQBgNVBAMMCTEyNy4wLjAu -MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOTUPP4Be094ZVYBR8Sr -Rt9AJVNF9EbHmn02BasRh6atEioWTRI77zO/Ul1YNd7b4zZczSAlvK5++q5Miqsi -rzZH4s5BOPl6iueWKKmhC0c8Mnp8uZtbDpAHAHImATb/o+UfZHPg243QQ64x6mSk -5p7OH4U8uM24cHOnMFxHA/PpEysviWcQseIbK8v4h4X+aL850EyeaXdlz6fEDnvJ -gwdYDf9KvQCluLLLswCLZ9R4Gy4NeYf8I5Ak8TuTwCpEzx/2EUOtq4cNgS//WGHn -s9nVgkTYRkWrrIUuFUy0KQxFTC/voZq9FgtG/zN4QPIwAN1HsTMndZ17APV4JdGr -tgMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYXfzhqmhKpHM5tsf53DQYgDbQgfS -bivFLCoo4iyUlNcEjoskv9oykDIr2CVGz7uYIgGAddoiTWv8T4GyTN6/xmE6YrO9 -MS/wSLkAFJVqMeHfSWgu8ArZFPOZwc5qZCd211af8E9cyYxop/tGb6Dpa3Qpyj/o -A9F60aN7YOcvAILlq9zI1hWw3CCboW0BVXX/oHU2DHf17Lr5uMqmfjkKnITK6dqU -JyQyfBqQRdtLmjUi5NijpJqOISkv4rvDd0ahf9kOetH+AucKnQmbEEgUHIHKy8xx -IqjWfKWDGIrxnIiCnGBZ8DF/1mVndGsb+ufVdUB1A59CFJxgTXbBaSI7Vw== ------END CERTIFICATE-----"); - - public static FixedString4096Bytes PrivateKey1 = new FixedString4096Bytes( -@"-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA5NQ8/gF7T3hlVgFHxKtG30AlU0X0RseafTYFqxGHpq0SKhZN -EjvvM79SXVg13tvjNlzNICW8rn76rkyKqyKvNkfizkE4+XqK55YoqaELRzwyeny5 -m1sOkAcAciYBNv+j5R9kc+DbjdBDrjHqZKTmns4fhTy4zbhwc6cwXEcD8+kTKy+J -ZxCx4hsry/iHhf5ovznQTJ5pd2XPp8QOe8mDB1gN/0q9AKW4ssuzAItn1HgbLg15 -h/wjkCTxO5PAKkTPH/YRQ62rhw2BL/9YYeez2dWCRNhGRaushS4VTLQpDEVML++h -mr0WC0b/M3hA8jAA3UexMyd1nXsA9Xgl0au2AwIDAQABAoIBABN/QfMijS8oQvoh -AcxwC4naHVQLEdU2DKO+hp0c0UISXQnY/JghIzB1jL8HVQ+4DJQFNDUZAgqcJx/S -xb3vJp6pYVnRlNks58jzsmNBpYRcTLDDD8185LDA5/jR5Cibf4t2MUjuT8a1iA6/ -kgkhQ8UkKutfUR+2J16zUBuMsXmGqSUUGbrx/hZsyYXHYjCPhPhMS8yw+XJKGLTo -Rl4m+kDEpEoVQLg/7KHyfkNIMdJsVQmTC0JaJGpVeJeEdfXSQJ3GkBkRcbP2pQnP -c0sbna6Kr7RSkFaL4dT7eRZBb9Wtxsmd5iaCqUM4wceLVnfo6geo5QQogXJWQ6eT -h+5f5oECgYEA+27Q6pywRJzG+sim4GsU7WuTApDu2pfNorQ07zz58jZFLvoBUJwi -T3BEqN2D+Vh65YBVVVjEBVme0dF0X+pKa0+cSwU5E5nrp6xrSzSQgkEZRpB5zoL2 -TF6E/KNe6Ic3kgAW3m7NnJ5lO8eYtBSRqOFsZbzZKBSYYgy1GRs73r8CgYEA6PxQ -ASBlJbupSF48A+YFGiSl4dwb1pvyoOTX9SPKx3nVwbz2xf9B51cbM67WDSwenBnp -EN2M5YbCIzhgxnjRBHRUvLmd6Rl+mDWedB64WXmLKuECkEhiGm0z0inzagEg9f84 -IZgX1BzVzDW5f4NsYAa5tH3BNZCZuiYMbLeLfb0CgYEAyHgBkJ3fmMUbjUbAbvxR -0j7MFuax2o3gghKGhh0q+Ci3Ho5sz/W9EXNk+vKrT/pw+l1JLGQ63j0neQk0bWkj -bs4pwlLmwC8gCi9Z2LuPYJtA6Nc2lyYp9JgEFl63xFRTRVBW64CS5YYRFwm8QhCI -VottXuykg+Vv3fVdyyfAo4cCgYBL1t3gEAB80OJgyTP1/OkKQoWwyKpTKH5JO1TE -2jrGxfT71JvrhZSZTnRvVWkd7o+kNpb0Q3n6uOv29QIjeO5o6ckviahKWV8pAsMq -f1l43qSbd5UTDEzK12M39SnkBqwJB2PpI44WILDDgXV5eXlMpMPMaeb7na88tefz -d6ezbQKBgQC4RcJ53ncf5fQbP53z7S8vCNg14bSyRb3PlHyKrZsPKqWrf+njwVog -T7m1PuFBHg2wpv7HgixWBVajCZtsvTFz1P9MtCDhVY9kXTASotqNdEx+lewuM/lm -WL6GYFckcjnM73hAviqtJ8/zHf2LiCMuS30Eyg1webI1xvHTffXF1A== ------END RSA PRIVATE KEY-----"); - - public static FixedString4096Bytes Certificate2 = new FixedString4096Bytes( -@"-----BEGIN CERTIFICATE----- -MIIDJzCCAg8CFDp7tjqt5Wu9lLe3VHOnx8nIXcI9MA0GCSqGSIb3DQEBCwUAMEQx -CzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1vbnRyZWFs -MREwDwYDVQQKDAhVbml0eSBDQTAeFw0yMjAzMTcwMzQ0MzJaFw0yMzAzMTcwMzQ0 -MzJaMFwxCzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZRdWViZWMxETAPBgNVBAcMCE1v -bnRyZWFsMRUwEwYDVQQKDAxVbml0eSBTZXJ2ZXIxEjAQBgNVBAMMCTEyNy4wLjAu -MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxkU5z1dLcRIoJ5R/6Z -CShkqgbYNSS57QCmSNq2Krym/y3o7D8tglVLOAA03eJMjHKryf60jVd9iiakdNeK -5MZACNl6dDXVyMqavLKWkbr6zG/SltKYn4Muax8GmyLF+GA7y1VQzQzoilW4bQz6 -H2rVeHy6lDMWnxzGVpMZPA+0F8tuqpoHewl1X2JJLTVZ9Esiyo4z8aIDb9iW2Ru0 -1IBlAxp3omlLLyPO8LKrG/KGq1+dbXyVMZW2yNj7e/vKmQo+Thx7m27ivBYESijM -5zpCUYibg6JAE0tc1iLgj3nPvyl2AEQxNWn0g91b0iKR7kvor/saYQ5DDeoHgLt6 -Z8MCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUw4FH69SkW94hHxua/4/fVmGxdZb -ulAPAvLVKo4d0A4UdP2BV0G+9SezbZ+haGKQrPjrE8dfsC/q82Rz45aqyUNk9IVR -r4cHAVNw+aJQjB/JM2O5dxVE15mn9onoPm8exzNJnVBCER0ctNMjDqhvxRHMqZxO -lGJllv7AkSoe10UClceNz2ajrr/IskD4K1yH78QVDjRHZlze/M3mk61oL9CJPc5E -Lexn/cfKD5aNau7aIPE23yv47r3AdHHeRVku1XVJeHan/D3MFHRJzB7s+tBOKzxa -nR1pOcy7A7kpaDSt0e3RNgRTXnD81VLv0JLus/pZzmqgQK02rgZ9SlImAQ== ------END CERTIFICATE-----"); - - public static FixedString4096Bytes PrivateKey2 = new FixedString4096Bytes( -@"-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAvGRTnPV0txEignlH/pkJKGSqBtg1JLntAKZI2rYqvKb/Lejs -Py2CVUs4ADTd4kyMcqvJ/rSNV32KJqR014rkxkAI2Xp0NdXIypq8spaRuvrMb9KW -0pifgy5rHwabIsX4YDvLVVDNDOiKVbhtDPofatV4fLqUMxafHMZWkxk8D7QXy26q -mgd7CXVfYkktNVn0SyLKjjPxogNv2JbZG7TUgGUDGneiaUsvI87wsqsb8oarX51t -fJUxlbbI2Pt7+8qZCj5OHHubbuK8FgRKKMznOkJRiJuDokATS1zWIuCPec+/KXYA -RDE1afSD3VvSIpHuS+iv+xphDkMN6geAu3pnwwIDAQABAoIBAG+1mfLvhXbsPSda -3Tr16f0+u6d1WwXdDdEdyQOPk4XsDFJf1H6d8LO894b/0jZXJ8zzWsKis1EWyu2h -BPuui3uXCuhSeUhW7UpeONg4+k8CWmlQWUilYai6xKBQHXugImiF7Es5r20hEq5D -vr48Lpb94AUt6aTlnBHG9h0hkIDKR+/FlCJ5Z/W2phtx0SUumnc180ZbmycJlFe2 -VktCYI3qiEzK2rsBc/Hen116iDP6E45dZ2Wkj+AXbg/stwEJrrZop0aEuaMBTwBB -yh7RPOWv9Omj+692C6ypiXKSw0nmbfBgs+JSVt00TKEnPvEpIbmFws2jh6uiTKIH -vcWRcPkCgYEA4RV39IfRbhU8Dk4v51zFF/aHf6D9Dxp+jiH2vnHaGoNIgUYIpEWP -iNoA86z8uf0A79s+pkXilV5OARHStNr9z4LVlc/5cj6NcycXK+Th0uwk9ZaUTbRk -btwPwJbGSUdebex9n5JiLdREalUwSAvgC39pyjpvOD4fPpLgO9cg3kcCgYEA1kSs -dpRl36MiIt2y7/113lCIYhR2x/NpiL1Va7MyoDdWonFa9onW3PntLdQz2WHJlnBz -G+juezi2LUbu+OfQFtjGxdyPH82ja498mz9beM/2pYZXdwtg8p48eeUqVDzGGb7i -4yX1oALc4GojW2SojVV0dDnnfIiV9TDFjNv5vKUCgYEA1MLTYe0kdXdTDn6v33Yc -xDr/+A+4RZeYu0e1km3Saa9hUPlfX9B1DjpsHeqN9k0GnrPS858pwGGlQHOVhelf -c9DLjlJKC8i/MnNn/tDa+eLISPEufIkhSn1v0m9zPX9d7nBWiwj3DzUP+qdN/Txk -atnkqQk1aqemJyL1HPuMkS8CgYAJSq0X71ODoTVnwaly0tD7tQ5VAoIsJZgb/+nm -Y7z8SaoOHVBOhKe2qXfpltwwvxbY2UOEoFNHMKxlxj+yt4rEkt370pa6UFaX4X8L -1HGJCauufebpGKs5mjcRKijtwjzs/OQl6Y1/ROCDMtcZrYHJrzz/lUs+kUW/fj/y -3c80qQKBgQCPDk8kJImFHKGsm99Z1SA1oV6/aoNDiCGBuaMVbgIZJjoGWTGv8h4P -ssL8RLkruyKw/qzZ3iZhLpM/vbiQ2YFS4uNAPGpib19Wv+mNTUo5q9LHm/7fuQfc -OdGlPz3iWjcykylZskqzt+74Yn7EBYNTEPPU5csv2ZU4298Zo7N8Pw== ------END RSA PRIVATE KEY-----"); - } -} - -#endif diff --git a/Tests/Runtime/Utilities/SecureTestParameters.cs.meta b/Tests/Runtime/Utilities/SecureTestParameters.cs.meta deleted file mode 100644 index 228ae9a..0000000 --- a/Tests/Runtime/Utilities/SecureTestParameters.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 28a56da86abf2a84a9bad1a9a132c513 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/ValidationExceptions.json b/ValidationExceptions.json deleted file mode 100644 index 59678d2..0000000 --- a/ValidationExceptions.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "ErrorExceptions": [ - { - "ValidationTest": "Restricted File Type Validation", - "ExceptionMessage": "/Samples~/CustomNetworkInterface/Scripts/network.bindings~/build.bat cannot be included in a package.", - "PackageVersion": "1.2.0" - }, - { - "ValidationTest": "Restricted File Type Validation", - "ExceptionMessage": "/Samples~/CustomNetworkInterface/Scripts/network.bindings~/shell.bat cannot be included in a package.", - "PackageVersion": "1.2.0" - } - ], - "WarningExceptions": [] -} diff --git a/ValidationExceptions.json.meta b/ValidationExceptions.json.meta deleted file mode 100644 index 989e521..0000000 --- a/ValidationExceptions.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 47776a1957ab4d948b35a10ee1b53c3c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/package.json b/package.json index 0752dc0..50539a3 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,27 @@ { "name": "com.unity.transport", "displayName": "Unity Transport", - "version": "1.2.0", - "unity": "2020.3", - "unityRelease": "0f1", - "description": "Unity network transport layer - the low-level interface for sending UDP data", + "version": "2.0.0-exp.6", + "unity": "2022.2", + "unityRelease": "0a18", + "description": "Unity network transport layer - the low-level interface for connecting and sending data through a network", "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.burst": "1.6.6", + "com.unity.collections": "2.1.0-exp.4", + "com.unity.burst": "1.7.3", "com.unity.mathematics": "1.2.6" }, + "relatedPackages": { + "com.unity.transport.tests": "0.0.0" + }, "upmCi": { - "footprint": "b86aef366145fa59d88b43f35ef32b38cf576ef6" + "footprint": "ae0c94fd1e9687f90a185f24a4518a96261572a0" }, "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.transport.git", "type": "git", - "revision": "b4fadab8f255b0b13f371bea8d2819c2ca472648" + "revision": "23d0879f8d0b103358842e05e1a40c951b712dfe" }, "samples": [ - { - "displayName": "Custom Network Interface", - "description": "Shows how to build a custom network interface using the INetworkInterface API", - "path": "Samples~/CustomNetworkInterface" - }, { "displayName": "Ping/Pong Sample", "description": "Several samples on the basics of jobs, mainthread setups using the Unity Transport",