// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Buffers
{
public static partial class SequenceReaderExtensions
{
/// <summary>
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
/// structs- see remarks for full details.
/// </summary>
/// <remarks>
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as <see cref="TryReadLittleEndian(ref SequenceReader{byte}, out short)"/>
/// </remarks>
/// <returns>
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool TryRead<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
{
ReadOnlySpan<byte> span = reader.UnreadSpan;
if (span.Length < sizeof(T))
return TryReadMultisegment(ref reader, out value);
value = MemoryMarshal.Read<T>(span);
reader.Advance(sizeof(T));
return true;
}
private static unsafe bool TryReadMultisegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = MemoryMarshal.Read<T>(tempSpan);
reader.Advance(sizeof(T));
return true;
}
/// <summary>
/// Reads an <see cref="short"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="short"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out short value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="short"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="short"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out short value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out short value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
/// <summary>
/// Reads an <see cref="int"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="int"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out int value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="int"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="int"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out int value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out int value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
/// <summary>
/// Reads an <see cref="long"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="long"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out long value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="long"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="long"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out long value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out long value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
/// <summary>
/// Tries to peek at an <see cref="short"/> as little endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as little endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="short"/>.
/// </returns>
public static bool TryPeekLittleEndian(ref this SequenceReader<byte> reader, out short value)
{
if (!BinaryPrimitives.TryReadInt16LittleEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt16LittleEndian(tmpSpan);
}
return true;
}
/// <summary>
/// Tries to peek at an <see cref="short"/> as big endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as big endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="short"/>.
/// </returns>
public static bool TryPeekBigEndian(ref this SequenceReader<byte> reader, out short value)
{
if (!BinaryPrimitives.TryReadInt16BigEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt16BigEndian(tmpSpan);
}
return true;
}
/// <summary>
/// Tries to peek at an <see cref="int"/> as little endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as little endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="int"/>.
/// </returns>
public static bool TryPeekLittleEndian(ref this SequenceReader<byte> reader, out int value)
{
if (!BinaryPrimitives.TryReadInt32LittleEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0, 0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt32LittleEndian(tmpSpan);
}
return true;
}
/// <summary>
/// Tries to peek at an <see cref="int"/> as big endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as big endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="int"/>.
/// </returns>
public static bool TryPeekBigEndian(ref this SequenceReader<byte> reader, out int value)
{
if (!BinaryPrimitives.TryReadInt32BigEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0, 0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt32BigEndian(tmpSpan);
}
return true;
}
/// <summary>
/// Tries to peek at an <see cref="long"/> as little endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as little endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="long"/>.
/// </returns>
public static bool TryPeekLittleEndian(ref this SequenceReader<byte> reader, out long value)
{
if (!BinaryPrimitives.TryReadInt64LittleEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0, 0, 0, 0, 0, 0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt64LittleEndian(tmpSpan);
}
return true;
}
/// <summary>
/// Tries to peek at an <see cref="long"/> as big endian without advancing the reader.
/// </summary>
/// <param name="reader">The byte sequence reader instance from which the value is to be peeked.</param>
/// <param name="value">When the method returns, the value peeked out of the byte sequence reader, as big endian.</param>
/// <returns>
/// <see langword="true" /> if the peek operation is successful; <see langword="false" /> if there isn't enough data for an <see cref="long"/>.
/// </returns>
public static bool TryPeekBigEndian(ref this SequenceReader<byte> reader, out long value)
{
if (!BinaryPrimitives.TryReadInt64BigEndian(reader.UnreadSpan, out value))
{
Span<byte> tmpSpan = [0, 0, 0, 0, 0, 0, 0, 0];
if (!reader.TryCopyTo(tmpSpan))
{
return false;
}
value = BinaryPrimitives.ReadInt64BigEndian(tmpSpan);
}
return true;
}
}
}