using DodoSSH.Client.Ssh; namespace DodoSSH.Client.App.Tests; /// /// An SSH layer that connects to nothing and hands out shells that simply stay open. /// /// /// This suite has no network and no sshd; what it needs from a session is only that one can exist /// and outlive a lock. The real factory was here before and was never called by anything, which made the /// suite's independence from the network a coincidence rather than a property. /// internal sealed class IdleSshConnectionFactory : ISshConnectionFactory { /// public Task ConnectAsync( SshConnectionRequest request, CancellationToken cancellationToken) => Task.FromResult(new IdleConnection(request)); } /// A connection whose shells never end by themselves. internal sealed class IdleConnection(SshConnectionRequest request) : ISshConnection { /// public bool IsConnected { get; private set; } = true; /// public HostKeyPresentation HostKey { get; } = new(request.Host, request.Port, "ssh-ed25519", "SHA256:idle"); /// public Task OpenShellAsync( TerminalSize size, CancellationToken cancellationToken) => Task.FromResult(new IdleShell()); /// public ValueTask DisposeAsync() { IsConnected = false; return ValueTask.CompletedTask; } } /// /// A shell that is connected, silent, and ends only when something ends it. /// /// /// Which is what the interesting case looks like from the host's side: a remote sitting in the middle of /// a long job produces nothing for minutes and must not be mistaken for one that has exited. /// internal sealed class IdleShell : ISshShellSession { /// public bool IsOpen { get; private set; } = true; /// public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { // Never returns 0 of its own accord: 0 would tell the pump the remote closed the channel, which // is the opposite of what this stands in for. await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, cancellationToken) .ConfigureAwait(false); return 0; } /// public ValueTask WriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) => ValueTask.CompletedTask; /// public void Resize(TerminalSize size) { } /// public ValueTask DisposeAsync() { IsOpen = false; return ValueTask.CompletedTask; } }