1388 lines
29 KiB
C#
1388 lines
29 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using ProtoBuf.Meta;
|
|
|
|
namespace ProtoBuf;
|
|
|
|
public sealed class ProtoReader : IDisposable
|
|
{
|
|
private Stream source;
|
|
|
|
private byte[] ioBuffer;
|
|
|
|
private TypeModel model;
|
|
|
|
private int fieldNumber;
|
|
|
|
private int depth;
|
|
|
|
private int ioIndex;
|
|
|
|
private int available;
|
|
|
|
private long position64;
|
|
|
|
private long blockEnd64;
|
|
|
|
private long dataRemaining64;
|
|
|
|
private WireType wireType;
|
|
|
|
private bool isFixedLength;
|
|
|
|
private bool internStrings;
|
|
|
|
private NetObjectCache netCache;
|
|
|
|
private uint trapCount;
|
|
|
|
internal const long TO_EOF = -1L;
|
|
|
|
private SerializationContext context;
|
|
|
|
private const long Int64Msb = long.MinValue;
|
|
|
|
private const int Int32Msb = int.MinValue;
|
|
|
|
private Dictionary<string, string> stringInterner;
|
|
|
|
private static readonly UTF8Encoding encoding = new UTF8Encoding();
|
|
|
|
private static readonly byte[] EmptyBlob = new byte[0];
|
|
|
|
[ThreadStatic]
|
|
private static ProtoReader lastReader;
|
|
|
|
public int FieldNumber => fieldNumber;
|
|
|
|
public WireType WireType => wireType;
|
|
|
|
public bool InternStrings
|
|
{
|
|
get
|
|
{
|
|
return internStrings;
|
|
}
|
|
set
|
|
{
|
|
internStrings = value;
|
|
}
|
|
}
|
|
|
|
public SerializationContext Context => context;
|
|
|
|
public int Position => checked((int)position64);
|
|
|
|
public long LongPosition => position64;
|
|
|
|
public TypeModel Model => model;
|
|
|
|
internal NetObjectCache NetCache => netCache;
|
|
|
|
[Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", false)]
|
|
public ProtoReader(Stream source, TypeModel model, SerializationContext context)
|
|
{
|
|
Init(this, source, model, context, -1L);
|
|
}
|
|
|
|
[Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", false)]
|
|
public ProtoReader(Stream source, TypeModel model, SerializationContext context, int length)
|
|
{
|
|
Init(this, source, model, context, length);
|
|
}
|
|
|
|
[Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", false)]
|
|
public ProtoReader(Stream source, TypeModel model, SerializationContext context, long length)
|
|
{
|
|
Init(this, source, model, context, length);
|
|
}
|
|
|
|
private static void Init(ProtoReader reader, Stream source, TypeModel model, SerializationContext context, long length)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException("source");
|
|
}
|
|
if (!source.CanRead)
|
|
{
|
|
throw new ArgumentException("Cannot read from stream", "source");
|
|
}
|
|
reader.source = source;
|
|
reader.ioBuffer = BufferPool.GetBuffer();
|
|
reader.model = model;
|
|
reader.dataRemaining64 = ((reader.isFixedLength = length >= 0) ? length : 0);
|
|
if (context == null)
|
|
{
|
|
context = SerializationContext.Default;
|
|
}
|
|
else
|
|
{
|
|
context.Freeze();
|
|
}
|
|
reader.context = context;
|
|
reader.position64 = 0L;
|
|
reader.available = (reader.depth = (reader.fieldNumber = (reader.ioIndex = 0)));
|
|
reader.blockEnd64 = long.MaxValue;
|
|
reader.internStrings = RuntimeTypeModel.Default.InternStrings;
|
|
reader.wireType = WireType.None;
|
|
reader.trapCount = 1u;
|
|
if (reader.netCache == null)
|
|
{
|
|
reader.netCache = new NetObjectCache();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
source = null;
|
|
model = null;
|
|
BufferPool.ReleaseBufferToPool(ref ioBuffer);
|
|
if (stringInterner != null)
|
|
{
|
|
stringInterner.Clear();
|
|
stringInterner = null;
|
|
}
|
|
if (netCache != null)
|
|
{
|
|
netCache.Clear();
|
|
}
|
|
context = null;
|
|
}
|
|
|
|
internal int TryReadUInt32VariantWithoutMoving(bool trimNegative, out uint value)
|
|
{
|
|
if (available < 10)
|
|
{
|
|
Ensure(10, strict: false);
|
|
}
|
|
if (available == 0)
|
|
{
|
|
value = 0u;
|
|
return 0;
|
|
}
|
|
int num = ioIndex;
|
|
value = ioBuffer[num++];
|
|
if ((value & 0x80) == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
value &= 127u;
|
|
if (available == 1)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
uint num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 7;
|
|
if ((num2 & 0x80) == 0)
|
|
{
|
|
return 2;
|
|
}
|
|
if (available == 2)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 14;
|
|
if ((num2 & 0x80) == 0)
|
|
{
|
|
return 3;
|
|
}
|
|
if (available == 3)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 21;
|
|
if ((num2 & 0x80) == 0)
|
|
{
|
|
return 4;
|
|
}
|
|
if (available == 4)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num];
|
|
value |= num2 << 28;
|
|
if ((num2 & 0xF0) == 0)
|
|
{
|
|
return 5;
|
|
}
|
|
if (trimNegative && (num2 & 0xF0) == 240 && available >= 10 && ioBuffer[++num] == byte.MaxValue && ioBuffer[++num] == byte.MaxValue && ioBuffer[++num] == byte.MaxValue && ioBuffer[++num] == byte.MaxValue && ioBuffer[++num] == 1)
|
|
{
|
|
return 10;
|
|
}
|
|
throw AddErrorData(new OverflowException(), this);
|
|
}
|
|
|
|
private uint ReadUInt32Variant(bool trimNegative)
|
|
{
|
|
uint value;
|
|
int num = TryReadUInt32VariantWithoutMoving(trimNegative, out value);
|
|
if (num > 0)
|
|
{
|
|
ioIndex += num;
|
|
available -= num;
|
|
position64 += num;
|
|
return value;
|
|
}
|
|
throw EoF(this);
|
|
}
|
|
|
|
private bool TryReadUInt32Variant(out uint value)
|
|
{
|
|
int num = TryReadUInt32VariantWithoutMoving(trimNegative: false, out value);
|
|
if (num > 0)
|
|
{
|
|
ioIndex += num;
|
|
available -= num;
|
|
position64 += num;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public uint ReadUInt32()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Variant:
|
|
return ReadUInt32Variant(trimNegative: false);
|
|
case WireType.Fixed32:
|
|
if (available < 4)
|
|
{
|
|
Ensure(4, strict: true);
|
|
}
|
|
position64 += 4L;
|
|
available -= 4;
|
|
return (uint)(ioBuffer[ioIndex++] | (ioBuffer[ioIndex++] << 8) | (ioBuffer[ioIndex++] << 16) | (ioBuffer[ioIndex++] << 24));
|
|
case WireType.Fixed64:
|
|
{
|
|
ulong num = ReadUInt64();
|
|
return checked((uint)num);
|
|
}
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
internal void Ensure(int count, bool strict)
|
|
{
|
|
if (count > ioBuffer.Length)
|
|
{
|
|
BufferPool.ResizeAndFlushLeft(ref ioBuffer, count, ioIndex, available);
|
|
ioIndex = 0;
|
|
}
|
|
else if (ioIndex + count >= ioBuffer.Length)
|
|
{
|
|
Buffer.BlockCopy(ioBuffer, ioIndex, ioBuffer, 0, available);
|
|
ioIndex = 0;
|
|
}
|
|
count -= available;
|
|
int num = ioIndex + available;
|
|
int num2 = ioBuffer.Length - num;
|
|
if (isFixedLength && dataRemaining64 < num2)
|
|
{
|
|
num2 = (int)dataRemaining64;
|
|
}
|
|
int num3;
|
|
while (count > 0 && num2 > 0 && (num3 = source.Read(ioBuffer, num, num2)) > 0)
|
|
{
|
|
available += num3;
|
|
count -= num3;
|
|
num2 -= num3;
|
|
num += num3;
|
|
if (isFixedLength)
|
|
{
|
|
dataRemaining64 -= num3;
|
|
}
|
|
}
|
|
if (strict && count > 0)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
}
|
|
|
|
public short ReadInt16()
|
|
{
|
|
return checked((short)ReadInt32());
|
|
}
|
|
|
|
public ushort ReadUInt16()
|
|
{
|
|
return checked((ushort)ReadUInt32());
|
|
}
|
|
|
|
public byte ReadByte()
|
|
{
|
|
return checked((byte)ReadUInt32());
|
|
}
|
|
|
|
public sbyte ReadSByte()
|
|
{
|
|
return checked((sbyte)ReadInt32());
|
|
}
|
|
|
|
public int ReadInt32()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Variant:
|
|
return (int)ReadUInt32Variant(trimNegative: true);
|
|
case WireType.Fixed32:
|
|
if (available < 4)
|
|
{
|
|
Ensure(4, strict: true);
|
|
}
|
|
position64 += 4L;
|
|
available -= 4;
|
|
return ioBuffer[ioIndex++] | (ioBuffer[ioIndex++] << 8) | (ioBuffer[ioIndex++] << 16) | (ioBuffer[ioIndex++] << 24);
|
|
case WireType.Fixed64:
|
|
{
|
|
long num = ReadInt64();
|
|
return checked((int)num);
|
|
}
|
|
case WireType.SignedVariant:
|
|
return Zag(ReadUInt32Variant(trimNegative: true));
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
private static int Zag(uint ziggedValue)
|
|
{
|
|
return (int)(0 - (ziggedValue & 1)) ^ (((int)ziggedValue >> 1) & 0x7FFFFFFF);
|
|
}
|
|
|
|
private static long Zag(ulong ziggedValue)
|
|
{
|
|
return (long)(0L - (ziggedValue & 1)) ^ (((long)ziggedValue >> 1) & 0x7FFFFFFFFFFFFFFFL);
|
|
}
|
|
|
|
public long ReadInt64()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Variant:
|
|
return (long)ReadUInt64Variant();
|
|
case WireType.Fixed32:
|
|
return ReadInt32();
|
|
case WireType.Fixed64:
|
|
if (available < 8)
|
|
{
|
|
Ensure(8, strict: true);
|
|
}
|
|
position64 += 8L;
|
|
available -= 8;
|
|
return (long)(ioBuffer[ioIndex++] | ((ulong)ioBuffer[ioIndex++] << 8) | ((ulong)ioBuffer[ioIndex++] << 16) | ((ulong)ioBuffer[ioIndex++] << 24) | ((ulong)ioBuffer[ioIndex++] << 32) | ((ulong)ioBuffer[ioIndex++] << 40) | ((ulong)ioBuffer[ioIndex++] << 48) | ((ulong)ioBuffer[ioIndex++] << 56));
|
|
case WireType.SignedVariant:
|
|
return Zag(ReadUInt64Variant());
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
private int TryReadUInt64VariantWithoutMoving(out ulong value)
|
|
{
|
|
if (available < 10)
|
|
{
|
|
Ensure(10, strict: false);
|
|
}
|
|
if (available == 0)
|
|
{
|
|
value = 0uL;
|
|
return 0;
|
|
}
|
|
int num = ioIndex;
|
|
value = ioBuffer[num++];
|
|
if ((value & 0x80) == 0L)
|
|
{
|
|
return 1;
|
|
}
|
|
value &= 127uL;
|
|
if (available == 1)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
ulong num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 7;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 2;
|
|
}
|
|
if (available == 2)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 14;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 3;
|
|
}
|
|
if (available == 3)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 21;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 4;
|
|
}
|
|
if (available == 4)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 28;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 5;
|
|
}
|
|
if (available == 5)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 35;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 6;
|
|
}
|
|
if (available == 6)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 42;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 7;
|
|
}
|
|
if (available == 7)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 49;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 8;
|
|
}
|
|
if (available == 8)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num++];
|
|
value |= (num2 & 0x7F) << 56;
|
|
if ((num2 & 0x80) == 0L)
|
|
{
|
|
return 9;
|
|
}
|
|
if (available == 9)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
num2 = ioBuffer[num];
|
|
value |= num2 << 63;
|
|
if ((num2 & 0xFFFFFFFFFFFFFFFEuL) != 0L)
|
|
{
|
|
throw AddErrorData(new OverflowException(), this);
|
|
}
|
|
return 10;
|
|
}
|
|
|
|
private ulong ReadUInt64Variant()
|
|
{
|
|
ulong value;
|
|
int num = TryReadUInt64VariantWithoutMoving(out value);
|
|
if (num > 0)
|
|
{
|
|
ioIndex += num;
|
|
available -= num;
|
|
position64 += num;
|
|
return value;
|
|
}
|
|
throw EoF(this);
|
|
}
|
|
|
|
private string Intern(string value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return null;
|
|
}
|
|
if (value.Length == 0)
|
|
{
|
|
return "";
|
|
}
|
|
string value2;
|
|
if (stringInterner == null)
|
|
{
|
|
stringInterner = new Dictionary<string, string> { { value, value } };
|
|
}
|
|
else if (stringInterner.TryGetValue(value, out value2))
|
|
{
|
|
value = value2;
|
|
}
|
|
else
|
|
{
|
|
stringInterner.Add(value, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public string ReadString()
|
|
{
|
|
if (wireType == WireType.String)
|
|
{
|
|
int num = (int)ReadUInt32Variant(trimNegative: false);
|
|
if (num == 0)
|
|
{
|
|
return "";
|
|
}
|
|
if (num < 0)
|
|
{
|
|
ThrowInvalidLength(num);
|
|
}
|
|
if (available < num)
|
|
{
|
|
Ensure(num, strict: true);
|
|
}
|
|
string text = encoding.GetString(ioBuffer, ioIndex, num);
|
|
if (internStrings)
|
|
{
|
|
text = Intern(text);
|
|
}
|
|
available -= num;
|
|
position64 += num;
|
|
ioIndex += num;
|
|
return text;
|
|
}
|
|
throw CreateWireTypeException();
|
|
}
|
|
|
|
public void ThrowEnumException(Type type, int value)
|
|
{
|
|
string text = (((object)type == null) ? "<null>" : type.FullName);
|
|
throw AddErrorData(new ProtoException("No " + text + " enum is mapped to the wire-value " + value), this);
|
|
}
|
|
|
|
private void ThrowInvalidLength(long length)
|
|
{
|
|
throw AddErrorData(new InvalidOperationException("Invalid length: " + length), this);
|
|
}
|
|
|
|
private Exception CreateWireTypeException()
|
|
{
|
|
return CreateException("Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see https://stackoverflow.com/q/2152978/23354");
|
|
}
|
|
|
|
private Exception CreateException(string message)
|
|
{
|
|
return AddErrorData(new ProtoException(message), this);
|
|
}
|
|
|
|
public unsafe double ReadDouble()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Fixed32:
|
|
return ReadSingle();
|
|
case WireType.Fixed64:
|
|
{
|
|
long num = ReadInt64();
|
|
return *(double*)(&num);
|
|
}
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public static object ReadObject(object value, int key, ProtoReader reader)
|
|
{
|
|
return ReadTypedObject(value, key, reader, null);
|
|
}
|
|
|
|
internal static object ReadTypedObject(object value, int key, ProtoReader reader, Type type)
|
|
{
|
|
if (reader.model == null)
|
|
{
|
|
throw AddErrorData(new InvalidOperationException("Cannot deserialize sub-objects unless a model is provided"), reader);
|
|
}
|
|
SubItemToken token = StartSubItem(reader);
|
|
if (key >= 0)
|
|
{
|
|
value = reader.model.Deserialize(key, value, reader);
|
|
}
|
|
else if ((object)type == null || !reader.model.TryDeserializeAuxiliaryType(reader, DataFormat.Default, 1, type, ref value, skipOtherFields: true, asListItem: false, autoCreate: true, insideList: false, null))
|
|
{
|
|
TypeModel.ThrowUnexpectedType(type);
|
|
}
|
|
EndSubItem(token, reader);
|
|
return value;
|
|
}
|
|
|
|
public static void EndSubItem(SubItemToken token, ProtoReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw new ArgumentNullException("reader");
|
|
}
|
|
long value = token.value64;
|
|
WireType wireType = reader.wireType;
|
|
if (wireType == WireType.EndGroup)
|
|
{
|
|
if (value >= 0)
|
|
{
|
|
throw AddErrorData(new ArgumentException("token"), reader);
|
|
}
|
|
if (-(int)value != reader.fieldNumber)
|
|
{
|
|
throw reader.CreateException("Wrong group was ended");
|
|
}
|
|
reader.wireType = WireType.None;
|
|
reader.depth--;
|
|
}
|
|
else
|
|
{
|
|
if (value < reader.position64)
|
|
{
|
|
throw reader.CreateException($"Sub-message not read entirely; expected {value}, was {reader.position64}");
|
|
}
|
|
if (reader.blockEnd64 != reader.position64 && reader.blockEnd64 != long.MaxValue)
|
|
{
|
|
throw reader.CreateException("Sub-message not read correctly");
|
|
}
|
|
reader.blockEnd64 = value;
|
|
reader.depth--;
|
|
}
|
|
}
|
|
|
|
public static SubItemToken StartSubItem(ProtoReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw new ArgumentNullException("reader");
|
|
}
|
|
switch (reader.wireType)
|
|
{
|
|
case WireType.StartGroup:
|
|
reader.wireType = WireType.None;
|
|
reader.depth++;
|
|
return new SubItemToken((long)(-reader.fieldNumber));
|
|
case WireType.String:
|
|
{
|
|
long num = (long)reader.ReadUInt64Variant();
|
|
if (num < 0)
|
|
{
|
|
reader.ThrowInvalidLength(num);
|
|
}
|
|
long value = reader.blockEnd64;
|
|
reader.blockEnd64 = reader.position64 + num;
|
|
reader.depth++;
|
|
return new SubItemToken(value);
|
|
}
|
|
default:
|
|
throw reader.CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public int ReadFieldHeader()
|
|
{
|
|
if (blockEnd64 <= position64 || wireType == WireType.EndGroup)
|
|
{
|
|
return 0;
|
|
}
|
|
if (TryReadUInt32Variant(out var value) && value != 0)
|
|
{
|
|
wireType = (WireType)(value & 7);
|
|
fieldNumber = (int)(value >> 3);
|
|
if (fieldNumber < 1)
|
|
{
|
|
throw new ProtoException("Invalid field in source data: " + fieldNumber);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wireType = WireType.None;
|
|
fieldNumber = 0;
|
|
}
|
|
if (wireType == WireType.EndGroup)
|
|
{
|
|
if (depth > 0)
|
|
{
|
|
return 0;
|
|
}
|
|
throw new ProtoException("Unexpected end-group in source data; this usually means the source data is corrupt");
|
|
}
|
|
return fieldNumber;
|
|
}
|
|
|
|
public bool TryReadFieldHeader(int field)
|
|
{
|
|
if (blockEnd64 <= position64 || this.wireType == WireType.EndGroup)
|
|
{
|
|
return false;
|
|
}
|
|
uint value;
|
|
int num = TryReadUInt32VariantWithoutMoving(trimNegative: false, out value);
|
|
WireType wireType;
|
|
if (num > 0 && (int)value >> 3 == field && (wireType = (WireType)(value & 7)) != WireType.EndGroup)
|
|
{
|
|
this.wireType = wireType;
|
|
fieldNumber = field;
|
|
position64 += num;
|
|
ioIndex += num;
|
|
available -= num;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Hint(WireType wireType)
|
|
{
|
|
if (this.wireType != wireType && (wireType & (WireType)7) == this.wireType)
|
|
{
|
|
this.wireType = wireType;
|
|
}
|
|
}
|
|
|
|
public void Assert(WireType wireType)
|
|
{
|
|
if (this.wireType != wireType)
|
|
{
|
|
if ((wireType & (WireType)7) != this.wireType)
|
|
{
|
|
throw CreateWireTypeException();
|
|
}
|
|
this.wireType = wireType;
|
|
}
|
|
}
|
|
|
|
public void SkipField()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Fixed32:
|
|
if (available < 4)
|
|
{
|
|
Ensure(4, strict: true);
|
|
}
|
|
available -= 4;
|
|
ioIndex += 4;
|
|
position64 += 4L;
|
|
break;
|
|
case WireType.Fixed64:
|
|
if (available < 8)
|
|
{
|
|
Ensure(8, strict: true);
|
|
}
|
|
available -= 8;
|
|
ioIndex += 8;
|
|
position64 += 8L;
|
|
break;
|
|
case WireType.String:
|
|
{
|
|
long num2 = (long)ReadUInt64Variant();
|
|
if (num2 < 0)
|
|
{
|
|
ThrowInvalidLength(num2);
|
|
}
|
|
if (num2 <= available)
|
|
{
|
|
available -= (int)num2;
|
|
ioIndex += (int)num2;
|
|
position64 += num2;
|
|
break;
|
|
}
|
|
position64 += num2;
|
|
num2 -= available;
|
|
ioIndex = (available = 0);
|
|
if (isFixedLength)
|
|
{
|
|
if (num2 > dataRemaining64)
|
|
{
|
|
throw EoF(this);
|
|
}
|
|
dataRemaining64 -= num2;
|
|
}
|
|
Seek(source, num2, ioBuffer);
|
|
break;
|
|
}
|
|
case WireType.Variant:
|
|
case WireType.SignedVariant:
|
|
ReadUInt64Variant();
|
|
break;
|
|
case WireType.StartGroup:
|
|
{
|
|
int num = fieldNumber;
|
|
depth++;
|
|
while (ReadFieldHeader() > 0)
|
|
{
|
|
SkipField();
|
|
}
|
|
depth--;
|
|
if (wireType == WireType.EndGroup && fieldNumber == num)
|
|
{
|
|
wireType = WireType.None;
|
|
break;
|
|
}
|
|
throw CreateWireTypeException();
|
|
}
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public ulong ReadUInt64()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Variant:
|
|
return ReadUInt64Variant();
|
|
case WireType.Fixed32:
|
|
return ReadUInt32();
|
|
case WireType.Fixed64:
|
|
if (available < 8)
|
|
{
|
|
Ensure(8, strict: true);
|
|
}
|
|
position64 += 8L;
|
|
available -= 8;
|
|
return ioBuffer[ioIndex++] | ((ulong)ioBuffer[ioIndex++] << 8) | ((ulong)ioBuffer[ioIndex++] << 16) | ((ulong)ioBuffer[ioIndex++] << 24) | ((ulong)ioBuffer[ioIndex++] << 32) | ((ulong)ioBuffer[ioIndex++] << 40) | ((ulong)ioBuffer[ioIndex++] << 48) | ((ulong)ioBuffer[ioIndex++] << 56);
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public unsafe float ReadSingle()
|
|
{
|
|
switch (wireType)
|
|
{
|
|
case WireType.Fixed32:
|
|
{
|
|
int num3 = ReadInt32();
|
|
return *(float*)(&num3);
|
|
}
|
|
case WireType.Fixed64:
|
|
{
|
|
double num = ReadDouble();
|
|
float num2 = (float)num;
|
|
if (float.IsInfinity(num2) && !double.IsInfinity(num))
|
|
{
|
|
throw AddErrorData(new OverflowException(), this);
|
|
}
|
|
return num2;
|
|
}
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public bool ReadBoolean()
|
|
{
|
|
return ReadUInt32() switch
|
|
{
|
|
0u => false,
|
|
1u => true,
|
|
_ => throw CreateException("Unexpected boolean value"),
|
|
};
|
|
}
|
|
|
|
public static byte[] AppendBytes(byte[] value, ProtoReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw new ArgumentNullException("reader");
|
|
}
|
|
switch (reader.wireType)
|
|
{
|
|
case WireType.String:
|
|
{
|
|
int num = (int)reader.ReadUInt32Variant(trimNegative: false);
|
|
reader.wireType = WireType.None;
|
|
if (num == 0)
|
|
{
|
|
return value ?? EmptyBlob;
|
|
}
|
|
if (num < 0)
|
|
{
|
|
reader.ThrowInvalidLength(num);
|
|
}
|
|
int num2;
|
|
if (value == null || value.Length == 0)
|
|
{
|
|
num2 = 0;
|
|
value = new byte[num];
|
|
}
|
|
else
|
|
{
|
|
num2 = value.Length;
|
|
byte[] array = new byte[value.Length + num];
|
|
Buffer.BlockCopy(value, 0, array, 0, value.Length);
|
|
value = array;
|
|
}
|
|
reader.position64 += num;
|
|
while (num > reader.available)
|
|
{
|
|
if (reader.available > 0)
|
|
{
|
|
Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, num2, reader.available);
|
|
num -= reader.available;
|
|
num2 += reader.available;
|
|
reader.ioIndex = (reader.available = 0);
|
|
}
|
|
int num3 = ((num > reader.ioBuffer.Length) ? reader.ioBuffer.Length : num);
|
|
if (num3 > 0)
|
|
{
|
|
reader.Ensure(num3, strict: true);
|
|
}
|
|
}
|
|
if (num > 0)
|
|
{
|
|
Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, num2, num);
|
|
reader.ioIndex += num;
|
|
reader.available -= num;
|
|
}
|
|
return value;
|
|
}
|
|
case WireType.Variant:
|
|
return new byte[0];
|
|
default:
|
|
throw reader.CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
private static int ReadByteOrThrow(Stream source)
|
|
{
|
|
int num = source.ReadByte();
|
|
if (num < 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
return num;
|
|
}
|
|
|
|
public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber)
|
|
{
|
|
int bytesRead;
|
|
return ReadLengthPrefix(source, expectHeader, style, out fieldNumber, out bytesRead);
|
|
}
|
|
|
|
public static int DirectReadLittleEndianInt32(Stream source)
|
|
{
|
|
return ReadByteOrThrow(source) | (ReadByteOrThrow(source) << 8) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 24);
|
|
}
|
|
|
|
public static int DirectReadBigEndianInt32(Stream source)
|
|
{
|
|
return (ReadByteOrThrow(source) << 24) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 8) | ReadByteOrThrow(source);
|
|
}
|
|
|
|
public static int DirectReadVarintInt32(Stream source)
|
|
{
|
|
ulong value;
|
|
int num = TryReadUInt64Variant(source, out value);
|
|
if (num <= 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
return checked((int)value);
|
|
}
|
|
|
|
public static void DirectReadBytes(Stream source, byte[] buffer, int offset, int count)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException("source");
|
|
}
|
|
int num;
|
|
while (count > 0 && (num = source.Read(buffer, offset, count)) > 0)
|
|
{
|
|
count -= num;
|
|
offset += num;
|
|
}
|
|
if (count > 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
}
|
|
|
|
public static byte[] DirectReadBytes(Stream source, int count)
|
|
{
|
|
byte[] array = new byte[count];
|
|
DirectReadBytes(source, array, 0, count);
|
|
return array;
|
|
}
|
|
|
|
public static string DirectReadString(Stream source, int length)
|
|
{
|
|
byte[] array = new byte[length];
|
|
DirectReadBytes(source, array, 0, length);
|
|
return Encoding.UTF8.GetString(array, 0, length);
|
|
}
|
|
|
|
public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead)
|
|
{
|
|
if (style == PrefixStyle.None)
|
|
{
|
|
bytesRead = (fieldNumber = 0);
|
|
return int.MaxValue;
|
|
}
|
|
long num = ReadLongLengthPrefix(source, expectHeader, style, out fieldNumber, out bytesRead);
|
|
return checked((int)num);
|
|
}
|
|
|
|
public static long ReadLongLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead)
|
|
{
|
|
fieldNumber = 0;
|
|
switch (style)
|
|
{
|
|
case PrefixStyle.None:
|
|
bytesRead = 0;
|
|
return long.MaxValue;
|
|
case PrefixStyle.Base128:
|
|
{
|
|
bytesRead = 0;
|
|
ulong value;
|
|
int num2;
|
|
if (expectHeader)
|
|
{
|
|
num2 = TryReadUInt64Variant(source, out value);
|
|
bytesRead += num2;
|
|
if (num2 > 0)
|
|
{
|
|
if ((value & 7) != 2)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
fieldNumber = (int)(value >> 3);
|
|
num2 = TryReadUInt64Variant(source, out value);
|
|
bytesRead += num2;
|
|
if (bytesRead == 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
return (long)value;
|
|
}
|
|
bytesRead = 0;
|
|
return -1L;
|
|
}
|
|
num2 = TryReadUInt64Variant(source, out value);
|
|
bytesRead += num2;
|
|
if (bytesRead >= 0)
|
|
{
|
|
return (long)value;
|
|
}
|
|
return -1L;
|
|
}
|
|
case PrefixStyle.Fixed32:
|
|
{
|
|
int num3 = source.ReadByte();
|
|
if (num3 < 0)
|
|
{
|
|
bytesRead = 0;
|
|
return -1L;
|
|
}
|
|
bytesRead = 4;
|
|
return num3 | (ReadByteOrThrow(source) << 8) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 24);
|
|
}
|
|
case PrefixStyle.Fixed32BigEndian:
|
|
{
|
|
int num = source.ReadByte();
|
|
if (num < 0)
|
|
{
|
|
bytesRead = 0;
|
|
return -1L;
|
|
}
|
|
bytesRead = 4;
|
|
return (num << 24) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 8) | ReadByteOrThrow(source);
|
|
}
|
|
default:
|
|
throw new ArgumentOutOfRangeException("style");
|
|
}
|
|
}
|
|
|
|
private static int TryReadUInt64Variant(Stream source, out ulong value)
|
|
{
|
|
value = 0uL;
|
|
int num = source.ReadByte();
|
|
if (num < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
value = (uint)num;
|
|
if ((value & 0x80) == 0L)
|
|
{
|
|
return 1;
|
|
}
|
|
value &= 127uL;
|
|
int num2 = 1;
|
|
int num3 = 7;
|
|
while (num2 < 9)
|
|
{
|
|
num = source.ReadByte();
|
|
if (num < 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
value |= ((ulong)num & 0x7FuL) << num3;
|
|
num3 += 7;
|
|
num2++;
|
|
if ((num & 0x80) == 0)
|
|
{
|
|
return num2;
|
|
}
|
|
}
|
|
num = source.ReadByte();
|
|
if (num < 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
if ((num & 1) == 0)
|
|
{
|
|
value |= ((ulong)num & 0x7FuL) << num3;
|
|
return ++num2;
|
|
}
|
|
throw new OverflowException();
|
|
}
|
|
|
|
internal static void Seek(Stream source, long count, byte[] buffer)
|
|
{
|
|
if (source.CanSeek)
|
|
{
|
|
source.Seek(count, SeekOrigin.Current);
|
|
count = 0L;
|
|
}
|
|
else if (buffer != null)
|
|
{
|
|
int num;
|
|
while (count > buffer.Length && (num = source.Read(buffer, 0, buffer.Length)) > 0)
|
|
{
|
|
count -= num;
|
|
}
|
|
while (count > 0 && (num = source.Read(buffer, 0, (int)count)) > 0)
|
|
{
|
|
count -= num;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer = BufferPool.GetBuffer();
|
|
try
|
|
{
|
|
int num2;
|
|
while (count > buffer.Length && (num2 = source.Read(buffer, 0, buffer.Length)) > 0)
|
|
{
|
|
count -= num2;
|
|
}
|
|
while (count > 0 && (num2 = source.Read(buffer, 0, (int)count)) > 0)
|
|
{
|
|
count -= num2;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
BufferPool.ReleaseBufferToPool(ref buffer);
|
|
}
|
|
}
|
|
if (count > 0)
|
|
{
|
|
throw EoF(null);
|
|
}
|
|
}
|
|
|
|
internal static Exception AddErrorData(Exception exception, ProtoReader source)
|
|
{
|
|
if (exception != null && source != null && !exception.Data.Contains("protoSource"))
|
|
{
|
|
exception.Data.Add("protoSource", string.Format("tag={0}; wire-type={1}; offset={2}; depth={3}", new object[4] { source.fieldNumber, source.wireType, source.position64, source.depth }));
|
|
}
|
|
return exception;
|
|
}
|
|
|
|
private static Exception EoF(ProtoReader source)
|
|
{
|
|
return AddErrorData(new EndOfStreamException(), source);
|
|
}
|
|
|
|
public void AppendExtensionData(IExtensible instance)
|
|
{
|
|
if (instance == null)
|
|
{
|
|
throw new ArgumentNullException("instance");
|
|
}
|
|
IExtension extensionObject = instance.GetExtensionObject(createIfMissing: true);
|
|
bool commit = false;
|
|
Stream stream = extensionObject.BeginAppend();
|
|
try
|
|
{
|
|
using (ProtoWriter protoWriter = ProtoWriter.Create(stream, model))
|
|
{
|
|
AppendExtensionField(protoWriter);
|
|
protoWriter.Close();
|
|
}
|
|
commit = true;
|
|
}
|
|
finally
|
|
{
|
|
extensionObject.EndAppend(stream, commit);
|
|
}
|
|
}
|
|
|
|
private void AppendExtensionField(ProtoWriter writer)
|
|
{
|
|
ProtoWriter.WriteFieldHeader(fieldNumber, wireType, writer);
|
|
switch (wireType)
|
|
{
|
|
case WireType.Fixed32:
|
|
ProtoWriter.WriteInt32(ReadInt32(), writer);
|
|
break;
|
|
case WireType.Variant:
|
|
case WireType.Fixed64:
|
|
case WireType.SignedVariant:
|
|
ProtoWriter.WriteInt64(ReadInt64(), writer);
|
|
break;
|
|
case WireType.String:
|
|
ProtoWriter.WriteBytes(AppendBytes(null, this), writer);
|
|
break;
|
|
case WireType.StartGroup:
|
|
{
|
|
SubItemToken token = StartSubItem(this);
|
|
SubItemToken token2 = ProtoWriter.StartSubItem(null, writer);
|
|
while (ReadFieldHeader() > 0)
|
|
{
|
|
AppendExtensionField(writer);
|
|
}
|
|
EndSubItem(token, this);
|
|
ProtoWriter.EndSubItem(token2, writer);
|
|
break;
|
|
}
|
|
default:
|
|
throw CreateWireTypeException();
|
|
}
|
|
}
|
|
|
|
public static bool HasSubValue(WireType wireType, ProtoReader source)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException("source");
|
|
}
|
|
if (source.blockEnd64 <= source.position64 || wireType == WireType.EndGroup)
|
|
{
|
|
return false;
|
|
}
|
|
source.wireType = wireType;
|
|
return true;
|
|
}
|
|
|
|
internal int GetTypeKey(ref Type type)
|
|
{
|
|
return model.GetKey(ref type);
|
|
}
|
|
|
|
internal Type DeserializeType(string value)
|
|
{
|
|
return TypeModel.DeserializeType(model, value);
|
|
}
|
|
|
|
internal void SetRootObject(object value)
|
|
{
|
|
netCache.SetKeyedObject(0, value);
|
|
trapCount--;
|
|
}
|
|
|
|
public static void NoteObject(object value, ProtoReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw new ArgumentNullException("reader");
|
|
}
|
|
if (reader.trapCount != 0)
|
|
{
|
|
reader.netCache.RegisterTrappedObject(value);
|
|
reader.trapCount--;
|
|
}
|
|
}
|
|
|
|
public Type ReadType()
|
|
{
|
|
return TypeModel.DeserializeType(model, ReadString());
|
|
}
|
|
|
|
internal void TrapNextObject(int newObjectKey)
|
|
{
|
|
trapCount++;
|
|
netCache.SetKeyedObject(newObjectKey, null);
|
|
}
|
|
|
|
internal void CheckFullyConsumed()
|
|
{
|
|
if (isFixedLength)
|
|
{
|
|
if (dataRemaining64 != 0L)
|
|
{
|
|
throw new ProtoException("Incorrect number of bytes consumed");
|
|
}
|
|
}
|
|
else if (available != 0)
|
|
{
|
|
throw new ProtoException("Unconsumed data left in the buffer; this suggests corrupt input");
|
|
}
|
|
}
|
|
|
|
public static object Merge(ProtoReader parent, object from, object to)
|
|
{
|
|
if (parent == null)
|
|
{
|
|
throw new ArgumentNullException("parent");
|
|
}
|
|
TypeModel typeModel = parent.Model;
|
|
SerializationContext serializationContext = parent.Context;
|
|
if (typeModel == null)
|
|
{
|
|
throw new InvalidOperationException("Types cannot be merged unless a type-model has been specified");
|
|
}
|
|
using MemoryStream memoryStream = new MemoryStream();
|
|
typeModel.Serialize(memoryStream, from, serializationContext);
|
|
memoryStream.Position = 0L;
|
|
return typeModel.Deserialize(memoryStream, to, null);
|
|
}
|
|
|
|
internal static ProtoReader Create(Stream source, TypeModel model, SerializationContext context, int len)
|
|
{
|
|
return Create(source, model, context, (long)len);
|
|
}
|
|
|
|
public static ProtoReader Create(Stream source, TypeModel model, SerializationContext context = null, long length = -1L)
|
|
{
|
|
ProtoReader recycled = GetRecycled();
|
|
if (recycled == null)
|
|
{
|
|
return new ProtoReader(source, model, context, length);
|
|
}
|
|
Init(recycled, source, model, context, length);
|
|
return recycled;
|
|
}
|
|
|
|
private static ProtoReader GetRecycled()
|
|
{
|
|
ProtoReader result = lastReader;
|
|
lastReader = null;
|
|
return result;
|
|
}
|
|
|
|
internal static void Recycle(ProtoReader reader)
|
|
{
|
|
if (reader != null)
|
|
{
|
|
reader.Dispose();
|
|
lastReader = reader;
|
|
}
|
|
}
|
|
}
|