Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,45 @@ public static bool ReadLittleEndian(this ref ReadOnlySequence<byte> sequence, re
return true;
}
}

/// <summary>
/// Find the absolute position of <paramref name="value"/> in <paramref name="sequence"/>.
/// </summary>
/// <typeparam name="T">The type of each element in <paramref name="sequence"/>.</typeparam>
/// <param name="sequence">The sequence to search.</param>
/// <param name="value">The value to search for.</param>
/// <returns>The absolute position of <paramref name="value"/>.</returns>
/// <remarks>
/// This is similar to <see cref="BuffersExtensions.PositionOf{T}"/>. However, this returns the
/// number of elements prior to the first appearance of <paramref name="value"/>, while PositionOf
/// returns a <see cref="SequencePosition"/>.
/// </remarks>
public static long IndexOf<T>(this in ReadOnlySequence<T> sequence, T value)
where T : IEquatable<T>
{
if (sequence.IsSingleSegment)
{
return sequence.First.Span.IndexOf(value);
}

long cumulativeIndex = 0;

SequencePosition position = sequence.Start;
while (sequence.TryGet(ref position, out ReadOnlyMemory<T> currChunk, advance: true))
{
int idx = currChunk.Span.IndexOf(value);

if (idx != -1)
{
cumulativeIndex += idx;
return cumulativeIndex;
}
else
{
cumulativeIndex += currChunk.Length;
}
}

return -1;
}
}
109 changes: 109 additions & 0 deletions src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/DacResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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.Diagnostics;

#nullable enable

namespace Microsoft.Data.Sql;

/// <summary>
/// A single parsed SSRP response.
/// </summary>
/// <seealso href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/45b52721-7a48-45cf-9c84-e6db905ad6df"/>
/// <remarks>
/// <para>This corresponds to an SVR_RESP (DAC) structure within the MC-SQLR specification.</para>
/// <para>An SVR_RESP (DAC) structure is a byte array with the following layout:</para>
/// <list type="number">
/// <item>SVR_RESP: 1 byte, always 0x05.</item>
/// <item>RESP_SIZE: 2 bytes, always 0x06. Written in little-endian byte order.</item>
/// <item>PROTOCOLVERSION: 1 byte, always 0x01.</item>
/// <item>TCP_DAC_PORT: 2 bytes, the TCP port number that is used for the DAC. Written in little-endian byte order.</item>
/// </list>
/// </remarks>
internal readonly ref struct DacResponse
{
private const int ResponseHeaderOffset = 0;
private const int ResponseSizeOffset = ResponseHeaderOffset + sizeof(byte);
private const int ProtocolVersionOffset = ResponseSizeOffset + sizeof(ushort);
private const int DacPortOffset = ProtocolVersionOffset + sizeof(byte);
private const int TotalResponseSize = DacPortOffset + sizeof(ushort);

private const byte ResponseHeaderValue = 0x05;
private const ushort ResponseSizeValue = 0x06;
private const byte ProtocolVersionValue = 0x01;

public ushort DacPort { get; }

private DacResponse(ushort tcpDacPort)
{
DacPort = tcpDacPort;
}

/// <summary>
/// Attempts to parse a single SSRP response from the start of the provided source sequence.
/// If an SSRP response cannot be found, supplies the maximum number of bytes to advance the
/// sequence by before attempting to parse another response.
/// </summary>
/// <param name="sourceSequence">The source buffer to read from.</param>
/// <param name="response">The populated SSRP response (or default, if one cannot be found.)</param>
/// <param name="bytesRead">The number of bytes to advance <paramref name="sourceSequence"/> by.</param>
/// <returns><c>true</c> if the response was processed, <c>false</c> if not.</returns>
/// <remarks>
/// If the sequence does not start with an SSRP response, <paramref name="bytesRead"/> will
/// contain the position of the next possible <c>SVR_RESP</c> header byte (<c>0x05</c>), or
/// the length of <paramref name="sourceSequence"/> if this header byte is not present in the
/// sequence.
/// </remarks>
public static bool TryParse(ReadOnlySequence<byte> sourceSequence, out DacResponse response, out long bytesRead)
{
// Make sure we have enough data to read the header.
if (sourceSequence.Length < TotalResponseSize)
{
bytesRead = sourceSequence.Length;
response = default;
return false;
}

ReadOnlySequence<byte> currSequence = sourceSequence;
ReadOnlySpan<byte> currSpan = currSequence.First.Span;
long currOffset = 0;

// Read and validate the response header.
if (currSequence.ReadByte(ref currSpan, ref currOffset, out byte responseHeader)
// RESP_SVR must be 0x05.
&& responseHeader == ResponseHeaderValue
&& currSequence.ReadLittleEndian(ref currSpan, ref currOffset, out ushort responseSize)
// RESP_SIZE must be 0x0006.
&& responseSize == ResponseSizeValue
&& currSequence.ReadByte(ref currSpan, ref currOffset, out byte protocolVersion)
// PROTOCOLVERSION must be 0x01.
&& protocolVersion == ProtocolVersionValue
&& currSequence.ReadLittleEndian(ref currSpan, ref currOffset, out ushort tcpDacPort)
// TCP_DAC_PORT must be non-zero.
&& tcpDacPort != 0)
{
Debug.Assert(currOffset == TotalResponseSize);

bytesRead = currOffset;
response = new DacResponse(tcpDacPort);
return true;
}
else
{
// Find the next possible response start across the sequence. Always advance by one byte (to skip the current
// header byte.)
long idx = sourceSequence.Slice(1).IndexOf(ResponseHeaderValue);

bytesRead = idx == -1
? sourceSequence.Length
: idx + 1;
response = default;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 System.Diagnostics;

#nullable enable

namespace Microsoft.Data.Sql;

/// <summary>
/// Utilities used to extract zero or more parsed SSRP DAC responses from a set of packet buffers.
/// </summary>
internal static class DacResponseReader

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.

The name change here and in SqlDataSourceResponseReader is purely a layering point:

  • DacResponse handles the parsing of one SVR_RESPONSE (DAC) from the start of a ReadOnlySequence;
  • DacResponseReader builds on that to read the first/last DacResponse from the ReadOnlySequence;
  • In future, the layer above will handle the networking and interpretation of the results to output the DAC port. This will be the processing layer.

{
/// <summary>
/// Returns the first SSRP response from the provided source sequence, if one exists.
/// </summary>
/// <param name="sourceSequence">The source sequence of buffers from the network.</param>
/// <param name="firstResponse">The first SSRP response (if available.)</param>
/// <returns><c>true</c> if an SSRP response can be located in the source sequence, <c>false</c> otherwise.</returns>
public static bool TryReadFirst(ReadOnlySequence<byte> sourceSequence, out DacResponse firstResponse)
{
ReadOnlySequence<byte> remainingSequence = sourceSequence;

while (!remainingSequence.IsEmpty)
{
bool parsedCurrentRequest = DacResponse.TryParse(remainingSequence, out DacResponse current, out long bytesRead);

if (parsedCurrentRequest)
{
firstResponse = current;
return true;
}

// If the current request cannot be parsed, advance by bytesRead.
// bytesRead will always be greater than zero - it'll be the next possibly-viable
// position of a DAC response, or the end of the sequence if no viable DAC responses
// can be found.
Debug.Assert(bytesRead > 0 && bytesRead <= remainingSequence.Length);
remainingSequence = remainingSequence.Slice(bytesRead);
}

firstResponse = default;
return false;
}
}
Loading
Loading