# Transport Plugin Development Guide This guide explains how to create custom router transport plugins for StellaOps. ## Overview Router transport plugins implement the `IRouterTransportPlugin` interface to provide custom communication protocols. The plugin system enables: - Runtime loading of transport implementations - Isolation from the Gateway codebase - Hot-swappable transports (restart required) - Third-party transport extensions ## Prerequisites - .NET 10 SDK - Understanding of async socket programming - Familiarity with dependency injection ## Creating a Transport Plugin ### Step 1: Create Project ```bash mkdir MyCompany.Router.Transport.Custom cd MyCompany.Router.Transport.Custom dotnet new classlib -f net10.0 dotnet add package Microsoft.Extensions.Configuration.Binder dotnet add package Microsoft.Extensions.DependencyInjection.Abstractions dotnet add package Microsoft.Extensions.Logging.Abstractions dotnet add package Microsoft.Extensions.Options ``` Add project reference to Router.Common: ```xml ``` ### Step 2: Create Options Class ```csharp namespace MyCompany.Router.Transport.Custom; /// /// Configuration options for the custom transport. /// public sealed class CustomTransportOptions { /// /// Host address to bind/connect to. /// public string Host { get; set; } = "0.0.0.0"; /// /// Port number. /// public int Port { get; set; } = 5200; /// /// Connection timeout. /// public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromSeconds(30); /// /// Maximum concurrent connections. /// public int MaxConnections { get; set; } = 1000; } ``` ### Step 3: Implement Transport Server ```csharp using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Router.Common.Abstractions; namespace MyCompany.Router.Transport.Custom; public sealed class CustomTransportServer : ITransportServer { private readonly CustomTransportOptions _options; private readonly ILogger _logger; public CustomTransportServer( IOptions options, ILogger logger) { _options = options.Value; _logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting custom transport server on {Host}:{Port}", _options.Host, _options.Port); // Initialize your transport server (socket, listener, etc.) // ... } public async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Stopping custom transport server"); // Graceful shutdown // ... } public async Task AcceptAsync(CancellationToken cancellationToken) { // Accept incoming connection // Return ITransportConnection implementation throw new NotImplementedException(); } public void Dispose() { // Cleanup resources } } ``` ### Step 4: Implement Transport Client ```csharp using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Router.Common.Abstractions; namespace MyCompany.Router.Transport.Custom; public sealed class CustomTransportClient : ITransportClient, IMicroserviceTransport { private readonly CustomTransportOptions _options; private readonly ILogger _logger; public CustomTransportClient( IOptions options, ILogger logger) { _options = options.Value; _logger = logger; } public async Task ConnectAsync( string host, int port, CancellationToken cancellationToken) { _logger.LogDebug("Connecting to {Host}:{Port}", host, port); // Establish connection // Return ITransportConnection implementation throw new NotImplementedException(); } public async Task DisconnectAsync(CancellationToken cancellationToken) { // Disconnect from server } public void Dispose() { // Cleanup resources } } ``` ### Step 5: Implement Connection ```csharp using StellaOps.Router.Common.Abstractions; namespace MyCompany.Router.Transport.Custom; public sealed class CustomTransportConnection : ITransportConnection { public string ConnectionId { get; } = Guid.NewGuid().ToString("N"); public bool IsConnected { get; private set; } public EndPoint? RemoteEndPoint { get; private set; } public async Task SendAsync( ReadOnlyMemory data, CancellationToken cancellationToken) { // Send data over transport throw new NotImplementedException(); } public async Task ReceiveAsync( Memory buffer, CancellationToken cancellationToken) { // Receive data from transport throw new NotImplementedException(); } public async Task CloseAsync(CancellationToken cancellationToken) { IsConnected = false; // Close connection } public void Dispose() { // Cleanup } } ``` ### Step 6: Implement Plugin ```csharp using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using StellaOps.Router.Common.Abstractions; using StellaOps.Router.Common.Plugins; namespace MyCompany.Router.Transport.Custom; /// /// Plugin implementation for custom transport. /// public sealed class CustomTransportPlugin : IRouterTransportPlugin { /// public string TransportName => "custom"; /// public string DisplayName => "Custom Transport"; /// public bool IsAvailable(IServiceProvider services) { // Check if required dependencies are available // Return false if transport cannot be used in current environment return true; } /// public void Register(RouterTransportRegistrationContext context) { var services = context.Services; var configuration = context.Configuration; // Bind configuration var configSection = context.ConfigurationSection is not null ? configuration.GetSection(context.ConfigurationSection) : configuration.GetSection("Router:Transport:Custom"); services.AddOptions(); if (configSection.GetChildren().Any()) { services.Configure(options => { configSection.Bind(options); }); } // Register server if requested if (context.Mode.HasFlag(RouterTransportMode.Server)) { services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); } // Register client if requested if (context.Mode.HasFlag(RouterTransportMode.Client)) { services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); } } } ``` ## Building and Packaging ### Build Configuration Add to your `.csproj`: ```xml net10.0 enable enable MyCompany.Router.Transport.Custom true $(SolutionDir)plugins\router\transports\ false ``` ### Build Commands ```bash # Debug build dotnet build # Release build to plugins directory dotnet build -c Release # Publish with all dependencies dotnet publish -c Release -o ./publish ``` ## Configuration Schema Create `plugin.json` manifest: ```json { "schemaVersion": "2.0", "id": "mycompany.router.transport.custom", "name": "Custom Transport", "version": "1.0.0", "assembly": { "path": "MyCompany.Router.Transport.Custom.dll", "entryType": "MyCompany.Router.Transport.Custom.CustomTransportPlugin" }, "capabilities": ["server", "client", "streaming"], "platforms": ["linux-x64", "win-x64", "osx-arm64"], "enabled": true, "priority": 100 } ``` Create `config.yaml` for runtime configuration: ```yaml id: mycompany.router.transport.custom name: Custom Transport enabled: true config: host: "0.0.0.0" port: 5200 maxConnections: 1000 connectTimeout: "00:00:30" ``` ## Testing ### Unit Tests ```csharp public class CustomTransportPluginTests { [Fact] public void TransportName_ReturnsCustom() { var plugin = new CustomTransportPlugin(); Assert.Equal("custom", plugin.TransportName); } [Fact] public void Register_AddsServerServices() { var plugin = new CustomTransportPlugin(); var services = new ServiceCollection(); var config = new ConfigurationBuilder().Build(); var context = new RouterTransportRegistrationContext( services, config, RouterTransportMode.Server); plugin.Register(context); var provider = services.BuildServiceProvider(); Assert.NotNull(provider.GetService()); } } ``` ### Integration Tests ```csharp public class CustomTransportIntegrationTests { [Fact] public async Task Server_AcceptsConnections() { var services = new ServiceCollection(); services.AddLogging(); services.Configure(opts => { opts.Port = 15200; // Test port }); services.AddSingleton(); var provider = services.BuildServiceProvider(); var server = provider.GetRequiredService(); await server.StartAsync(CancellationToken.None); // Connect client and verify // ... await server.StopAsync(CancellationToken.None); } } ``` ## Best Practices ### Error Handling ```csharp public async Task ConnectAsync( string host, int port, CancellationToken cancellationToken) { try { // Attempt connection return await ConnectInternalAsync(host, port, cancellationToken); } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused) { _logger.LogWarning("Connection refused to {Host}:{Port}", host, port); throw new TransportConnectionException($"Connection refused to {host}:{port}", ex); } catch (OperationCanceledException) { _logger.LogDebug("Connection attempt cancelled"); throw; } catch (Exception ex) { _logger.LogError(ex, "Failed to connect to {Host}:{Port}", host, port); throw new TransportConnectionException($"Failed to connect to {host}:{port}", ex); } } ``` ### Resource Management ```csharp public sealed class CustomTransportServer : ITransportServer, IAsyncDisposable { private readonly SemaphoreSlim _connectionLock = new(1, 1); private readonly List _connections = []; private bool _disposed; public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; await _connectionLock.WaitAsync(); try { foreach (var conn in _connections) { await conn.CloseAsync(CancellationToken.None); conn.Dispose(); } _connections.Clear(); } finally { _connectionLock.Release(); _connectionLock.Dispose(); } } } ``` ### Logging ```csharp // Use structured logging _logger.LogInformation( "Connection established {ConnectionId} from {RemoteEndPoint}", connection.ConnectionId, connection.RemoteEndPoint); // Include correlation IDs using (_logger.BeginScope(new Dictionary { ["ConnectionId"] = connectionId, ["TraceId"] = Activity.Current?.TraceId.ToString() ?? "N/A" })) { await ProcessConnectionAsync(connection, cancellationToken); } ``` ## Deployment ### Copy to Plugins Directory ```bash cp ./publish/*.dll /opt/stellaops/plugins/router/transports/ ``` ### Verify Plugin Loading ```bash # Check logs for plugin discovery grep "Loaded router transport plugin" /var/log/stellaops/gateway.log ``` ### Configuration ```yaml # router.yaml Router: Transport: Type: custom Custom: Host: "0.0.0.0" Port: 5200 ``` ## See Also - [Transport Overview](./README.md) - [Plugin SDK Guide](../../10_PLUGIN_SDK_GUIDE.md) - [IRouterTransportPlugin API](../../../src/Router/__Libraries/StellaOps.Router.Common/Plugins/IRouterTransportPlugin.cs)