Skip to content

Commit c8f6688

Browse files
committed
Add scaffolding for SSRP response parsing
This consists of: * ReadOnlySequenceSegment derivative, * Utilities to read data from a ReadOnlySequence<byte> * Test cases
1 parent da467a7 commit c8f6688

File tree

8 files changed

+742
-0
lines changed

8 files changed

+742
-0
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@
8787
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\NameValuePair.cs">
8888
<Link>Microsoft\Data\Common\NameValuePair.cs</Link>
8989
</Compile>
90+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\PacketBuffer.cs">
91+
<Link>Microsoft\Data\Common\PacketBuffer.cs</Link>
92+
</Compile>
93+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\ReadOnlySequenceUtilities.cs">
94+
<Link>Microsoft\Data\Common\ReadOnlySequenceUtilities.cs</Link>
95+
</Compile>
9096
<Compile Include="$(CommonSourceRoot)Microsoft\Data\DataException.cs">
9197
<Link>Microsoft\Data\DataException.cs</Link>
9298
</Compile>

src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,12 @@
262262
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\NameValuePair.cs">
263263
<Link>Microsoft\Data\Common\NameValuePair.cs</Link>
264264
</Compile>
265+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\PacketBuffer.cs">
266+
<Link>Microsoft\Data\Common\PacketBuffer.cs</Link>
267+
</Compile>
268+
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\ReadOnlySequenceUtilities.cs">
269+
<Link>Microsoft\Data\Common\ReadOnlySequenceUtilities.cs</Link>
270+
</Compile>
265271
<Compile Include="$(CommonSourceRoot)Microsoft\Data\DataException.cs">
266272
<Link>Microsoft\Data\DataException.cs</Link>
267273
</Compile>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Buffers;
7+
8+
#nullable enable
9+
10+
namespace Microsoft.Data.Common;
11+
12+
/// <summary>
13+
/// One buffer, which may contain one unparsed packet from a single destination.
14+
/// </summary>
15+
internal sealed class PacketBuffer : ReadOnlySequenceSegment<byte>
16+
{
17+
public PacketBuffer(ReadOnlyMemory<byte> buffer, PacketBuffer? previous)
18+
{
19+
Memory = buffer;
20+
21+
if (previous is not null)
22+
{
23+
previous.Next = this;
24+
RunningIndex = previous.RunningIndex + previous.Memory.Length;
25+
}
26+
else
27+
{
28+
RunningIndex = 0;
29+
}
30+
}
31+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Buffers;
7+
using System.Buffers.Binary;
8+
9+
namespace Microsoft.Data.Common;
10+
11+
internal static class ReadOnlySequenceUtilities
12+
{
13+
/// <summary>
14+
/// Reads the next byte from the sequence, advancing its position by one byte.
15+
/// </summary>
16+
/// <param name="sequence">The sequence to read and to advance from.</param>
17+
/// <param name="currSpan">The first span in the sequence. Reassigned if the next byte can only be read from the next span.</param>
18+
/// <param name="currPos">Current position in the sequence. Advanced by one byte following a successful read.</param>
19+
/// <param name="value">The <see cref="byte"/> value read from <paramref name="sequence"/>.</param>
20+
/// <returns><c>true</c> if <paramref name="sequence"/> was long enough to retrieve the next byte, <c>false</c> otherwise.</returns>
21+
public static bool ReadByte(this ref ReadOnlySequence<byte> sequence, ref ReadOnlySpan<byte> currSpan, ref long currPos, out byte value)
22+
{
23+
if (sequence.Length < sizeof(byte))
24+
{
25+
value = default;
26+
return false;
27+
}
28+
29+
currPos += sizeof(byte);
30+
if (currSpan.Length >= sizeof(byte))
31+
{
32+
value = currSpan[0];
33+
34+
sequence = sequence.Slice(sizeof(byte));
35+
currSpan = currSpan.Slice(sizeof(byte));
36+
37+
return true;
38+
}
39+
else
40+
{
41+
Span<byte> buffer = stackalloc byte[sizeof(byte)];
42+
43+
sequence.Slice(0, sizeof(byte)).CopyTo(buffer);
44+
value = buffer[0];
45+
46+
sequence = sequence.Slice(sizeof(byte));
47+
currSpan = sequence.First.Span;
48+
49+
return true;
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Reads the next two bytes from the sequence as a <see cref="ushort"/>, advancing its position by two bytes.
55+
/// </summary>
56+
/// <param name="sequence">The sequence to read and to advance from.</param>
57+
/// <param name="currSpan">The first span in the sequence. Reassigned if the next two bytes can only be read from the next span.</param>
58+
/// <param name="currPos">Current position in the sequence. Advanced by two bytes following a successful read.</param>
59+
/// <param name="value">The <see cref="ushort"/> value read from <paramref name="sequence"/></param>
60+
/// <returns><c>true</c> if <paramref name="sequence"/> was long enough to retrieve the next two bytes, <c>false</c> otherwise.</returns>
61+
public static bool ReadLittleEndian(this ref ReadOnlySequence<byte> sequence, ref ReadOnlySpan<byte> currSpan, ref long currPos, out ushort value)
62+
{
63+
if (sequence.Length < sizeof(ushort))
64+
{
65+
value = default;
66+
return false;
67+
}
68+
69+
currPos += sizeof(ushort);
70+
if (currSpan.Length >= sizeof(ushort))
71+
{
72+
value = BinaryPrimitives.ReadUInt16LittleEndian(currSpan);
73+
74+
sequence = sequence.Slice(sizeof(ushort));
75+
currSpan = currSpan.Slice(sizeof(ushort));
76+
77+
return true;
78+
}
79+
else
80+
{
81+
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
82+
83+
sequence.Slice(0, sizeof(ushort)).CopyTo(buffer);
84+
value = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
85+
86+
sequence = sequence.Slice(sizeof(ushort));
87+
currSpan = sequence.First.Span;
88+
89+
return true;
90+
}
91+
}
92+
}

src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,13 @@ public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
4343
internal sealed class NotNullAttribute : Attribute
4444
{
4545
}
46+
47+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
48+
internal sealed class NotNullWhenAttribute : Attribute
49+
{
50+
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
51+
52+
public bool ReturnValue { get; }
53+
}
4654
#endif
4755
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Buffers;
6+
using Xunit;
7+
8+
namespace Microsoft.Data.Sql.UnitTests;
9+
10+
public class DacResponseProcessorTest
11+
{
12+
[Theory]
13+
[MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
14+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
15+
public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
16+
{
17+
_ = packetBuffers;
18+
}
19+
20+
[Theory]
21+
[MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESP_DACPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
22+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
23+
public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
24+
{
25+
_ = packetBuffers;
26+
}
27+
28+
[Theory]
29+
[MemberData(nameof(SsrpPacketTestData.ValidSVR_RESP_DACPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
30+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
31+
public void Process_ValidDacResponse_ReturnsData(ReadOnlySequence<byte> packetBuffers, int expectedDacPort)
32+
{
33+
_ = packetBuffers;
34+
_ = expectedDacPort;
35+
}
36+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Buffers;
6+
using Xunit;
7+
8+
namespace Microsoft.Data.Sql.UnitTests;
9+
10+
public class SqlDataSourceResponseProcessorTest
11+
{
12+
[Theory]
13+
[MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
14+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
15+
public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
16+
{
17+
_ = packetBuffers;
18+
}
19+
20+
[Theory]
21+
[MemberData(nameof(SsrpPacketTestData.InvalidSVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
22+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
23+
public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
24+
{
25+
_ = packetBuffers;
26+
}
27+
28+
[Theory]
29+
[MemberData(nameof(SsrpPacketTestData.InvalidRESP_DATAPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
30+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
31+
public void Process_InvalidSqlDataSourceResponse_RESP_DATA_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
32+
{
33+
_ = packetBuffers;
34+
}
35+
36+
[Theory]
37+
[MemberData(nameof(SsrpPacketTestData.InvalidTCP_INFOPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
38+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
39+
public void Process_InvalidSqlDataSourceResponse_TCP_INFO_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
40+
{
41+
_ = packetBuffers;
42+
}
43+
44+
[Theory]
45+
[MemberData(nameof(SsrpPacketTestData.Invalid_CLNT_UCAST_INST_SVR_RESPPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
46+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
47+
public void Process_InvalidSqlDataSourceResponseToCLNT_UCAST_INST_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
48+
{
49+
_ = packetBuffers;
50+
}
51+
52+
[Theory]
53+
[MemberData(nameof(SsrpPacketTestData.ValidSVR_RESPPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
54+
[ActiveIssue("https://github.com/dotnet/SqlClient/issues/3700")]
55+
public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence<byte> packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName)
56+
{
57+
_ = packetBuffers;
58+
_ = expectedVersion;
59+
_ = expectedTcpPort;
60+
_ = expectedPipeName;
61+
}
62+
}

0 commit comments

Comments
 (0)