-
Notifications
You must be signed in to change notification settings - Fork 317
Feature | Add scaffolding for SSRP response parsing #3741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| /// <summary> | ||
| /// One buffer, which may contain one unparsed packet from a single destination. | ||
| /// </summary> | ||
| internal sealed class PacketBuffer : ReadOnlySequenceSegment<byte> | ||
| { | ||
| public PacketBuffer(ReadOnlyMemory<byte> buffer, PacketBuffer? previous) | ||
| { | ||
| Memory = buffer; | ||
|
|
||
| if (previous is not null) | ||
| { | ||
| previous.Next = this; | ||
| RunningIndex = previous.RunningIndex + previous.Memory.Length; | ||
| } | ||
| else | ||
| { | ||
| RunningIndex = 0; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// Reads the next byte from the sequence, advancing its position by one byte. | ||
| /// </summary> | ||
| /// <param name="sequence">The sequence to read and to advance from.</param> | ||
| /// <param name="currSpan">The first span in the sequence. Reassigned if the next byte can only be read from the next span.</param> | ||
| /// <param name="currPos">Current position in the sequence. Advanced by one byte following a successful read.</param> | ||
| /// <param name="value">The <see cref="byte"/> value read from <paramref name="sequence"/>.</param> | ||
| /// <returns><c>true</c> if <paramref name="sequence"/> was long enough to retrieve the next byte, <c>false</c> otherwise.</returns> | ||
| public static bool ReadByte(this ref ReadOnlySequence<byte> sequence, ref ReadOnlySpan<byte> 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<byte> 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; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Reads the next two bytes from the sequence as a <see cref="ushort"/>, advancing its position by two bytes. | ||
| /// </summary> | ||
| /// <param name="sequence">The sequence to read and to advance from.</param> | ||
| /// <param name="currSpan">The first span in the sequence. Reassigned if the next two bytes can only be read from the next span.</param> | ||
| /// <param name="currPos">Current position in the sequence. Advanced by two bytes following a successful read.</param> | ||
| /// <param name="value">The <see cref="ushort"/> value read from <paramref name="sequence"/></param> | ||
| /// <returns><c>true</c> if <paramref name="sequence"/> was long enough to retrieve the next two bytes, <c>false</c> otherwise.</returns> | ||
| public static bool ReadLittleEndian(this ref ReadOnlySequence<byte> sequence, ref ReadOnlySpan<byte> currSpan, ref long currPos, out ushort value) | ||
| { | ||
| if (sequence.Length < sizeof(ushort)) | ||
|
Comment on lines
+61
to
+63
|
||
| { | ||
| 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<byte> 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; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<byte> 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<byte> 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<byte> packetBuffers, int expectedDacPort) | ||
| { | ||
| _ = packetBuffers; | ||
| _ = expectedDacPort; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName) | ||
| { | ||
| _ = packetBuffers; | ||
| _ = expectedVersion; | ||
| _ = expectedTcpPort; | ||
| _ = expectedPipeName; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
currPosparameter is incremented but its updated value may not reflect the actual state if the sequence is too short (line 23-27). Consider moving the increment after the length check succeeds, or document that callers should not rely oncurrPoswhen the method returns false.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currPosis the current position in theReadOnlySequence<byte>. If there's not enough space in the sequence, the method doesn't advance and it's thus appropriate to leavecurrPosuntouched.