Compare commits

...

1 Commits

Author SHA1 Message Date
KyuubiYoru e626b89909 build: bootstrap solution and quality gates (#3)
quality-gate / quality (push) Successful in 58s
Closes #3
2026-07-16 04:25:54 +02:00
28 changed files with 803 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{cs,csproj,props,targets}]
indent_style = space
indent_size = 4
[*.cs]
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion
[*.{json,yml,yaml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+34
View File
@@ -0,0 +1,34 @@
name: quality-gate
on:
push:
branches:
- main
- codex/**
pull_request:
workflow_dispatch:
jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.301
- name: Restore locked dependencies
run: dotnet restore Rendezvous.slnx --locked-mode
- name: Verify formatting and analyzers
run: dotnet format Rendezvous.slnx --verify-no-changes --no-restore
- name: Build
run: dotnet build Rendezvous.slnx --configuration Release --no-restore
- name: Test
run: dotnet test Rendezvous.slnx --configuration Release --no-build
+8
View File
@@ -0,0 +1,8 @@
**/bin/
**/obj/
TestResults/
.idea/
.vs/
*.suo
*.user
*.userosscache
+15
View File
@@ -0,0 +1,15 @@
<Project>
<PropertyGroup>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<Deterministic>true</Deterministic>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RestoreLockedMode Condition="'$(CI)' == 'true'">true</RestoreLockedMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
+12
View File
@@ -0,0 +1,12 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="LiteNetLib" Version="2.1.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
+18
View File
@@ -79,3 +79,21 @@ Rendezvous is currently in its initial design and bootstrap stage. The first imp
The ratified v1 boundaries, trust decisions, privacy rules, safety budgets, and The ratified v1 boundaries, trust decisions, privacy rules, safety budgets, and
threat model are indexed in [the architecture documentation](docs/architecture/README.md). threat model are indexed in [the architecture documentation](docs/architecture/README.md).
## Development
The repository pins .NET SDK 10.0.301. From a clean clone, run the same gates as
CI from the repository root:
```bash
dotnet restore Rendezvous.slnx --locked-mode
dotnet format Rendezvous.slnx --verify-no-changes --no-restore
dotnet build Rendezvous.slnx --configuration Release --no-restore
dotnet test Rendezvous.slnx --configuration Release --no-build
```
Run the bootstrap server with
`dotnet run --project src/FinalFactory.Rendezvous.Server`. It serves HTTP health endpoints and binds
the configured UDP mediator port; both stop through normal host cancellation.
The project dependency rules and supported runtime choices are documented in
[project and dependency boundaries](docs/architecture/project-boundaries.md).
+11
View File
@@ -0,0 +1,11 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj" />
<Project Path="src/FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<Project Path="src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj" />
<Project Path="src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/FinalFactory.Rendezvous.Tests/FinalFactory.Rendezvous.Tests.csproj" />
</Folder>
</Solution>
+45
View File
@@ -0,0 +1,45 @@
# Project and dependency boundaries
Tracking: #3
```text
FinalFactory.Rendezvous.Contracts <- FinalFactory.Rendezvous.Client
^ ^
| |
FinalFactory.Rendezvous.Server FinalFactory.Rendezvous.TestClient
```
- `Contracts` targets `netstandard2.1` and contains only versioned,
transport-neutral IDs and wire contracts. It cannot reference Server,
LiteNetLib, or Godot.
- `Client` targets `netstandard2.1`, references Contracts and the pinned
LiteNetLib package, and contains no Godot or Server dependency.
- `Server` targets .NET 10 LTS, references Contracts and LiteNetLib, and owns
HTTP hosting, UDP mediation, application policy, and ephemeral state.
- `TestClient` targets .NET 8, references only the public Client/Contracts seams
and LiteNetLib, and must never reach into Server internals.
- `Tests` target .NET 10 and may reference every project solely to verify public
behavior and architecture boundaries.
The dependency-boundary tests inspect compiled assembly references. A forbidden
engine, transport, or server dependency therefore fails the normal test gate.
## Supported toolchain
- Build SDK: .NET SDK 10.0.301, pinned by `global.json`.
- Server runtime: .NET 10 LTS.
- Client/contracts compatibility target: .NET Standard 2.1, consumable by the
.NET 8-or-later runtime used by current Godot 4 C# projects.
- TestClient runtime: .NET 8.
- LiteNetLib: 2.1.4, pinned centrally and restored from the lock files.
The repository uses central package versions, per-project lock files,
deterministic compilation, nullable reference types, warnings as errors, current
.NET analyzers, and formatting verification. CI restores in locked mode so a
package graph change must be deliberate and committed.
Primary compatibility references:
- [.NET support policy](https://dotnet.microsoft.com/en-us/platform/support/policy)
- [Godot stable C# prerequisites](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html)
- [LiteNetLib 2.1.4 on NuGet](https://www.nuget.org/packages/LiteNetLib/2.1.4)
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "10.0.301",
"rollForward": "disable",
"allowPrerelease": false
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.Client</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.Client</RootNamespace>
<IsPackable>true</IsPackable>
<PackageId>FinalFactory.Rendezvous.Client</PackageId>
<Description>Godot-independent client SDK for Final Factory Rendezvous.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<PackageReference Include="LiteNetLib" />
</ItemGroup>
</Project>
@@ -0,0 +1,16 @@
{
"version": 2,
"dependencies": {
".NETStandard,Version=v2.1": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
"finalfactory.rendezvous.contracts": {
"type": "Project"
}
}
}
}
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.Contracts</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.Contracts</RootNamespace>
<IsPackable>true</IsPackable>
<PackageId>FinalFactory.Rendezvous.Contracts</PackageId>
<Description>Versioned transport-neutral contracts for Final Factory Rendezvous.</Description>
</PropertyGroup>
</Project>
@@ -0,0 +1,6 @@
{
"version": 2,
"dependencies": {
".NETStandard,Version=v2.1": {}
}
}
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.Server</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.Server</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<PackageReference Include="LiteNetLib" />
</ItemGroup>
</Project>
@@ -0,0 +1,31 @@
using System.Net;
using FinalFactory.Rendezvous.Server.Transport;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<UdpMediatorOptions>()
.BindConfiguration(UdpMediatorOptions.SectionName)
.ValidateDataAnnotations()
.Validate(
options => IPAddress.TryParse(options.ListenAddress, out _),
$"{UdpMediatorOptions.SectionName}:ListenAddress must be an IP address.")
.ValidateOnStart();
builder.Services.AddSingleton<UdpMediatorService>();
builder.Services.AddHostedService(static services => services.GetRequiredService<UdpMediatorService>());
WebApplication app = builder.Build();
app.MapGet("/health/live", static () => Results.Ok(new { status = "live" }));
app.MapGet(
"/health/ready",
static (UdpMediatorService mediator) => mediator.LocalEndpoint is null
? Results.StatusCode(StatusCodes.Status503ServiceUnavailable)
: Results.Ok(new { status = "ready" }));
await app.RunAsync();
/// <summary>
/// Entry point marker used by integration-test hosts.
/// </summary>
public partial class Program;
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
namespace FinalFactory.Rendezvous.Server.Transport;
/// <summary>
/// Configures the UDP endpoint reserved for the Rendezvous mediator.
/// </summary>
public sealed class UdpMediatorOptions
{
/// <summary>
/// Configuration section containing UDP mediator settings.
/// </summary>
public const string SectionName = "Rendezvous:Udp";
/// <summary>
/// Gets or sets the numeric IP address to bind.
/// </summary>
[Required]
public string ListenAddress { get; set; } = "0.0.0.0";
/// <summary>
/// Gets or sets the UDP port. Zero requests an ephemeral port for tests.
/// </summary>
[Range(0, 65_535)]
public int Port { get; set; } = 9050;
}
@@ -0,0 +1,116 @@
using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Options;
namespace FinalFactory.Rendezvous.Server.Transport;
/// <summary>
/// Owns the cancellable UDP socket used by the future NAT mediator.
/// </summary>
public sealed partial class UdpMediatorService : BackgroundService
{
private readonly ILogger<UdpMediatorService> _logger;
private readonly UdpMediatorOptions _options;
private UdpClient? _udpClient;
/// <summary>
/// Initializes a new UDP mediator service.
/// </summary>
public UdpMediatorService(
IOptions<UdpMediatorOptions> options,
ILogger<UdpMediatorService> logger)
{
_options = options.Value;
_logger = logger;
}
/// <summary>
/// Gets the bound endpoint after startup completes.
/// </summary>
public IPEndPoint? LocalEndpoint { get; private set; }
/// <inheritdoc />
public override Task StartAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_udpClient is not null)
{
throw new InvalidOperationException("The UDP mediator is already running.");
}
IPAddress listenAddress = IPAddress.Parse(_options.ListenAddress);
UdpClient udpClient = new(new IPEndPoint(listenAddress, _options.Port));
_udpClient = udpClient;
IPEndPoint localEndpoint =
(IPEndPoint?)udpClient.Client.LocalEndPoint
?? throw new InvalidOperationException("The UDP socket did not expose its bound endpoint.");
LocalEndpoint = localEndpoint;
LogMediatorListening(_logger, localEndpoint.Address, localEndpoint.Port);
return base.StartAsync(cancellationToken);
}
/// <inheritdoc />
public override async Task StopAsync(CancellationToken cancellationToken)
{
await base.StopAsync(cancellationToken).ConfigureAwait(false);
_udpClient?.Dispose();
_udpClient = null;
LocalEndpoint = null;
LogMediatorStopped(_logger);
}
/// <inheritdoc />
public override void Dispose()
{
_udpClient?.Dispose();
_udpClient = null;
LocalEndpoint = null;
base.Dispose();
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
UdpClient udpClient = _udpClient
?? throw new InvalidOperationException("The UDP mediator socket was not initialized.");
try
{
while (!stoppingToken.IsCancellationRequested)
{
_ = await udpClient.ReceiveAsync(stoppingToken).ConfigureAwait(false);
// Bootstrap deliberately emits no UDP response. Protocol handling lands in #11.
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// Expected during normal shutdown.
}
catch (ObjectDisposedException) when (stoppingToken.IsCancellationRequested)
{
// Disposing the socket is the fallback that releases a blocked receive.
}
finally
{
LocalEndpoint = null;
}
}
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "UDP mediator listening on {ListenAddress}:{ListenPort}")]
private static partial void LogMediatorListening(
ILogger logger,
IPAddress listenAddress,
int listenPort);
[LoggerMessage(
EventId = 2,
Level = LogLevel.Information,
Message = "UDP mediator stopped")]
private static partial void LogMediatorStopped(ILogger logger);
}
@@ -0,0 +1,15 @@
{
"Rendezvous": {
"Udp": {
"ListenAddress": "0.0.0.0",
"Port": 9050
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -0,0 +1,16 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
"finalfactory.rendezvous.contracts": {
"type": "Project"
}
}
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.TestClient</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.TestClient</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj" />
<ProjectReference Include="../FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<PackageReference Include="LiteNetLib" />
</ItemGroup>
</Project>
@@ -0,0 +1,16 @@
namespace FinalFactory.Rendezvous.TestClient;
/// <summary>
/// Bootstrap entry point for the public-SDK-only diagnostic client.
/// </summary>
public static class Program
{
/// <summary>
/// Runs the bootstrap diagnostic.
/// </summary>
public static int Main()
{
Console.WriteLine("Rendezvous TestClient bootstrap is ready.");
return 0;
}
}
@@ -0,0 +1,23 @@
{
"version": 2,
"dependencies": {
"net8.0": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
"finalfactory.rendezvous.client": {
"type": "Project",
"dependencies": {
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
"LiteNetLib": "[2.1.4, )"
}
},
"finalfactory.rendezvous.contracts": {
"type": "Project"
}
}
}
}
@@ -0,0 +1,113 @@
using System.Reflection;
using System.Xml.Linq;
namespace FinalFactory.Rendezvous.Tests.Architecture;
public sealed class DependencyBoundaryTests
{
[Fact]
public void ContractsAreTransportAndEngineIndependent()
{
string[] references = GetReferences("FinalFactory.Rendezvous.Contracts");
Assert.DoesNotContain(references, IsGodotAssembly);
Assert.DoesNotContain(references, IsServerAssembly);
Assert.DoesNotContain(references, IsLiteNetLibAssembly);
}
[Fact]
public void ClientIsGodotAndServerIndependent()
{
string[] references = GetReferences("FinalFactory.Rendezvous.Client");
Assert.DoesNotContain(references, IsGodotAssembly);
Assert.DoesNotContain(references, IsServerAssembly);
}
[Fact]
public void TestClientUsesOnlyPublicRendezvousDependencies()
{
string[] references = GetReferences("FinalFactory.Rendezvous.TestClient");
Assert.DoesNotContain(references, IsGodotAssembly);
Assert.DoesNotContain(references, IsServerAssembly);
}
[Fact]
public void DeclaredDependencyGraphMatchesTheArchitecture()
{
AssertDeclaredDependencies(
"src/FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj",
[],
[]);
AssertDeclaredDependencies(
"src/FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj",
["FinalFactory.Rendezvous.Contracts"],
["LiteNetLib"]);
AssertDeclaredDependencies(
"src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj",
["FinalFactory.Rendezvous.Contracts"],
["LiteNetLib"]);
AssertDeclaredDependencies(
"src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj",
["FinalFactory.Rendezvous.Client", "FinalFactory.Rendezvous.Contracts"],
["LiteNetLib"]);
}
private static void AssertDeclaredDependencies(
string projectPath,
string[] expectedProjects,
string[] expectedPackages)
{
XDocument project = XDocument.Load(Path.Combine(FindRepositoryRoot(), projectPath));
string[] projects = project
.Descendants("ProjectReference")
.Select(static element =>
Path.GetFileNameWithoutExtension(element.Attribute("Include")?.Value) ?? string.Empty)
.Order(StringComparer.Ordinal)
.ToArray();
string[] packages = project
.Descendants("PackageReference")
.Select(static element => element.Attribute("Include")?.Value ?? string.Empty)
.Order(StringComparer.Ordinal)
.ToArray();
Assert.Equal(expectedProjects.Order(StringComparer.Ordinal), projects);
Assert.Equal(expectedPackages.Order(StringComparer.Ordinal), packages);
}
private static string FindRepositoryRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "Rendezvous.slnx")))
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not locate the Rendezvous repository root.");
}
private static string[] GetReferences(string assemblyName) => Assembly
.Load(assemblyName)
.GetReferencedAssemblies()
.Select(static reference => reference.Name ?? string.Empty)
.ToArray();
private static bool IsGodotAssembly(string assemblyName) =>
assemblyName.StartsWith("Godot", StringComparison.OrdinalIgnoreCase);
private static bool IsLiteNetLibAssembly(string assemblyName) =>
string.Equals(assemblyName, "LiteNetLib", StringComparison.Ordinal);
private static bool IsServerAssembly(string assemblyName) =>
string.Equals(
assemblyName,
"FinalFactory.Rendezvous.Server",
StringComparison.Ordinal);
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.Tests</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.Tests</RootNamespace>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj" />
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj" />
<ProjectReference Include="../../src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,34 @@
using System.Net;
using FinalFactory.Rendezvous.Server.Transport;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace FinalFactory.Rendezvous.Tests.Server;
public sealed class UdpMediatorServiceTests
{
[Fact]
public async Task ServiceBindsAnEphemeralUdpPortAndStopsCleanly()
{
using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(5));
UdpMediatorOptions options = new()
{
ListenAddress = IPAddress.Loopback.ToString(),
Port = 0,
};
using UdpMediatorService service = new(
Options.Create(options),
NullLogger<UdpMediatorService>.Instance);
await service.StartAsync(timeout.Token);
IPEndPoint? boundEndpoint = service.LocalEndpoint;
Assert.NotNull(boundEndpoint);
Assert.Equal(IPAddress.Loopback, boundEndpoint.Address);
Assert.InRange(boundEndpoint.Port, 1, 65_535);
await service.StopAsync(timeout.Token);
Assert.Null(service.LocalEndpoint);
}
}
@@ -0,0 +1,129 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"Microsoft.NET.Test.Sdk": {
"type": "Direct",
"requested": "[18.4.0, )",
"resolved": "18.4.0",
"contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==",
"dependencies": {
"Microsoft.CodeCoverage": "18.4.0",
"Microsoft.TestPlatform.TestHost": "18.4.0"
}
},
"xunit": {
"type": "Direct",
"requested": "[2.9.3, )",
"resolved": "2.9.3",
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
"dependencies": {
"xunit.analyzers": "1.18.0",
"xunit.assert": "2.9.3",
"xunit.core": "[2.9.3]"
}
},
"xunit.runner.visualstudio": {
"type": "Direct",
"requested": "[3.1.5, )",
"resolved": "3.1.5",
"contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA=="
},
"Microsoft.CodeCoverage": {
"type": "Transitive",
"resolved": "18.4.0",
"contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow=="
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "18.4.0",
"contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg=="
},
"Microsoft.TestPlatform.TestHost": {
"type": "Transitive",
"resolved": "18.4.0",
"contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==",
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "18.4.0",
"Newtonsoft.Json": "13.0.3"
}
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"xunit.abstractions": {
"type": "Transitive",
"resolved": "2.0.3",
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
},
"xunit.analyzers": {
"type": "Transitive",
"resolved": "1.18.0",
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
},
"xunit.assert": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
},
"xunit.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]",
"xunit.extensibility.execution": "[2.9.3]"
}
},
"xunit.extensibility.core": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
"dependencies": {
"xunit.abstractions": "2.0.3"
}
},
"xunit.extensibility.execution": {
"type": "Transitive",
"resolved": "2.9.3",
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
"dependencies": {
"xunit.extensibility.core": "[2.9.3]"
}
},
"finalfactory.rendezvous.client": {
"type": "Project",
"dependencies": {
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
"LiteNetLib": "[2.1.4, )"
}
},
"finalfactory.rendezvous.contracts": {
"type": "Project"
},
"finalfactory.rendezvous.server": {
"type": "Project",
"dependencies": {
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
"LiteNetLib": "[2.1.4, )"
}
},
"finalfactory.rendezvous.testclient": {
"type": "Project",
"dependencies": {
"FinalFactory.Rendezvous.Client": "[1.0.0, )",
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
"LiteNetLib": "[2.1.4, )"
}
},
"LiteNetLib": {
"type": "CentralTransitive",
"requested": "[2.1.4, )",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
}
}
}
}