Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\NameValuePair.cs">
<Link>Microsoft\Data\Common\NameValuePair.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\PacketBuffer.cs">
<Link>Microsoft\Data\Common\PacketBuffer.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\ReadOnlySequenceUtilities.cs">
<Link>Microsoft\Data\Common\ReadOnlySequenceUtilities.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\DataException.cs">
<Link>Microsoft\Data\DataException.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\NameValuePair.cs">
<Link>Microsoft\Data\Common\NameValuePair.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\PacketBuffer.cs">
<Link>Microsoft\Data\Common\PacketBuffer.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\Common\ReadOnlySequenceUtilities.cs">
<Link>Microsoft\Data\Common\ReadOnlySequenceUtilities.cs</Link>
</Compile>
<Compile Include="$(CommonSourceRoot)Microsoft\Data\DataException.cs">
<Link>Microsoft\Data\DataException.cs</Link>
</Compile>
Expand Down
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))
Comment on lines +21 to +23
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The currPos parameter 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 on currPos when the method returns false.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currPos is the current position in the ReadOnlySequence<byte>. If there's not enough space in the sequence, the method doesn't advance and it's thus appropriate to leave currPos untouched.

{
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
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the ReadByte method, the currPos parameter is incremented (line 69) before the actual read operation, which could leave it in an inconsistent state if the sequence length check fails. Consider moving the increment after successful validation or documenting this behavior.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@edwardneal edwardneal Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the ReadByte method - currPos is always in a consistent state. Once execution reaches line 69, there's guaranteed to be enough space in the ReadOnlySpan<byte> - the only question is whether we can directly read it from the current span, or need to reassemble it because byte 1 is in the current span and byte 2 is in the next span.

{
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
Expand Up @@ -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
}
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;
}
}
Loading
Loading