using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using JetBrains.Annotations;
using JetBrains.Diagnostics;
using JetBrains.Util;
using JetBrains.Util.Internal;
// ReSharper disable BuiltInTypeReferenceStyle
// ReSharper disable ArrangeRedundantParentheses
namespace JetBrains.Serialization
{
///
/// Deserialize data from byte buffer that was initially serialized by
///
///
///
//Can't be struct because internal state must change during methods invocation
[PublicAPI]
public unsafe class UnsafeReader
{
private byte* myPtr;
private byte* myInitialPtr;
private int myMaxlen;
public static UnsafeReader CreateReader(byte* ptr, int len)
{
var reader = new UnsafeReader();
reader.Reset(ptr, len);
return reader;
}
public static void With(byte[] data, Action action)
{
fixed (byte* resptr = data)
{
action(CreateReader(resptr, data.Length));
}
}
//allows to reuse this instance (and get rid of boxing)
public UnsafeReader Reset(byte* ptr, int len)
{
myInitialPtr = ptr;
myPtr = ptr;
myMaxlen = len;
return this;
}
public int Position => (int) (myPtr - myInitialPtr);
public void Skip(int bytes)
{
AssertLength(bytes);
myPtr += bytes;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
private void AssertLength(int size)
{
// NOTE: having this makes it possible for JIT to just drop the remaining method's body when assertions are disabled
if (!Mode.IsAssertion) return;
var alreadyRead = (int)(myPtr - myInitialPtr);
if (alreadyRead + size > myMaxlen)
{
ThrowOutOfRange(size, alreadyRead);
}
}
private void ThrowOutOfRange(int size, int alreadyRead)
{
Assertion.Fail(
"Can't read from unsafe reader: alreadyRead={0} size={1} maxlen={2}. " +
"Usually this happens when you change serialization format and forget to clear previous entries from disk. " +
"For example, you forgot to advance persistent caches version - do this in 'SolutionCaches' and 'ShellCaches' classes).", alreadyRead, size, myMaxlen);
}
#region Primitive readers
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public byte* ReadRaw(int count)
{
AssertLength(count);
var res = myPtr;
myPtr += count;
return res;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public bool ReadBoolean()
{
AssertLength(sizeof(byte));
return *(myPtr++) != 0;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public byte ReadByte()
{
AssertLength(sizeof(byte));
return *(myPtr++);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public sbyte ReadSByte()
{
AssertLength(sizeof(sbyte));
return (sbyte) *(myPtr++);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Guid ReadGuid()
{
var array = ReadArray(reader => reader.ReadByte());
return new Guid(array!);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public char ReadChar()
{
AssertLength(sizeof(char));
var x = (char*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public decimal ReadDecimal()
{
AssertLength(sizeof(decimal));
var x = (decimal*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public double ReadDouble()
{
AssertLength(sizeof(double));
var x = (double*)myPtr;
myPtr = (byte*)(x + 1);
if (!RuntimeInfo.IsUnalignedAccessAllowed)
{
double d = 0;
Buffer.MemoryCopy(x, &d, sizeof(double), sizeof(double));
return d;
}
else
{
return *x;
}
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public float ReadFloat()
{
AssertLength(sizeof(float));
var x = (float*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Int16 ReadInt16()
{
AssertLength(sizeof(Int16));
var x = (Int16*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Int32 ReadInt32()
{
AssertLength(sizeof(Int32));
var x = (Int32*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
public int Read7BitEncodedInt32()
{
// read out an Int32 7 bits at a time
var count = 0;
var shift = 0;
byte b;
do
{
if (shift == 5 * 7)
throw new FormatException();
b = ReadByte();
count |= (b & 0b1111111) << shift;
shift += 7;
} while ((b & 0b10000000) != 0);
return count;
}
public int ReadOftenSmallPositiveInt32()
{
var byteValue = ReadByte();
if (byteValue == byte.MaxValue)
{
return ReadInt32();
}
return byteValue;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Int64 ReadInt64()
{
AssertLength(sizeof(Int64));
var x = (Int64*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public UInt16 ReadUInt16()
{
AssertLength(sizeof(UInt16));
var x = (UInt16*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public UInt32 ReadUInt32()
{
AssertLength(sizeof(UInt32));
var x = (UInt32*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public UInt64 ReadUInt64()
{
AssertLength(sizeof(UInt64));
var x = (UInt64*)myPtr;
myPtr = (byte*)(x + 1);
return *x;
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public DateTime ReadDateTime()
{
return new DateTime(ReadLong(), DateTimeKind.Utc);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public TimeSpan ReadTimeSpan()
{
return TimeSpan.FromTicks(ReadLong());
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Uri ReadUri()
{
var uri = ReadString();
return new Uri(Uri.UnescapeDataString(uri!), UriKind.RelativeOrAbsolute);
}
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public string? ReadString()
{
var len = ReadInt32();
if (len < 0) return null;
if (len == 0) return string.Empty;
var raw = (char*) ReadRaw(len * sizeof(char));
if (raw == (char*)0)
throw new InvalidOperationException($"Bad memory (null) after reading string of size {len:N}");
var res = new string(raw, 0, len);
return res;
}
public string? ReadStringUTF8()
{
switch (ReadByte())
{
case 0: return null;
case 1: return "";
case var byteData:
{
var bytesCount = byteData == 0xFF ? ReadInt32() : byteData - 1;
var startPtr = ReadRaw(bytesCount);
var value = Encoding.UTF8.GetString(startPtr, bytesCount);
return value;
}
}
}
#endregion
#region Intern
public interface IRawStringIntern
{
string Intern(RawString raw);
}
[StructLayout(LayoutKind.Sequential)]
public struct RawString
{
public int Length;
public char* Data;
public string GetString()
{
return new string(Data, 0, Length);
}
}
public string? ReadStringInterned(IRawStringIntern intern)
{
var len = ReadInt32();
if (len < 0) return null;
if (len == 0) return string.Empty;
var raw = (char*) ReadRaw(len * sizeof(char));
if (raw == (char*)0)
throw new InvalidOperationException($"Bad memory (null) after reading string of size {len:N}");
var res = intern.Intern(new RawString {Length = len, Data = raw});
return res;
}
#endregion
#region Delegates
public delegate T ReadDelegate(UnsafeReader reader);
public static readonly ReadDelegate BoolDelegate = reader => reader.ReadBoolean();
public static readonly ReadDelegate BooleanDelegate = reader => reader.ReadBoolean(); //alias
public static readonly ReadDelegate ByteDelegate = reader => reader.ReadByte();
public static readonly ReadDelegate GuidDelegate = reader => reader.ReadGuid();
public static readonly ReadDelegate CharDelegate = reader => reader.ReadChar();
public static readonly ReadDelegate DecimalDelegate = reader => reader.ReadDecimal();
public static readonly ReadDelegate DoubleDelegate = reader => reader.ReadDouble();
public static readonly ReadDelegate FloatDelegate = reader => reader.ReadFloat();
public static readonly ReadDelegate Int16Delegate = reader => reader.ReadInt16();
public static readonly ReadDelegate ShortDelegate = reader => reader.ReadInt16(); //alias
public static readonly ReadDelegate Int32Delegate = reader => reader.ReadInt32();
public static readonly ReadDelegate IntDelegate = reader => reader.ReadInt32(); //alias
public static readonly ReadDelegate Int64Delegate = reader => reader.ReadInt64();
public static readonly ReadDelegate LongDelegate = reader => reader.ReadInt64(); //alias
public static readonly ReadDelegate UInt16Delegate = reader => reader.ReadUInt16();
public static readonly ReadDelegate UInt32Delegate = reader => reader.ReadUInt32();
public static readonly ReadDelegate UInt64Delegate = reader => reader.ReadUInt64();
public static readonly ReadDelegate DateTimeDelegate = reader => reader.ReadDateTime();
public static readonly ReadDelegate UriDelegate = reader => reader.ReadUri();
public static readonly ReadDelegate StringDelegate = reader => reader.ReadString();
public static readonly ReadDelegate ByteArrayDelegate = reader => reader.ReadByteArray();
public static readonly ReadDelegate BoolArrayDelegate = reader => reader.ReadArray(BooleanDelegate);
public static readonly ReadDelegate IntArrayDelegate = reader => reader.ReadIntArray();
public static readonly ReadDelegate StringArrayDelegate = reader => reader.ReadArray(StringDelegate);
#endregion
#region Collection readers
public T?[]? ReadArray(ReadDelegate readDelegate)
{
var len = ReadInt32();
if (len < 0) return null;
if (len == 0) return EmptyArray.Instance;
var res = new T?[len];
for (var index = 0; index < len; index++)
{
res[index] = readDelegate(this);
}
return res;
}
public int[]? ReadIntArray()
{
var len = ReadInt32();
if (len < 0) return null;
if (len == 0) return EmptyArray.Instance;
var res = new int[len];
fixed (int* mem = res)
{
var size = len * sizeof(int);
Memory.CopyMemory(ReadRaw(size), (byte*)mem, size);
}
return res;
}
public byte[]? ReadByteArray()
{
var len = ReadInt32();
if (len < 0) return null;
if (len == 0) return EmptyArray.Instance;
var res = new byte[len];
fixed (byte* mem = res)
{
Memory.CopyMemory(ReadRaw(len), mem, len);
}
return res;
}
///
/// Non optimal collection serialization. One can serialize internal structure (eg. array) instead.
///
///
///
///
///
///
public TCollection? ReadCollection(ReadDelegate readDelegate, Func constructor) where TCollection : ICollection
{
var count = ReadInt32();
if (count < 0) return default;
var col = constructor(count);
for (var index = 0; index < count; index++)
{
col.Add(readDelegate(this));
}
return col;
}
public TDictionary? ReadDictionary(
ReadDelegate readKeyDelegate, ReadDelegate readValueDelegate, Func constructor)
where TDictionary : IDictionary
{
var count = ReadInt32();
if (count < 0) return default;
var dict = constructor(count);
for (var index = 0; index < count; index++)
{
dict[readKeyDelegate(this)] = readValueDelegate(this);
}
return dict;
}
#endregion
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public bool ReadNullness() => ReadBoolean();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public bool ReadBool() => ReadBoolean();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public Int16 ReadShort() => ReadInt16();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public int ReadInt() => ReadInt32();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public long ReadLong() => ReadInt64();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public byte ReadUByte() => ReadByte();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public ushort ReadUShort() => ReadUInt16();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public uint ReadUInt() => ReadUInt32();
[MethodImpl(MethodImplAdvancedOptions.AggressiveInlining)]
public ulong ReadULong() => ReadUInt64();
public static UInt16 ReadUInt16FromBytes(byte[] bytes)
{
fixed (byte* bb = bytes)
{
return *(UInt16*) bb;
}
}
public static Int32 ReadInt32FromBytes(byte[] bytes, int offset = 0)
{
fixed (byte* bb = bytes)
{
return *(Int32*) (bb + offset);
}
}
public static Int64 ReadInt64FromBytes(byte[] bytes, int offset = 0)
{
fixed (byte* bb = bytes)
{
return *(Int64*) (bb + offset);
}
}
public static UInt64 ReadUInt64FromBytes(byte[] bytes)
{
fixed (byte* bb = bytes)
{
return *(UInt64*) bb;
}
}
}
}