// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
/* 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.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MessagePack
{
internal static partial class SequenceReaderExtensions
{
///
/// 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.
///
///
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of its members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as .
///
///
/// True if successful. will be default if failed (due to lack of space).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool TryRead(ref this SequenceReader reader, out T value)
where T : unmanaged
{
ReadOnlySpan span = reader.UnreadSpan;
if (span.Length < sizeof(T))
{
return TryReadMultisegment(ref reader, out value);
}
value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span));
reader.Advance(sizeof(T));
return true;
}
#if UNITY_ANDROID
///
/// In Android 32bit device(armv7) + IL2CPP does not work correctly on Unsafe.ReadUnaligned.
/// Perhaps it is about memory alignment bug of Unity's IL2CPP VM.
/// For a workaround, read memory manually.
/// https://github.com/neuecc/MessagePack-CSharp/issues/748
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool TryRead(ref this SequenceReader reader, out long value)
{
ReadOnlySpan span = reader.UnreadSpan;
if (span.Length < sizeof(long))
{
return TryReadMultisegment(ref reader, out value);
}
value = MessagePack.SafeBitConverter.ToInt64(span);
reader.Advance(sizeof(long));
return true;
}
#endif
private static unsafe bool TryReadMultisegment(ref SequenceReader reader, out T value)
where T : unmanaged
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(T), "reader.UnreadSpan.Length < sizeof(T)");
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
Span tempSpan = new Span(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan));
reader.Advance(sizeof(T));
return true;
}
#if UNITY_ANDROID
private static unsafe bool TryReadMultisegment(ref SequenceReader reader, out long value)
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(long), "reader.UnreadSpan.Length < sizeof(long)");
// Not enough data in the current segment, try to peek for the data we need.
long buffer = default;
Span tempSpan = new Span(&buffer, sizeof(long));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = MessagePack.SafeBitConverter.ToInt64(tempSpan);
reader.Advance(sizeof(long));
return true;
}
#endif
///
/// Reads an from the next position in the sequence.
///
/// The reader to read from.
/// Receives the value read.
/// true if there was another byte in the sequence; false otherwise.
public static bool TryRead(ref this SequenceReader reader, out sbyte value)
{
if (TryRead(ref reader, out byte byteValue))
{
value = unchecked((sbyte)byteValue);
return true;
}
value = default;
return false;
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out short value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out ushort value)
{
if (TryReadBigEndian(ref reader, out short shortValue))
{
value = unchecked((ushort)shortValue);
return true;
}
value = default;
return false;
}
private static bool TryReadReverseEndianness(ref SequenceReader reader, out short value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out int value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out uint value)
{
if (TryReadBigEndian(ref reader, out int intValue))
{
value = unchecked((uint)intValue);
return true;
}
value = default;
return false;
}
private static bool TryReadReverseEndianness(ref SequenceReader reader, out int value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out long value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
///
/// Reads an as big endian.
///
/// False if there wasn't enough data for an .
public static bool TryReadBigEndian(ref this SequenceReader reader, out ulong value)
{
if (TryReadBigEndian(ref reader, out long longValue))
{
value = unchecked((ulong)longValue);
return true;
}
value = default;
return false;
}
private static bool TryReadReverseEndianness(ref SequenceReader reader, out long value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
///
/// Reads a as big endian.
///
/// False if there wasn't enough data for a .
public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out float value)
{
if (TryReadBigEndian(ref reader, out int intValue))
{
value = *(float*)&intValue;
return true;
}
value = default;
return false;
}
///
/// Reads a as big endian.
///
/// False if there wasn't enough data for a .
public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out double value)
{
if (TryReadBigEndian(ref reader, out long longValue))
{
value = *(double*)&longValue;
return true;
}
value = default;
return false;
}
}
}