using System.Buffers.Binary; using System.Net.Sockets; using Tpm2Lib; namespace DodoSSH.Client.App.Tests; /// /// Talks to a software TPM over its TCP data channel, so the real store can be driven without hardware. /// /// /// /// TSS.Net ships a TcpTpmDevice and it cannot be used here. It also opens the simulator's /// platform socket and exchanges a handshake that the Microsoft reference simulator answers and /// swtpm does not, so Connect() blocks forever — with no error and no timeout. That was found by /// watching it hang, and it is the whole reason this file exists. /// /// /// The data channel is the framing tcti-mssim uses and swtpm does implement: a command word, a /// locality byte, a length and the bytes, answered by a length, the bytes and an acknowledgement word. /// Power management is unnecessary when swtpm is started with --flags not-need-init,startup-clear, /// which is the invocation documents. /// /// internal sealed class SoftwareTpm : Tpm2Device { /// tcti-mssim's "here is a command for the TPM". private const uint SendCommand = 8; private readonly string host; private readonly int port; private TcpClient? client; private NetworkStream? stream; internal SoftwareTpm(string host, int port) { this.host = host; this.port = port; } public override void Connect() { client = new TcpClient(); client.Connect(host, port); stream = client.GetStream(); } public override void Close() { stream?.Dispose(); client?.Dispose(); stream = null; client = null; } public override void DispatchCommand(CommandModifier active, byte[] inBuf, out byte[] outBuf) { ArgumentNullException.ThrowIfNull(active); ArgumentNullException.ThrowIfNull(inBuf); if (stream is null) { throw new InvalidOperationException("Not connected."); } Span header = stackalloc byte[9]; BinaryPrimitives.WriteUInt32BigEndian(header, SendCommand); header[4] = active.ActiveLocality; BinaryPrimitives.WriteUInt32BigEndian(header[5..], (uint)inBuf.Length); stream.Write(header); stream.Write(inBuf, 0, inBuf.Length); stream.Flush(); outBuf = ReadExactly((int)ReadUInt32()); // The trailing acknowledgement word. It has to be consumed even though nothing reads it, or every // response after this one is misframed by four bytes. _ = ReadUInt32(); } protected override void Dispose(bool disposing) { if (disposing) { Close(); } base.Dispose(disposing); } private uint ReadUInt32() => BinaryPrimitives.ReadUInt32BigEndian(ReadExactly(4)); private byte[] ReadExactly(int count) { var buffer = new byte[count]; var read = 0; while (read < count) { var got = stream!.Read(buffer, read, count - read); if (got <= 0) { throw new IOException("The TPM closed the connection."); } read += got; } return buffer; } }