From c8f66886bafd89e7ac7c97532c0fc2f81d65da87 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:34:49 +0000 Subject: [PATCH 1/7] Add scaffolding for SSRP response parsing This consists of: * ReadOnlySequenceSegment derivative, * Utilities to read data from a ReadOnlySequence * Test cases --- .../src/Microsoft.Data.SqlClient.csproj | 6 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 6 + .../src/Microsoft/Data/Common/PacketBuffer.cs | 31 ++ .../Data/Common/ReadOnlySequenceUtilities.cs | 92 ++++ .../src/System/Diagnostics/CodeAnalysis.cs | 8 + .../Data/Sql/DacResponseProcessorTest.cs | 36 ++ .../Sql/SqlDataSourceResponseProcessorTest.cs | 62 +++ .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 501 ++++++++++++++++++ 8 files changed, 742 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 6332d1d8c4..c170074782 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -87,6 +87,12 @@ Microsoft\Data\Common\NameValuePair.cs + + Microsoft\Data\Common\PacketBuffer.cs + + + Microsoft\Data\Common\ReadOnlySequenceUtilities.cs + Microsoft\Data\DataException.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index fc00117bd7..dcaea53932 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -262,6 +262,12 @@ Microsoft\Data\Common\NameValuePair.cs + + Microsoft\Data\Common\PacketBuffer.cs + + + Microsoft\Data\Common\ReadOnlySequenceUtilities.cs + Microsoft\Data\DataException.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs new file mode 100644 index 0000000000..079c2933a5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; + +#nullable enable + +namespace Microsoft.Data.Common; + +/// +/// One buffer, which may contain one unparsed packet from a single destination. +/// +internal sealed class PacketBuffer : ReadOnlySequenceSegment +{ + public PacketBuffer(ReadOnlyMemory buffer, PacketBuffer? previous) + { + Memory = buffer; + + if (previous is not null) + { + previous.Next = this; + RunningIndex = previous.RunningIndex + previous.Memory.Length; + } + else + { + RunningIndex = 0; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs new file mode 100644 index 0000000000..792ca6ddfc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Buffers.Binary; + +namespace Microsoft.Data.Common; + +internal static class ReadOnlySequenceUtilities +{ + /// + /// Reads the next byte from the sequence, advancing its position by one byte. + /// + /// The sequence to read and to advance from. + /// The first span in the sequence. Reassigned if the next byte can only be read from the next span. + /// Current position in the sequence. Advanced by one byte following a successful read. + /// The value read from . + /// true if was long enough to retrieve the next byte, false otherwise. + public static bool ReadByte(this ref ReadOnlySequence sequence, ref ReadOnlySpan currSpan, ref long currPos, out byte value) + { + if (sequence.Length < sizeof(byte)) + { + value = default; + return false; + } + + currPos += sizeof(byte); + if (currSpan.Length >= sizeof(byte)) + { + value = currSpan[0]; + + sequence = sequence.Slice(sizeof(byte)); + currSpan = currSpan.Slice(sizeof(byte)); + + return true; + } + else + { + Span buffer = stackalloc byte[sizeof(byte)]; + + sequence.Slice(0, sizeof(byte)).CopyTo(buffer); + value = buffer[0]; + + sequence = sequence.Slice(sizeof(byte)); + currSpan = sequence.First.Span; + + return true; + } + } + + /// + /// Reads the next two bytes from the sequence as a , advancing its position by two bytes. + /// + /// The sequence to read and to advance from. + /// The first span in the sequence. Reassigned if the next two bytes can only be read from the next span. + /// Current position in the sequence. Advanced by two bytes following a successful read. + /// The value read from + /// true if was long enough to retrieve the next two bytes, false otherwise. + public static bool ReadLittleEndian(this ref ReadOnlySequence sequence, ref ReadOnlySpan currSpan, ref long currPos, out ushort value) + { + if (sequence.Length < sizeof(ushort)) + { + value = default; + return false; + } + + currPos += sizeof(ushort); + if (currSpan.Length >= sizeof(ushort)) + { + value = BinaryPrimitives.ReadUInt16LittleEndian(currSpan); + + sequence = sequence.Slice(sizeof(ushort)); + currSpan = currSpan.Slice(sizeof(ushort)); + + return true; + } + else + { + Span buffer = stackalloc byte[sizeof(ushort)]; + + sequence.Slice(0, sizeof(ushort)).CopyTo(buffer); + value = BinaryPrimitives.ReadUInt16LittleEndian(buffer); + + sequence = sequence.Slice(sizeof(ushort)); + currSpan = sequence.First.Span; + + return true; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs index ffb0003f64..8eb8e3429d 100644 --- a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs +++ b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs @@ -43,5 +43,13 @@ public MemberNotNullWhenAttribute(bool returnValue, params string[] members) internal sealed class NotNullAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + public bool ReturnValue { get; } + } #endif } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs new file mode 100644 index 0000000000..65cd8f2553 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +public class DacResponseProcessorTest +{ + [Theory] + [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESP_DACPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESP_DACPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_ValidDacResponse_ReturnsData(ReadOnlySequence packetBuffers, int expectedDacPort) + { + _ = packetBuffers; + _ = expectedDacPort; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs new file mode 100644 index 0000000000..04ffde489b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +public class SqlDataSourceResponseProcessorTest +{ + [Theory] + [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.InvalidRESP_DATAPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_InvalidSqlDataSourceResponse_RESP_DATA_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.InvalidTCP_INFOPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_InvalidSqlDataSourceResponse_TCP_INFO_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.Invalid_CLNT_UCAST_INST_SVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_InvalidSqlDataSourceResponseToCLNT_UCAST_INST_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory] + [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESPPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] + public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName) + { + _ = packetBuffers; + _ = expectedVersion; + _ = expectedTcpPort; + _ = expectedPipeName; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs new file mode 100644 index 0000000000..1fd4e1e5bf --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -0,0 +1,501 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Data.Common; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +/// +/// Test cases used to verify the successful processing of valid SSRP responses and the silent +/// discarding of invalid SSRP responses. +/// +internal static class SsrpPacketTestData +{ + /// + /// One empty packet buffer, which should be successfully processed and contain zero responses. + /// + /// + /// + public static TheoryData> EmptyPacketBuffer => + new(GeneratePacketBuffers([])); + + /// + /// Various combinations of packet buffers containing normal SVR_RESP responses, all of which + /// should be successfully processed. + /// + /// + public static TheoryData, string, int, string?> ValidSVR_RESPPacketBuffer + { + get + { + byte[] complexValidPacket = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1433", npInfo: @"np;\\svr1\pipe\SampléPipeName", + viaInfo: "via;svr1 1:1433", rpcInfo: "rpc;svr1", spxInfo: "spx;MSSQLSERVER", + adspInfo: "adsp;SQL2000", bvInfo: "bv;item;group;item;group;org", otherParameters: null))); + byte[] validPacket1 = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1433", null, null, null, null, null, null, null))); + byte[] validPacket2 = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1434", null, null, null, null, null, null, null))); + byte[] validPacket3 = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1435", null, null, null, null, null, null, null))); + byte[] validPacket4 = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1436", null, null, null, null, null, null, null))); + byte[] invalidPacket1 = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "v14", + CreateProtocolParameters(tcpInfo: "tcp;1433", null, null, null, null, null, null, null))); + + return new() + { + // One buffer, one response + { GeneratePacketBuffers( + complexValidPacket + ), "14.0.0.0", 1433, @"\\svr1\pipe\SampléPipeName" }, + // One response, split into four buffers in the middle of a string + { GeneratePacketBuffers( + complexValidPacket.AsSpan(0, 14).ToArray(), + complexValidPacket.AsSpan(14, 22).ToArray(), + complexValidPacket.AsSpan(36, 71).ToArray(), + // Position 107 is the second byte of the é character when encoded to UTF8. + complexValidPacket.AsSpan(107).ToArray() + ), "14.0.0.0", 1433, @"\\svr1\pipe\SampléPipeName" }, + // Four responses, each with different methods + { GeneratePacketBuffers( + validPacket1, validPacket2, validPacket3, validPacket4 + ), "14.0.0.0", 1436, null }, + // Five responses, with response three invalid + { GeneratePacketBuffers( + complexValidPacket, validPacket2, invalidPacket1, validPacket3, validPacket4 + ), "14.0.0.0", 1436, null } + }; + } + } + + /// + /// Various combinations of packet buffers containing SVR_RESP (DAC) responses, all of which + /// should be successfully processed. + /// + /// + public static TheoryData, int> ValidSVR_RESP_DACPacketBuffer + { + get + { + byte[] validPacket1 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1434)); + byte[] validPacket2 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1435)); + byte[] validPacket3 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1436)); + byte[] validPacket4 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1437)); + byte[] invalidPacket1 = FormatSVR_RESPMessage(0x05, 0x03, CreateRESP_DATA(0x01, 1434)); + + return new() + { + // One buffer, one response + { GeneratePacketBuffers(validPacket1), 1434 }, + + // One response, split into three buffers + { GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + validPacket1.AsSpan(4).ToArray()), 1434 }, + + // Two responses with trailing data + { GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + [..validPacket1.AsSpan(4).ToArray(), 0x05], + validPacket2.AsSpan(0, 2).ToArray(), + validPacket2.AsSpan(2).ToArray(), + [0x05]), 1435 }, + + // Four responses, each with different DAC ports + { GeneratePacketBuffers(validPacket1, validPacket2, validPacket3, validPacket4), 1437 }, + + // Five responses, with response three invalid + { GeneratePacketBuffers(validPacket1, validPacket2, invalidPacket1, validPacket3, validPacket4), 1437 }, + + // Four responses, with three extraneous 0x05 bytes between responses 2 and 3 + { GeneratePacketBuffers(validPacket1, [..validPacket2, 0x05], [0x05], [0x05, ..validPacket3], validPacket4), 1437 } + }; + } + } + + /// + /// Packet buffers containing nothing but invalid SVR_RESP (DAC) responses. + /// + /// + public static TheoryData> InvalidSVR_RESP_DACPackets + { + get + { + return new() + { + // Invalid header byte + { GeneratePacketBuffers(FormatSVR_RESPMessage(0x00, 0x06, CreateRESP_DATA(0x01, 1434))) }, + + // Invalid size + { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x09, CreateRESP_DATA(0x01, 1434))) }, + + // Invalid protocol version + { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x02, 1434))) }, + + // Invalid port + { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 0))) }, + }; + } + } + + /// + /// Packets containing an SVR_RESP response which is a valid response to a CLNT_[B|U]CAST_EX message + /// but not to a CLNT_UCAST_INST message. + /// + /// + public static TheoryData> Invalid_CLNT_UCAST_INST_SVR_RESPPackets + { + get + { + // The RESP_DATA section of the response to a CLNT_UCAST_INST message must be shorter than 1024 bytes. + byte[] longPacket = FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo: "tcp;1433", npInfo: @"np;" + new string('a', 1025), + null, null, null, null, null, null))); + + return new() + { + GeneratePacketBuffers(longPacket) + }; + } + } + + /// + /// Packet buffers containing an SSRP message which is failing due to invalid data + /// in the top-level SVR_RESP message fields. + /// + /// + public static TheoryData> InvalidSVR_RESPPackets + { + get + { + return new( + // Invalid SVR_RESP header field value + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x04, 0x06, CreateRESP_DATA(0x01, 1434)) + ), + + // RESP_SIZE too small (DAC response) + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x05, 0x05, CreateRESP_DATA(0x01, 1434)) + ), + + // RESP_SIZE too large (DAC response) + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x05, 0x07, CreateRESP_DATA(0x01, 1434)) + ), + + // RESP_SIZE larger than the buffer (normal response) + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x05, 72, + CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0")) + ) + ); + } + } + + /// + /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message + /// fields but invalid components of the child RESP_DATA structure. + /// + /// + public static TheoryData> InvalidRESP_DATAPackets + { + get + { + string validTcpInfo = CreateProtocolParameters("tcp;1433", null, null, null, null, null, null, null); + + return new( + // Does not start with "ServerName" string + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitServerName: true))), + + // Server name longer than 255 bytes + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA(new string('a', 256), "MSSQLSERVER", true, "14.0.0.0", validTcpInfo))), + + // Missing semicolons between keys and values + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitKeyValueSeparators: true))), + + // Missing terminating pair of semicolons + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitTrailingSemicolons: true))), + + // Missing "InstanceName" + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitInstanceName: true))), + + // Instance name longer than 255 bytes + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", new string('a', 256), true, "14.0.0.0", validTcpInfo))), + + // Missing "IsClustered" + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitIsClustered: true))), + + // Invalid IsClustered value + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", "IsClustered;INVALID;" + validTcpInfo, omitIsClustered: true))), + + // Missing "Version" + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitVersion: true))), + + // Empty version string + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "", validTcpInfo, omitVersion: true))), + + // Version string longer than 16 bytes + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "65535.65535.65.53", validTcpInfo))), + + // Version string not in the correct format: 1*[0-9"."] + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "v14", validTcpInfo))), + + // Protocol components listed twice + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters("tcp;1434", null, null, null, null, null, null, "tcp;1434")))), + + // Invalid protocol components appear + GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters("tcp;1434", null, null, null, null, null, null, "invalid_protocol;value")))), + + // Invalid PROTOCOLVERSION field value + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x02, 1434)) + ) + ); + } + } + + /// + /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message + /// fields, a valid RESP_DATA child structure but an invalid TCP_INFO structure. + /// + /// + public static TheoryData> InvalidTCP_INFOPackets + { + get + { + return new( + // Port is absent + CreateSVR_RESPMessage("tcp"), + + // Port is non-numeric + CreateSVR_RESPMessage("tcp;one"), + + // Port is > ushort.MaxValue + CreateSVR_RESPMessage("tcp;65536"), + + // Port is < 0 + CreateSVR_RESPMessage("tcp;-1") + ); + + static ReadOnlySequence CreateSVR_RESPMessage(string tcpInfo) => + GeneratePacketBuffers( + FormatSVR_RESPMessage(0x05, + CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", + CreateProtocolParameters(tcpInfo, null, null, null, null, null, null, null) + ) + ) + ); + } + } + + private static ReadOnlySequence GeneratePacketBuffers(params byte[][] packetBuffers) + { + if (packetBuffers.Length == 0) + { + return ReadOnlySequence.Empty; + } + + PacketBuffer first = new(packetBuffers[0], null); + PacketBuffer curr = first; + PacketBuffer last; + + for (int i = 1; i < packetBuffers.Length; i++) + { + curr = new(packetBuffers[i], curr); + } + last = curr; + + return new ReadOnlySequence(first, 0, last, last.Memory.Length); + } + + /// + /// Generates an SVR_RESP message with a valid length. + /// + /// The SVR_RESP header value. Expected to be 0x05. + /// The serialized RESP_DATA section. + /// + /// A byte representation of one SVR_RESP message. + private static byte[] FormatSVR_RESPMessage(byte header, ReadOnlySpan respData) => + FormatSVR_RESPMessage(header, (ushort)respData.Length, respData); + + /// + /// Generates an SVR_RESP message with specific characteristics. + /// + /// The SVR_RESP header value. Expected to be 0x05. + /// The RESP_SIZE field to be serialized to the header. + /// The serialized RESP_DATA section. + /// If specified, the number of bytes to actually write. + /// A byte representation of one SVR_RESP message. + /// + /// + private static byte[] FormatSVR_RESPMessage(byte header, ushort serializedResponseSize, ReadOnlySpan respData, + int? realResponseSize = null) + { + byte[] realRespData = realResponseSize is null + ? new byte[sizeof(byte) + sizeof(ushort) + respData.Length] + : new byte[realResponseSize.Value]; + + // Pad any free space after RESP_DATA with 0x05 + if (realRespData.Length > sizeof(byte) + sizeof(ushort) + respData.Length) + { + realRespData.AsSpan(sizeof(byte) + sizeof(ushort) + respData.Length).Fill(0x05); + } + + // Write RESP_DATA + if (realRespData.Length > sizeof(byte) + sizeof(ushort)) + { + int bytesToCopy = Math.Min(respData.Length, realRespData.Length - sizeof(byte) - sizeof(ushort)); + + respData.Slice(0, bytesToCopy).CopyTo(realRespData.AsSpan(sizeof(byte) + sizeof(ushort))); + } + + // Write RESP_SIZE + if (realRespData.Length > sizeof(byte)) + { + Span responseSizeBytes = stackalloc byte[sizeof(ushort)]; + int bytesToCopy = Math.Min(responseSizeBytes.Length, realRespData.Length - sizeof(byte)); + + BinaryPrimitives.WriteUInt16LittleEndian(responseSizeBytes, serializedResponseSize); + responseSizeBytes.Slice(0, bytesToCopy).CopyTo(realRespData.AsSpan(sizeof(byte))); + } + + // Write SVR_RESP + if (realRespData.Length > 0) + { + realRespData[0] = header; + } + + return realRespData; + } + + /// + /// Creates a new RESP_DATA section of an SVR_RESP message for the DAC request with a specified protocol version and TCP port number. + /// + /// Protocol version. Expected to be 0x01. + /// TCP port number of the DAC. + /// A byte representation of a RESP_DATA section. + /// + private static byte[] CreateRESP_DATA(byte protocolVersion, ushort dacPort) + { + byte[] data = new byte[sizeof(byte) + sizeof(ushort)]; + + data[0] = protocolVersion; + BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(1), dacPort); + return data; + } + + /// + /// Creates a RESP_DATA section of an SVR_RESP message with specific characteristics. + /// + /// ServerName parameter value. + /// InstanceName parameter value. + /// IsClustered parameter value. + /// Version parameter value. + /// If specified, the protocol parameters. Generated by . + /// If true, the ServerName, InstanceName, IsClustered and Version keys will be written in lowercase. + /// If true, the return value will not include the trailing ;;. + /// If true, no separators between the keys and values will be written. + /// If true, the mandatory ServerName parameter value will not be written. + /// If true, the mandatory InstanceName parameter value will not be written. + /// If true, the mandatory IsClustered parameter value will not be written. + /// If true, the mandatory Version parameter value will not be written. + /// If true, the key/value pairs will be written in a non-sequential order. + /// A byte representation of a RESP_DATA section. + /// + private static byte[] CreateRESP_DATA(string serverName, string instanceName, bool isClustered, string version, + string? protocolParameters = null, + bool lowercaseKey = false, bool omitTrailingSemicolons = false, bool omitKeyValueSeparators = false, + bool omitServerName = false, bool omitInstanceName = false, bool omitIsClustered = false, bool omitVersion = false, + bool shuffleKeys = false) + { + string serverNameKey = GenerateKeyValuePair("ServerName", serverName, omitServerName); + string instanceNameKey = GenerateKeyValuePair("InstanceName", instanceName, omitInstanceName); + string isClusteredKey = GenerateKeyValuePair("IsClustered", isClustered ? "Yes" : "No", omitIsClustered); + string versionKey = GenerateKeyValuePair("Version", version, omitVersion); + string[] components = + shuffleKeys + ? [protocolParameters ?? string.Empty, versionKey, isClusteredKey, instanceNameKey, serverNameKey] + : [serverNameKey, instanceNameKey, isClusteredKey, versionKey, protocolParameters ?? string.Empty]; + string outputString = string.Join(";", components) + + (omitTrailingSemicolons ? string.Empty : ";;"); + + return Encoding.UTF8.GetBytes(outputString); + + string GenerateKeyValuePair(string key, string value, bool omitKey) + { + if (omitKey) + { + return string.Empty; + } + + if (lowercaseKey) + { + key = key.ToLower(); + } + + return key + (omitKeyValueSeparators ? string.Empty : ";") + value; + } + } + + /// + /// Creates the protocol parameters for a RESP_DATA section. + /// + /// If non-null, the TCP_INFO data. + /// If non-null, the NP_INFO data. + /// If non-null, the VIA_INFO data. + /// If non-null, the RPC_INFO data + /// If non-null, the SPX_INFO data. + /// If non-null, the ADSP_INFO data. + /// If non-null, the BV_INFO data. + /// Any additional protocol parameters to include. + /// The collated protocol parameters for a RESP_DATA section. + private static string CreateProtocolParameters(string? tcpInfo, string? npInfo, + string? viaInfo, string? rpcInfo, string? spxInfo, string? adspInfo, string? bvInfo, + string? otherParameters) + { + List protocolParameters = []; + ReadOnlySpan allParams = [tcpInfo, npInfo, viaInfo, rpcInfo, spxInfo, adspInfo, bvInfo, otherParameters]; + + foreach (string? param in allParams) + { + if (param is not null) + { + protocolParameters.Add(param); + } + } + + return string.Join(";", protocolParameters); + } +} From 3d8176ac607dc4d7ee813508755b6c435a8a4ab3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 14 May 2026 20:13:26 +0100 Subject: [PATCH 2/7] Replace ActiveIssue attributes with [Theory(Skip = "...")] --- .../Data/Sql/DacResponseProcessorTest.cs | 11 ++++------ .../Sql/SqlDataSourceResponseProcessorTest.cs | 20 +++++++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs index 65cd8f2553..c8a585d44f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -9,25 +9,22 @@ namespace Microsoft.Data.Sql.UnitTests; public class DacResponseProcessorTest { - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESP_DACPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESP_DACPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_ValidDacResponse_ReturnsData(ReadOnlySequence packetBuffers, int expectedDacPort) { _ = packetBuffers; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs index 04ffde489b..6bcd43ac93 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -9,49 +9,43 @@ namespace Microsoft.Data.Sql.UnitTests; public class SqlDataSourceResponseProcessorTest { - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.InvalidRESP_DATAPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_InvalidSqlDataSourceResponse_RESP_DATA_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.InvalidTCP_INFOPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_InvalidSqlDataSourceResponse_TCP_INFO_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.Invalid_CLNT_UCAST_INST_SVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_InvalidSqlDataSourceResponseToCLNT_UCAST_INST_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } - [Theory] + [Theory(Skip = "Implementation in progress, see GH #3700")] [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESPPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")] public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName) { _ = packetBuffers; From ec2d00e95e8eae6c0fb2e28762520295bc182293 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 14 May 2026 22:03:05 +0100 Subject: [PATCH 3/7] Formatting changes Lifted some constant string components to constants. Newline formatting. Where possible, used expression body. --- .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 479 +++++++++++------- 1 file changed, 306 insertions(+), 173 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs index 1fd4e1e5bf..021f099de5 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -18,6 +18,19 @@ namespace Microsoft.Data.Sql.UnitTests; /// internal static class SsrpPacketTestData { + private const byte ValidSvrRespHeader = 0x05; + private const byte ValidRespDataDacProtocolVersion = 0x01; + private const byte ValidRespDataDacResponseSize = 0x06; + + private const string ValidServerName = "srv1"; + private const string ValidInstanceName = "MSSQLSERVER"; + private const string ValidServerVersion = "14.0.0.0"; + private const int ValidTcpPort1 = 1433; + private const int ValidTcpPort2 = 1434; + private const int ValidTcpPort3 = 1435; + private const int ValidTcpPort4 = 1436; + private const int ValidTcpPort5 = 1437; + /// /// One empty packet buffer, which should be successfully processed and contain zero responses. /// @@ -35,49 +48,73 @@ internal static class SsrpPacketTestData { get { - byte[] complexValidPacket = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1433", npInfo: @"np;\\svr1\pipe\SampléPipeName", - viaInfo: "via;svr1 1:1433", rpcInfo: "rpc;svr1", spxInfo: "spx;MSSQLSERVER", - adspInfo: "adsp;SQL2000", bvInfo: "bv;item;group;item;group;org", otherParameters: null))); - byte[] validPacket1 = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1433", null, null, null, null, null, null, null))); - byte[] validPacket2 = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1434", null, null, null, null, null, null, null))); - byte[] validPacket3 = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1435", null, null, null, null, null, null, null))); - byte[] validPacket4 = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1436", null, null, null, null, null, null, null))); - byte[] invalidPacket1 = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "v14", - CreateProtocolParameters(tcpInfo: "tcp;1433", null, null, null, null, null, null, null))); + const string PipeName = $@"\\{ValidServerName}\pipe\SampléPipeName"; + + string complexProtocolParameters = CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}", + npInfo: $"np;{PipeName}", + viaInfo: $"via;{ValidServerName} 1:1433", + rpcInfo: $"rpc;{ValidServerName}", + spxInfo: $"spx;{ValidInstanceName}", + adspInfo: "adsp;SQL2000", + bvInfo: "bv;item;group;item;group;org"); + + byte[] complexValidPacket = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, complexProtocolParameters)); + byte[] validPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); + byte[] validPacket2 = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}"))); + byte[] validPacket3 = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort3}"))); + byte[] validPacket4 = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort4}"))); + byte[] invalidPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, "v14", CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); return new() { - // One buffer, one response - { GeneratePacketBuffers( - complexValidPacket - ), "14.0.0.0", 1433, @"\\svr1\pipe\SampléPipeName" }, - // One response, split into four buffers in the middle of a string - { GeneratePacketBuffers( - complexValidPacket.AsSpan(0, 14).ToArray(), - complexValidPacket.AsSpan(14, 22).ToArray(), - complexValidPacket.AsSpan(36, 71).ToArray(), - // Position 107 is the second byte of the é character when encoded to UTF8. - complexValidPacket.AsSpan(107).ToArray() - ), "14.0.0.0", 1433, @"\\svr1\pipe\SampléPipeName" }, - // Four responses, each with different methods - { GeneratePacketBuffers( - validPacket1, validPacket2, validPacket3, validPacket4 - ), "14.0.0.0", 1436, null }, - // Five responses, with response three invalid - { GeneratePacketBuffers( - complexValidPacket, validPacket2, invalidPacket1, validPacket3, validPacket4 - ), "14.0.0.0", 1436, null } + { + // One buffer, one response + GeneratePacketBuffers(complexValidPacket), + ValidServerVersion, + ValidTcpPort1, + PipeName + }, + { + // One response, split into four buffers in the middle of a string + GeneratePacketBuffers( + complexValidPacket.AsSpan(0, 14).ToArray(), + complexValidPacket.AsSpan(14, 22).ToArray(), + complexValidPacket.AsSpan(36, 71).ToArray(), + // Position 107 is the second byte of the é character when encoded to UTF8. + complexValidPacket.AsSpan(107).ToArray()), + ValidServerVersion, + ValidTcpPort1, + PipeName + }, + { + // Four responses, each with different methods + GeneratePacketBuffers( + validPacket1, + validPacket2, + validPacket3, + validPacket4), + ValidServerVersion, + ValidTcpPort4, + null + }, + { + // Five responses, with response three invalid + GeneratePacketBuffers( + complexValidPacket, + validPacket2, + invalidPacket1, + validPacket3, + validPacket4), + ValidServerVersion, + ValidTcpPort4, + null + } }; } } @@ -91,38 +128,72 @@ public static TheoryData, int> ValidSVR_RESP_DACPacketBuf { get { - byte[] validPacket1 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1434)); - byte[] validPacket2 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1435)); - byte[] validPacket3 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1436)); - byte[] validPacket4 = FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 1437)); - byte[] invalidPacket1 = FormatSVR_RESPMessage(0x05, 0x03, CreateRESP_DATA(0x01, 1434)); + byte[] validPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2)); + byte[] validPacket2 = FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort3)); + byte[] validPacket3 = FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort4)); + byte[] validPacket4 = FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort5)); + byte[] invalidPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + serializedResponseSize: 0x03, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2)); return new() { - // One buffer, one response - { GeneratePacketBuffers(validPacket1), 1434 }, - - // One response, split into three buffers - { GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), - validPacket1.AsSpan(2, 2).ToArray(), - validPacket1.AsSpan(4).ToArray()), 1434 }, - - // Two responses with trailing data - { GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), - validPacket1.AsSpan(2, 2).ToArray(), - [..validPacket1.AsSpan(4).ToArray(), 0x05], - validPacket2.AsSpan(0, 2).ToArray(), - validPacket2.AsSpan(2).ToArray(), - [0x05]), 1435 }, - - // Four responses, each with different DAC ports - { GeneratePacketBuffers(validPacket1, validPacket2, validPacket3, validPacket4), 1437 }, - - // Five responses, with response three invalid - { GeneratePacketBuffers(validPacket1, validPacket2, invalidPacket1, validPacket3, validPacket4), 1437 }, - - // Four responses, with three extraneous 0x05 bytes between responses 2 and 3 - { GeneratePacketBuffers(validPacket1, [..validPacket2, 0x05], [0x05], [0x05, ..validPacket3], validPacket4), 1437 } + { + // One buffer, one response + GeneratePacketBuffers(validPacket1), + ValidTcpPort2 + }, + { + // One response, split into three buffers + GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + validPacket1.AsSpan(4).ToArray()), + ValidTcpPort2 + }, + { + // Two responses with trailing data + GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + [..validPacket1.AsSpan(4).ToArray(), 0x05], + validPacket2.AsSpan(0, 2).ToArray(), + validPacket2.AsSpan(2).ToArray(), + [0x05]), + ValidTcpPort3 + }, + { + // Four responses, each with different DAC ports + GeneratePacketBuffers(validPacket1, + validPacket2, + validPacket3, + validPacket4), + ValidTcpPort5 + }, + { + // Five responses, with response three invalid + GeneratePacketBuffers(validPacket1, + validPacket2, + invalidPacket1, + validPacket3, + validPacket4), + ValidTcpPort5 + }, + { + // Four responses, with three extraneous 0x05 bytes between responses 2 and 3 + GeneratePacketBuffers(validPacket1, + [..validPacket2, 0x05], + [0x05], + [0x05, ..validPacket3], + validPacket4), + ValidTcpPort5 + } }; } } @@ -131,26 +202,28 @@ public static TheoryData, int> ValidSVR_RESP_DACPacketBuf /// Packet buffers containing nothing but invalid SVR_RESP (DAC) responses. /// /// - public static TheoryData> InvalidSVR_RESP_DACPackets - { - get - { - return new() - { - // Invalid header byte - { GeneratePacketBuffers(FormatSVR_RESPMessage(0x00, 0x06, CreateRESP_DATA(0x01, 1434))) }, - - // Invalid size - { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x09, CreateRESP_DATA(0x01, 1434))) }, - - // Invalid protocol version - { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x02, 1434))) }, + public static TheoryData> InvalidSVR_RESP_DACPackets => + [ + // Invalid header byte + GeneratePacketBuffers(FormatSVR_RESPMessage(header: 0x00, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // Invalid size + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + serializedResponseSize: 0x09, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // Invalid protocol version + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(protocolVersion: 0x02, ValidTcpPort2))), - // Invalid port - { GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x01, 0))) }, - }; - } - } + // Invalid port + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, 0))) + ]; /// /// Packets containing an SVR_RESP response which is a valid response to a CLNT_[B|U]CAST_EX message @@ -162,15 +235,14 @@ public static TheoryData> Invalid_CLNT_UCAST_INST_SVR_RES get { // The RESP_DATA section of the response to a CLNT_UCAST_INST message must be shorter than 1024 bytes. - byte[] longPacket = FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo: "tcp;1433", npInfo: @"np;" + new string('a', 1025), - null, null, null, null, null, null))); - - return new() - { - GeneratePacketBuffers(longPacket) - }; + byte[] longPacket = FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}", npInfo: @"np;" + new string('a', 1025)))); + + return [GeneratePacketBuffers(longPacket)]; } } @@ -179,34 +251,28 @@ public static TheoryData> Invalid_CLNT_UCAST_INST_SVR_RES /// in the top-level SVR_RESP message fields. /// /// - public static TheoryData> InvalidSVR_RESPPackets - { - get - { - return new( - // Invalid SVR_RESP header field value - GeneratePacketBuffers( - FormatSVR_RESPMessage(0x04, 0x06, CreateRESP_DATA(0x01, 1434)) - ), - - // RESP_SIZE too small (DAC response) - GeneratePacketBuffers( - FormatSVR_RESPMessage(0x05, 0x05, CreateRESP_DATA(0x01, 1434)) - ), - - // RESP_SIZE too large (DAC response) - GeneratePacketBuffers( - FormatSVR_RESPMessage(0x05, 0x07, CreateRESP_DATA(0x01, 1434)) - ), - - // RESP_SIZE larger than the buffer (normal response) - GeneratePacketBuffers( - FormatSVR_RESPMessage(0x05, 72, - CreateRESP_DATA("svr1", "MSSQLSERVER", true, "14.0.0")) - ) - ); - } - } + public static TheoryData> InvalidSVR_RESPPackets => + [ + // Invalid SVR_RESP header field value + GeneratePacketBuffers(FormatSVR_RESPMessage(header: 0x04, + ValidRespDataDacResponseSize, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE too small (DAC response) + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + serializedResponseSize: 0x05, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE too large (DAC response) + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + serializedResponseSize : 0x07, + CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE larger than the buffer (normal response) + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + serializedResponseSize: 72, + CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))) + ]; /// /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message @@ -217,72 +283,134 @@ public static TheoryData> InvalidRESP_DATAPackets { get { - string validTcpInfo = CreateProtocolParameters("tcp;1433", null, null, null, null, null, null, null); + string validTcpInfo = CreateProtocolParameters($"tcp;{ValidTcpPort1}"); - return new( + return [ // Does not start with "ServerName" string - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitServerName: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitServerName: true))), // Server name longer than 255 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA(new string('a', 256), "MSSQLSERVER", true, "14.0.0.0", validTcpInfo))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(serverName: new string('a', 256), + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo))), // Missing semicolons between keys and values - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitKeyValueSeparators: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitKeyValueSeparators: true))), // Missing terminating pair of semicolons - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitTrailingSemicolons: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitTrailingSemicolons: true))), // Missing "InstanceName" - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitInstanceName: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitInstanceName: true))), // Instance name longer than 255 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", new string('a', 256), true, "14.0.0.0", validTcpInfo))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + instanceName: new string('a', 256), + isClustered: true, + ValidServerVersion, + validTcpInfo))), // Missing "IsClustered" - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitIsClustered: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitIsClustered: true))), // Invalid IsClustered value - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", "IsClustered;INVALID;" + validTcpInfo, omitIsClustered: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "IsClustered;INVALID;" + validTcpInfo, + omitIsClustered: true))), // Missing "Version" - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", validTcpInfo, omitVersion: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitVersion: true))), // Empty version string - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "", validTcpInfo, omitVersion: true))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + version: string.Empty, + validTcpInfo, + omitVersion: true))), // Version string longer than 16 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "65535.65535.65.53", validTcpInfo))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + version: "65535.65535.65.53", + validTcpInfo))), // Version string not in the correct format: 1*[0-9"."] - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "v14", validTcpInfo))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + version: "v14", + validTcpInfo))), // Protocol components listed twice - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters("tcp;1434", null, null, null, null, null, null, "tcp;1434")))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: $"tcp;{ValidTcpPort2}")))), // Invalid protocol components appear - GeneratePacketBuffers(FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters("tcp;1434", null, null, null, null, null, null, "invalid_protocol;value")))), + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: "invalid_protocol;value")))), // Invalid PROTOCOLVERSION field value - GeneratePacketBuffers( - FormatSVR_RESPMessage(0x05, 0x06, CreateRESP_DATA(0x02, 1434)) - ) - ); + GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRESP_DATA(protocolVersion: 0x02, ValidTcpPort2))) + ]; } } @@ -295,7 +423,7 @@ public static TheoryData> InvalidTCP_INFOPackets { get { - return new( + return [ // Port is absent CreateSVR_RESPMessage("tcp"), @@ -307,16 +435,16 @@ public static TheoryData> InvalidTCP_INFOPackets // Port is < 0 CreateSVR_RESPMessage("tcp;-1") - ); + ]; static ReadOnlySequence CreateSVR_RESPMessage(string tcpInfo) => GeneratePacketBuffers( - FormatSVR_RESPMessage(0x05, - CreateRESP_DATA("srv1", "MSSQLSERVER", true, "14.0.0.0", - CreateProtocolParameters(tcpInfo, null, null, null, null, null, null, null) - ) - ) - ); + FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRESP_DATA(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo)))); } } @@ -481,9 +609,14 @@ string GenerateKeyValuePair(string key, string value, bool omitKey) /// If non-null, the BV_INFO data. /// Any additional protocol parameters to include. /// The collated protocol parameters for a RESP_DATA section. - private static string CreateProtocolParameters(string? tcpInfo, string? npInfo, - string? viaInfo, string? rpcInfo, string? spxInfo, string? adspInfo, string? bvInfo, - string? otherParameters) + private static string CreateProtocolParameters(string? tcpInfo = null, + string? npInfo = null, + string? viaInfo = null, + string? rpcInfo = null, + string? spxInfo = null, + string? adspInfo = null, + string? bvInfo = null, + string? otherParameters = null) { List protocolParameters = []; ReadOnlySpan allParams = [tcpInfo, npInfo, viaInfo, rpcInfo, spxInfo, adspInfo, bvInfo, otherParameters]; From e65e4f1b47a983d3a027315ccd2a3788e6c9ebc3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 14 May 2026 22:11:37 +0100 Subject: [PATCH 4/7] Naming convention changes --- .../Data/Sql/DacResponseProcessorTest.cs | 4 +- .../Sql/SqlDataSourceResponseProcessorTest.cs | 16 +- .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 176 +++++++++--------- 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs index c8a585d44f..fe56f8c5d5 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs @@ -17,14 +17,14 @@ public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffer } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESP_DACPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [MemberData(nameof(SsrpPacketTestData.InvalidSvrRespDacPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESP_DACPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [MemberData(nameof(SsrpPacketTestData.ValidSvrRespDacPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] public void Process_ValidDacResponse_ReturnsData(ReadOnlySequence packetBuffers, int expectedDacPort) { _ = packetBuffers; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs index 6bcd43ac93..695a8a5cbb 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs @@ -17,35 +17,35 @@ public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffer } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [MemberData(nameof(SsrpPacketTestData.InvalidSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.InvalidRESP_DATAPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - public void Process_InvalidSqlDataSourceResponse_RESP_DATA_ReturnsFalse(ReadOnlySequence packetBuffers) + [MemberData(nameof(SsrpPacketTestData.InvalidRespDataPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponse_RespData_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.InvalidTCP_INFOPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - public void Process_InvalidSqlDataSourceResponse_TCP_INFO_ReturnsFalse(ReadOnlySequence packetBuffers) + [MemberData(nameof(SsrpPacketTestData.InvalidTcpInfoPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponse_TcpInfo_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.Invalid_CLNT_UCAST_INST_SVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] - public void Process_InvalidSqlDataSourceResponseToCLNT_UCAST_INST_ReturnsFalse(ReadOnlySequence packetBuffers) + [MemberData(nameof(SsrpPacketTestData.InvalidClntUcastInstSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponseToClntUcastInst_ReturnsFalse(ReadOnlySequence packetBuffers) { _ = packetBuffers; } [Theory(Skip = "Implementation in progress, see GH #3700")] - [MemberData(nameof(SsrpPacketTestData.ValidSVR_RESPPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + [MemberData(nameof(SsrpPacketTestData.ValidSvrRespPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName) { _ = packetBuffers; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs index 021f099de5..43b29f3bc2 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -44,7 +44,7 @@ internal static class SsrpPacketTestData /// should be successfully processed. /// /// - public static TheoryData, string, int, string?> ValidSVR_RESPPacketBuffer + public static TheoryData, string, int, string?> ValidSvrRespPacketBuffer { get { @@ -58,18 +58,18 @@ internal static class SsrpPacketTestData adspInfo: "adsp;SQL2000", bvInfo: "bv;item;group;item;group;org"); - byte[] complexValidPacket = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, complexProtocolParameters)); - byte[] validPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); - byte[] validPacket2 = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}"))); - byte[] validPacket3 = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort3}"))); - byte[] validPacket4 = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort4}"))); - byte[] invalidPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, - respData: CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, "v14", CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); + byte[] complexValidPacket = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, complexProtocolParameters)); + byte[] validPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); + byte[] validPacket2 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}"))); + byte[] validPacket3 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort3}"))); + byte[] validPacket4 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort4}"))); + byte[] invalidPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, "v14", CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); return new() { @@ -124,25 +124,25 @@ internal static class SsrpPacketTestData /// should be successfully processed. /// /// - public static TheoryData, int> ValidSVR_RESP_DACPacketBuffer + public static TheoryData, int> ValidSvrRespDacPacketBuffer { get { - byte[] validPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + byte[] validPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2)); - byte[] validPacket2 = FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2)); + byte[] validPacket2 = FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort3)); - byte[] validPacket3 = FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort3)); + byte[] validPacket3 = FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort4)); - byte[] validPacket4 = FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort4)); + byte[] validPacket4 = FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort5)); - byte[] invalidPacket1 = FormatSVR_RESPMessage(ValidSvrRespHeader, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort5)); + byte[] invalidPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize: 0x03, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2)); + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2)); return new() { @@ -202,41 +202,41 @@ public static TheoryData, int> ValidSVR_RESP_DACPacketBuf /// Packet buffers containing nothing but invalid SVR_RESP (DAC) responses. /// /// - public static TheoryData> InvalidSVR_RESP_DACPackets => + public static TheoryData> InvalidSvrRespDacPackets => [ // Invalid header byte - GeneratePacketBuffers(FormatSVR_RESPMessage(header: 0x00, + GeneratePacketBuffers(FormatSvrRespMessage(header: 0x00, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), // Invalid size - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize: 0x09, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), // Invalid protocol version - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(protocolVersion: 0x02, ValidTcpPort2))), + CreateRespData(protocolVersion: 0x02, ValidTcpPort2))), // Invalid port - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, 0))) + CreateRespData(ValidRespDataDacProtocolVersion, 0))) ]; /// /// Packets containing an SVR_RESP response which is a valid response to a CLNT_[B|U]CAST_EX message /// but not to a CLNT_UCAST_INST message. /// - /// - public static TheoryData> Invalid_CLNT_UCAST_INST_SVR_RESPPackets + /// + public static TheoryData> InvalidClntUcastInstSvrRespPackets { get { // The RESP_DATA section of the response to a CLNT_UCAST_INST message must be shorter than 1024 bytes. - byte[] longPacket = FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + byte[] longPacket = FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -251,35 +251,35 @@ public static TheoryData> Invalid_CLNT_UCAST_INST_SVR_RES /// in the top-level SVR_RESP message fields. /// /// - public static TheoryData> InvalidSVR_RESPPackets => + public static TheoryData> InvalidSvrRespPackets => [ // Invalid SVR_RESP header field value - GeneratePacketBuffers(FormatSVR_RESPMessage(header: 0x04, + GeneratePacketBuffers(FormatSvrRespMessage(header: 0x04, ValidRespDataDacResponseSize, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), // RESP_SIZE too small (DAC response) - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize: 0x05, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), // RESP_SIZE too large (DAC response) - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize : 0x07, - CreateRESP_DATA(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), // RESP_SIZE larger than the buffer (normal response) - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize: 72, - CreateRESP_DATA(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))) + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))) ]; /// /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message /// fields but invalid components of the child RESP_DATA structure. /// - /// - public static TheoryData> InvalidRESP_DATAPackets + /// + public static TheoryData> InvalidRespDataPackets { get { @@ -287,8 +287,8 @@ public static TheoryData> InvalidRESP_DATAPackets return [ // Does not start with "ServerName" string - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -296,16 +296,16 @@ public static TheoryData> InvalidRESP_DATAPackets omitServerName: true))), // Server name longer than 255 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(serverName: new string('a', 256), + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(serverName: new string('a', 256), ValidInstanceName, isClustered: true, ValidServerVersion, validTcpInfo))), // Missing semicolons between keys and values - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -313,8 +313,8 @@ public static TheoryData> InvalidRESP_DATAPackets omitKeyValueSeparators: true))), // Missing terminating pair of semicolons - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -322,8 +322,8 @@ public static TheoryData> InvalidRESP_DATAPackets omitTrailingSemicolons: true))), // Missing "InstanceName" - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -331,16 +331,16 @@ public static TheoryData> InvalidRESP_DATAPackets omitInstanceName: true))), // Instance name longer than 255 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, instanceName: new string('a', 256), isClustered: true, ValidServerVersion, validTcpInfo))), // Missing "IsClustered" - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -348,8 +348,8 @@ public static TheoryData> InvalidRESP_DATAPackets omitIsClustered: true))), // Invalid IsClustered value - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -357,8 +357,8 @@ public static TheoryData> InvalidRESP_DATAPackets omitIsClustered: true))), // Missing "Version" - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -366,8 +366,8 @@ public static TheoryData> InvalidRESP_DATAPackets omitVersion: true))), // Empty version string - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, version: string.Empty, @@ -375,41 +375,41 @@ public static TheoryData> InvalidRESP_DATAPackets omitVersion: true))), // Version string longer than 16 bytes - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, version: "65535.65535.65.53", validTcpInfo))), // Version string not in the correct format: 1*[0-9"."] - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, version: "v14", validTcpInfo))), // Protocol components listed twice - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: $"tcp;{ValidTcpPort2}")))), // Invalid protocol components appear - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: "invalid_protocol;value")))), // Invalid PROTOCOLVERSION field value - GeneratePacketBuffers(FormatSVR_RESPMessage(ValidSvrRespHeader, + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, - CreateRESP_DATA(protocolVersion: 0x02, ValidTcpPort2))) + CreateRespData(protocolVersion: 0x02, ValidTcpPort2))) ]; } } @@ -418,8 +418,8 @@ public static TheoryData> InvalidRESP_DATAPackets /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message /// fields, a valid RESP_DATA child structure but an invalid TCP_INFO structure. /// - /// - public static TheoryData> InvalidTCP_INFOPackets + /// + public static TheoryData> InvalidTcpInfoPackets { get { @@ -439,8 +439,8 @@ public static TheoryData> InvalidTCP_INFOPackets static ReadOnlySequence CreateSVR_RESPMessage(string tcpInfo) => GeneratePacketBuffers( - FormatSVR_RESPMessage(ValidSvrRespHeader, - CreateRESP_DATA(ValidServerName, + FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, @@ -473,10 +473,10 @@ private static ReadOnlySequence GeneratePacketBuffers(params byte[][] pack /// /// The SVR_RESP header value. Expected to be 0x05. /// The serialized RESP_DATA section. - /// + /// /// A byte representation of one SVR_RESP message. - private static byte[] FormatSVR_RESPMessage(byte header, ReadOnlySpan respData) => - FormatSVR_RESPMessage(header, (ushort)respData.Length, respData); + private static byte[] FormatSvrRespMessage(byte header, ReadOnlySpan respData) => + FormatSvrRespMessage(header, (ushort)respData.Length, respData); /// /// Generates an SVR_RESP message with specific characteristics. @@ -488,7 +488,7 @@ private static byte[] FormatSVR_RESPMessage(byte header, ReadOnlySpan resp /// A byte representation of one SVR_RESP message. /// /// - private static byte[] FormatSVR_RESPMessage(byte header, ushort serializedResponseSize, ReadOnlySpan respData, + private static byte[] FormatSvrRespMessage(byte header, ushort serializedResponseSize, ReadOnlySpan respData, int? realResponseSize = null) { byte[] realRespData = realResponseSize is null @@ -535,7 +535,7 @@ private static byte[] FormatSVR_RESPMessage(byte header, ushort serializedRespon /// TCP port number of the DAC. /// A byte representation of a RESP_DATA section. /// - private static byte[] CreateRESP_DATA(byte protocolVersion, ushort dacPort) + private static byte[] CreateRespData(byte protocolVersion, ushort dacPort) { byte[] data = new byte[sizeof(byte) + sizeof(ushort)]; @@ -562,7 +562,7 @@ private static byte[] CreateRESP_DATA(byte protocolVersion, ushort dacPort) /// If true, the key/value pairs will be written in a non-sequential order. /// A byte representation of a RESP_DATA section. /// - private static byte[] CreateRESP_DATA(string serverName, string instanceName, bool isClustered, string version, + private static byte[] CreateRespData(string serverName, string instanceName, bool isClustered, string version, string? protocolParameters = null, bool lowercaseKey = false, bool omitTrailingSemicolons = false, bool omitKeyValueSeparators = false, bool omitServerName = false, bool omitInstanceName = false, bool omitIsClustered = false, bool omitVersion = false, From 85c49d1f0e5870c78a469189a09b18e865167eb3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 14 May 2026 22:13:57 +0100 Subject: [PATCH 5/7] Parameter line chopping --- .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs index 43b29f3bc2..0910158601 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -488,7 +488,9 @@ private static byte[] FormatSvrRespMessage(byte header, ReadOnlySpan respD /// A byte representation of one SVR_RESP message. /// /// - private static byte[] FormatSvrRespMessage(byte header, ushort serializedResponseSize, ReadOnlySpan respData, + private static byte[] FormatSvrRespMessage(byte header, + ushort serializedResponseSize, + ReadOnlySpan respData, int? realResponseSize = null) { byte[] realRespData = realResponseSize is null @@ -562,10 +564,18 @@ private static byte[] CreateRespData(byte protocolVersion, ushort dacPort) /// If true, the key/value pairs will be written in a non-sequential order. /// A byte representation of a RESP_DATA section. /// - private static byte[] CreateRespData(string serverName, string instanceName, bool isClustered, string version, + private static byte[] CreateRespData(string serverName, + string instanceName, + bool isClustered, + string version, string? protocolParameters = null, - bool lowercaseKey = false, bool omitTrailingSemicolons = false, bool omitKeyValueSeparators = false, - bool omitServerName = false, bool omitInstanceName = false, bool omitIsClustered = false, bool omitVersion = false, + bool lowercaseKey = false, + bool omitTrailingSemicolons = false, + bool omitKeyValueSeparators = false, + bool omitServerName = false, + bool omitInstanceName = false, + bool omitIsClustered = false, + bool omitVersion = false, bool shuffleKeys = false) { string serverNameKey = GenerateKeyValuePair("ServerName", serverName, omitServerName); From 26b0d6f6023d18eb0ec1ad8fa215d6ffcf7dbb80 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 19 May 2026 16:43:35 +0100 Subject: [PATCH 6/7] Respond to code review Correct screaming snake case method. Correct ensure that test case provides a zero-length Version field (not an absent one.) --- .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs index 0910158601..1bbdc82497 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -371,8 +371,7 @@ public static TheoryData> InvalidRespDataPackets ValidInstanceName, isClustered: true, version: string.Empty, - validTcpInfo, - omitVersion: true))), + validTcpInfo))), // Version string longer than 16 bytes GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, @@ -425,19 +424,19 @@ public static TheoryData> InvalidTcpInfoPackets { return [ // Port is absent - CreateSVR_RESPMessage("tcp"), + CreateSvrRespMessage("tcp"), // Port is non-numeric - CreateSVR_RESPMessage("tcp;one"), + CreateSvrRespMessage("tcp;one"), // Port is > ushort.MaxValue - CreateSVR_RESPMessage("tcp;65536"), + CreateSvrRespMessage("tcp;65536"), // Port is < 0 - CreateSVR_RESPMessage("tcp;-1") + CreateSvrRespMessage("tcp;-1") ]; - static ReadOnlySequence CreateSVR_RESPMessage(string tcpInfo) => + static ReadOnlySequence CreateSvrRespMessage(string tcpInfo) => GeneratePacketBuffers( FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, From 95842c8b7de49467e3f0740928fe792240cacde4 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 19 May 2026 17:19:17 +0100 Subject: [PATCH 7/7] Add SSRP parsing test cases --- .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 101 +++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs index 1bbdc82497..9729c33949 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -152,7 +152,20 @@ public static TheoryData, int> ValidSvrRespDacPacketBuffe ValidTcpPort2 }, { - // One response, split into three buffers + // One response, split into three buffers. + // Buffer 1: the header byte + // Buffer 2: both bytes of RESP_SIZE and the first byte of RESP_DATA (protocol version). + // Buffer 3: remainder. + GeneratePacketBuffers(validPacket1.AsSpan(0, 1).ToArray(), + validPacket1.AsSpan(1, 3).ToArray(), + validPacket1.AsSpan(4).ToArray()), + ValidTcpPort2 + }, + { + // One response, split into three buffers. + // Buffer 1: the header and first byte of RESP_SIZE. + // Buffer 2: the second byte of RESP_SIZE and the first byte of RESP_DATA (protocol version). + // Buffer 3: remainder. GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), validPacket1.AsSpan(2, 2).ToArray(), validPacket1.AsSpan(4).ToArray()), @@ -268,9 +281,29 @@ public static TheoryData> InvalidClntUcastInstSvrRespPack serializedResponseSize : 0x07, CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + // RESP_SIZE zero length (DAC response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x00, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE far beyond reasonable limits (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: ushort.MaxValue, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + // RESP_SIZE larger than the buffer (normal response) GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, serializedResponseSize: 72, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))), + + // RESP_SIZE zero length (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x00, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))), + + // RESP_SIZE far beyond reasonable limits (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: ushort.MaxValue, CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))) ]; @@ -283,9 +316,28 @@ public static TheoryData> InvalidRespDataPackets { get { + const string InvalidServerName = "sr\u0008v\u00001"; string validTcpInfo = CreateProtocolParameters($"tcp;{ValidTcpPort1}"); return [ + // All keys lowercase + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + lowercaseKey: true))), + + // Keys shuffled + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + shuffleKeys: true))), + // Does not start with "ServerName" string GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, @@ -303,6 +355,14 @@ public static TheoryData> InvalidRespDataPackets ValidServerVersion, validTcpInfo))), + // Server name contains invalid characters + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(serverName: InvalidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo))), + // Missing semicolons between keys and values GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, @@ -330,6 +390,14 @@ public static TheoryData> InvalidRespDataPackets validTcpInfo, omitInstanceName: true))), + // Duplicate "InstanceName" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "InstanceName;DUPLICATE"))), + // Instance name longer than 255 bytes GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, @@ -347,7 +415,7 @@ public static TheoryData> InvalidRespDataPackets validTcpInfo, omitIsClustered: true))), - // Invalid IsClustered value + // Invalid IsClustered value - omit the IsClustered;Yes pair and supply an invalid one. GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, ValidInstanceName, @@ -356,6 +424,14 @@ public static TheoryData> InvalidRespDataPackets protocolParameters: "IsClustered;INVALID;" + validTcpInfo, omitIsClustered: true))), + // Duplicate "IsClustered" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "IsClustered;DUPLICATE"))), + // Missing "Version" GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, @@ -389,13 +465,21 @@ public static TheoryData> InvalidRespDataPackets version: "v14", validTcpInfo))), + // Duplicate "Version" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "Version;DUPLICATE"))), + // Protocol components listed twice GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, - CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: $"tcp;{ValidTcpPort2}")))), + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: $"tcp;{ValidTcpPort3}")))), // Invalid protocol components appear GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, @@ -405,6 +489,14 @@ public static TheoryData> InvalidRespDataPackets ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: "invalid_protocol;value")))), + // Empty protocol components appear + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(otherParameters: ";value")))), + // Invalid PROTOCOLVERSION field value GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, ValidRespDataDacResponseSize, @@ -426,6 +518,9 @@ public static TheoryData> InvalidTcpInfoPackets // Port is absent CreateSvrRespMessage("tcp"), + // Port is zero-length + CreateSvrRespMessage("tcp;"), + // Port is non-numeric CreateSvrRespMessage("tcp;one"),