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<IOException>(() => 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<IOException>(() => NativeHttpProtocol.ReadResponse(stream.Read, 1024));
}
}
}
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<IOException>(() => 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<IOException>(() => NativeHttpProtocol.ReadResponse(stream.Read, 1024));
}
}
}