diff --git a/Magma.sln b/Magma.sln
index de00e04..886c879 100644
--- a/Magma.sln
+++ b/Magma.sln
@@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.WinTun.TcpHost", "sam
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.AF_XDP", "src\Magma.AF_XDP\Magma.AF_XDP.csproj", "{5922914B-D7E6-4A23-9A26-5589FD72727B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.WinTun.Facts", "test\Magma.WinTun.Facts\Magma.WinTun.Facts.csproj", "{8B93DD2C-0C6B-4440-9126-2AF232478660}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -307,6 +309,18 @@ Global
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x64.Build.0 = Release|Any CPU
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.ActiveCfg = Release|Any CPU
{5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.Build.0 = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x64.Build.0 = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x86.Build.0 = Debug|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x64.ActiveCfg = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x64.Build.0 = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x86.ActiveCfg = Release|Any CPU
+ {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -332,6 +346,7 @@ Global
{503140F9-24F6-41B5-89F2-BB0FB24CCB2E} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
{845A5A0B-E59B-46AF-BF95-4FC50D660967} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0}
{5922914B-D7E6-4A23-9A26-5589FD72727B} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0}
+ {8B93DD2C-0C6B-4440-9126-2AF232478660} = {B1BA53C8-CCCF-46D5-BA9F-6031811F2E19}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {99D656D2-FC86-462A-BB4C-610D644ADC62}
diff --git a/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj b/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj
new file mode 100644
index 0000000..bd78be3
--- /dev/null
+++ b/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net10.0
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Magma.WinTun.Facts/README.md b/test/Magma.WinTun.Facts/README.md
new file mode 100644
index 0000000..969b419
--- /dev/null
+++ b/test/Magma.WinTun.Facts/README.md
@@ -0,0 +1,57 @@
+# Magma.WinTun.Facts
+
+Test suite for the WinTun module.
+
+## Running Tests
+
+### Unit Tests
+
+Unit tests run on all platforms (Windows and Linux) and do not require the WinTun driver:
+
+```bash
+dotnet test test/Magma.WinTun.Facts
+```
+
+### Integration Tests
+
+Integration tests require:
+- Windows operating system
+- WinTun driver installed
+- A configured TAP adapter
+
+Integration tests are **skipped by default**. To enable them:
+
+1. Set the environment variable:
+ ```powershell
+ $env:WINTUN_INTEGRATION_TESTS="1"
+ ```
+
+2. (Optional) Specify a custom adapter name:
+ ```powershell
+ $env:WINTUN_ADAPTER_NAME="YourAdapterName"
+ ```
+
+3. Run the tests:
+ ```bash
+ dotnet test test/Magma.WinTun.Facts
+ ```
+
+Or run only integration tests:
+```bash
+dotnet test test/Magma.WinTun.Facts --filter Category=Integration
+```
+
+## Test Categories
+
+- **Unit Tests**: Test public APIs without requiring the WinTun driver
+ - `WinIOFacts`: Constants and P/Invoke declarations
+ - `WinTunTransportReceiverFacts`: Transport receiver interface
+ - `WinTunMemoryPoolFacts`: Memory pool lifecycle and management
+
+- **Integration Tests** (Category="Integration"): Require WinTun driver
+ - `WinTunPortIntegrationFacts`: Adapter create/destroy lifecycle
+
+## Platform Support
+
+- **Linux**: Unit tests run and pass. Integration tests are skipped.
+- **Windows**: All tests can run if WinTun driver is installed and integration tests are enabled.
diff --git a/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs b/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs
new file mode 100644
index 0000000..56aaeff
--- /dev/null
+++ b/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Buffers;
+using System.Threading.Tasks;
+using Magma.WinTun.Internal;
+using Xunit;
+
+namespace Magma.WinTun.Facts
+{
+ public class WinTunMemoryPoolFacts
+ {
+ [Fact]
+ public void PoolCreatesBuffersOfCorrectSize()
+ {
+ var poolSize = 10;
+ var bufferSize = 2000;
+ var pool = new WinTunMemoryPool(poolSize, bufferSize);
+
+ var success = pool.TryGetMemory(out var memory);
+
+ Assert.True(success);
+ Assert.NotNull(memory);
+ Assert.Equal(bufferSize, memory.Memory.Length);
+
+ memory.Return();
+ }
+
+ [Fact]
+ public void PoolSupportsMultipleBuffers()
+ {
+ var poolSize = 5;
+ var bufferSize = 1500;
+ var pool = new WinTunMemoryPool(poolSize, bufferSize);
+
+ var buffers = new WinTunMemoryPool.WinTunOwnedMemory[poolSize];
+
+ for (var i = 0; i < poolSize; i++)
+ {
+ var success = pool.TryGetMemory(out buffers[i]);
+ Assert.True(success);
+ Assert.Equal(bufferSize, buffers[i].Memory.Length);
+ }
+
+ var exhausted = pool.TryGetMemory(out _);
+ Assert.False(exhausted);
+
+ for (var i = 0; i < poolSize; i++)
+ {
+ buffers[i].Return();
+ }
+ }
+
+ [Fact]
+ public void ReturnedBufferCanBeReused()
+ {
+ var pool = new WinTunMemoryPool(2, 1000);
+
+ var success1 = pool.TryGetMemory(out var memory1);
+ Assert.True(success1);
+
+ memory1.Return();
+
+ var success2 = pool.TryGetMemory(out var memory2);
+ Assert.True(success2);
+
+ memory2.Return();
+ }
+
+ [Fact]
+ public void TryGetMemoryReturnsFalseWhenExhausted()
+ {
+ var pool = new WinTunMemoryPool(1, 1000);
+
+ var success1 = pool.TryGetMemory(out var memory1);
+ Assert.True(success1);
+
+ var success2 = pool.TryGetMemory(out var memory2);
+ Assert.False(success2);
+ Assert.Null(memory2);
+
+ memory1.Return();
+ }
+
+ [Fact]
+ public async Task GetMemoryAsyncWaitsWhenPoolIsEmpty()
+ {
+ var pool = new WinTunMemoryPool(1, 1000);
+
+ var memory1 = await pool.GetMemoryAsync();
+
+ var getTask = pool.GetMemoryAsync();
+ await Task.Delay(50);
+ Assert.False(getTask.IsCompleted);
+
+ memory1.Return();
+
+ var memory2 = await getTask;
+ Assert.NotNull(memory2);
+
+ memory2.Return();
+ }
+
+ [Fact]
+ public void MemoryOwnerGetSpanReturnsCorrectSpan()
+ {
+ var pool = new WinTunMemoryPool(1, 1500);
+ var success = pool.TryGetMemory(out var memory);
+
+ Assert.True(success);
+ var span = memory.GetSpan();
+ Assert.Equal(1500, span.Length);
+
+ memory.Return();
+ }
+ }
+}
diff --git a/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs b/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs
new file mode 100644
index 0000000..685c272
--- /dev/null
+++ b/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Runtime.InteropServices;
+using Magma.Network.Abstractions;
+using Magma.WinTun.Internal;
+using Xunit;
+
+namespace Magma.WinTun.Facts
+{
+ [Trait("Category", "Integration")]
+ public class WinTunPortIntegrationFacts
+ {
+ private static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ private static bool IntegrationTestsEnabled =>
+ !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WINTUN_INTEGRATION_TESTS"));
+
+ [Fact(Skip = "Requires WinTun driver and adapter - set WINTUN_INTEGRATION_TESTS=1 to enable")]
+ public void CanCreateWinTunPort()
+ {
+ if (!IsWindows || !IntegrationTestsEnabled)
+ {
+ return;
+ }
+
+ var adapterName = Environment.GetEnvironmentVariable("WINTUN_ADAPTER_NAME") ?? "WinTun";
+
+ using (var port = new WinTunPort(
+ adapterName,
+ transmitter => new TestPacketReceiver()))
+ {
+ Assert.NotNull(port);
+ }
+ }
+
+ [Fact(Skip = "Requires WinTun driver and adapter - set WINTUN_INTEGRATION_TESTS=1 to enable")]
+ public void WinTunPortDisposesCleanly()
+ {
+ if (!IsWindows || !IntegrationTestsEnabled)
+ {
+ return;
+ }
+
+ var adapterName = Environment.GetEnvironmentVariable("WINTUN_ADAPTER_NAME") ?? "WinTun";
+
+ var port = new WinTunPort(
+ adapterName,
+ transmitter => new TestPacketReceiver());
+
+ port.Dispose();
+
+ port.Dispose();
+ }
+
+ private class TestPacketReceiver : IPacketReceiver
+ {
+ public void FlushPendingAcks()
+ {
+ }
+
+ public T TryConsume(T input) where T : System.Buffers.IMemoryOwner
+ {
+ return default;
+ }
+ }
+ }
+}
diff --git a/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs b/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs
new file mode 100644
index 0000000..e60c711
--- /dev/null
+++ b/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Buffers;
+using Magma.WinTun;
+using Xunit;
+
+namespace Magma.WinTun.Facts
+{
+ public class WinTunTransportReceiverFacts
+ {
+ [Fact]
+ public void CanInstantiateWinTunTransportReceiver()
+ {
+ var receiver = new WinTunTransportReceiver();
+ Assert.NotNull(receiver);
+ }
+
+ [Fact]
+ public void FlushPendingAcksThrowsNotImplementedException()
+ {
+ var receiver = new WinTunTransportReceiver();
+ Assert.Throws(() => receiver.FlushPendingAcks());
+ }
+
+ [Fact]
+ public void TryConsumeThrowsNotImplementedException()
+ {
+ var receiver = new WinTunTransportReceiver();
+ using var memory = MemoryPool.Shared.Rent(100);
+
+ Assert.Throws(() => receiver.TryConsume(memory));
+ }
+ }
+}