using System.Text; using XFESystem.NativeRuntime.Application.Web; namespace XFESystem.HttpProtocol.Tests; public sealed class IndependentConnectionReuseTests { [Theory] [InlineData("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK", true)] [InlineData("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nOK\r\n0\r\n\r\n", true)] [InlineData("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: keep-alive, Close\r\n\r\nOK", false)] [InlineData("HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nOK", false)] [InlineData("HTTP/1.1 200 OK\r\n\r\nOK", false)] [InlineData("HTTP/1.1 302 Found\r\nLocation: /next\r\nContent-Length: 4\r\n\r\nmove", false)] [InlineData("HTTP/1.1 204 No Content\r\n\r\n", false)] [InlineData("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOKunsolicited", false)] public void OnlyFullyConsumedPersistentResponsesCanBeReused(string wire, bool reusable) { // Every possible boundary must preserve the actual HTTP body. Surplus // bytes only disqualify a wire when actually buffered by this reader. byte[] bytes = Encoding.UTF8.GetBytes(wire); for (int packet = 1; packet <= bytes.Length; packet++) { using var stream = new MemoryStream(bytes); var response = NativeHttpProtocol.ReadResponse((b, o, n) => stream.Read(b, o, Math.Min(n, packet)), 1024); if (!wire.EndsWith("unsolicited", StringComparison.Ordinal) || packet == bytes.Length) Assert.Equal(reusable, response.CanReuseConnection); } } [Fact] public void SameVerifiedOriginReusesWireAndInstallsTheNewAbsoluteDeadline() { var pool = new NativeHttpConnectionPool(); int opens = 0, closes = 0; long installed = 0; var address = new Uri("https://example.test/a"); NativeHttpConnectionPool.Connection Open() { opens++; return new(address, new MemoryStream(), d => installed = d, () => closes++, true, "TLS 1.3"); } var first = pool.Take(address, 10, 500, Open, out bool reused); Assert.False(reused); Assert.Equal(500, installed); pool.Return(first, true, 20); var second = pool.Take(new("https://EXAMPLE.test/b"), 30, 1000, Open, out reused); Assert.True(reused); Assert.Same(first, second); Assert.Equal(1000, installed); Assert.Equal(1, opens); Assert.Equal(0, closes); pool.Return(second, false, 40); Assert.Equal(1, closes); } [Theory] [InlineData("https://other.test/", 20, true)] [InlineData("http://example.test/", 20, true)] [InlineData("https://example.test:444/", 20, true)] [InlineData("https://example.test/", 10_011, true)] [InlineData("https://example.test/", 0, true)] [InlineData("https://example.test/", 20, false)] public void ChangedOriginExpiredClockOrUnverifiedTlsClosesBeforeOpening(string target, long now, bool verified) { var pool = new NativeHttpConnectionPool(); bool closed = false; var previous = new NativeHttpConnectionPool.Connection(new("https://example.test/"), new MemoryStream(), _ => { }, () => closed = true, verified, "TLS 1.2"); pool.Return(previous, true, 10); var fresh = pool.Take(new(target), now, 20000, () => { Assert.True(closed); return new(new(target), new MemoryStream(), _ => { }, () => { }, true, "TLS 1.3"); }, out bool reused); Assert.False(reused); Assert.NotSame(previous, fresh); pool.Return(fresh, false, now); } [Fact] public void ClosingAndFailedDeadlineSetupReleaseTheOwnerOnce() { var pool = new NativeHttpConnectionPool(); int closes = 0; var connection = new NativeHttpConnectionPool.Connection(new("http://example.test/"), new MemoryStream(), _ => throw new IOException("deadline"), () => closes++, false, "HTTP"); Assert.Throws(() => pool.Take(connection.Origin, 0, 20, () => connection, out _)); connection.Close(); Assert.Equal(1, closes); } [Fact] public void IndependentParserStillRejectsAmbiguousOrTruncatedFraming() { foreach (string wire in new[] { "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nTransfer-Encoding: chunked\r\n\r\nOK", "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nOK" }) { using var stream = new MemoryStream(Encoding.ASCII.GetBytes(wire)); Assert.Throws(() => NativeHttpProtocol.ReadResponse(stream.Read, 1024)); } } }