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
@@ -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
Copy Markdown
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)
Comment thread
benrr101 marked this conversation as resolved.
{
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
Copy Markdown
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);
Comment thread
benrr101 marked this conversation as resolved.

sequence = sequence.Slice(sizeof(ushort));
currSpan = sequence.First.Span;

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ namespace System.Diagnostics.CodeAnalysis
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,33 @@
// 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(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
Comment thread
benrr101 marked this conversation as resolved.
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.InvalidSvrRespDacPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.ValidSvrRespDacPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
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,56 @@
// 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(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.InvalidSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.InvalidRespDataPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_InvalidSqlDataSourceResponse_RespData_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.InvalidTcpInfoPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_InvalidSqlDataSourceResponse_TcpInfo_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.InvalidClntUcastInstSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_InvalidSqlDataSourceResponseToClntUcastInst_ReturnsFalse(ReadOnlySequence<byte> packetBuffers)
{
_ = packetBuffers;
}

[Theory(Skip = "Implementation in progress, see GH #3700")]
[MemberData(nameof(SsrpPacketTestData.ValidSvrRespPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)]
public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence<byte> packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName)
{
_ = packetBuffers;
_ = expectedVersion;
_ = expectedTcpPort;
_ = expectedPipeName;
}
}
Loading
Loading