2015-04-21 18:27:52 +00:00
using System ;
2014-07-08 12:58:53 +00:00
using System.IO ;
using System.Text ;
using ProtoBuf.Meta ;
#if FEAT_IKVM
using Type = IKVM . Reflection . Type ;
#endif
#if MF
using EndOfStreamException = System . ApplicationException ;
using OverflowException = System . ApplicationException ;
#endif
namespace ProtoBuf
{
/// <summary>
/// A stateful reader, used to read a protobuf stream. Typical usage would be (sequentially) to call
/// ReadFieldHeader and (after matching the field) an appropriate Read* method.
/// </summary>
public sealed class ProtoReader : IDisposable
{
2015-04-21 18:27:52 +00:00
private Stream source ;
private byte [ ] ioBuffer ;
private TypeModel model ;
private int fieldNumber , depth , dataRemaining , ioIndex , position , available , blockEnd ;
private WireType wireType ;
private bool isFixedLength , internStrings ;
2014-07-30 15:32:25 +00:00
private NetObjectCache netCache ;
// this is how many outstanding objects do not currently have
// values for the purposes of reference tracking; we'll default
// to just trapping the root object
// note: objects are trapped (the ref and key mapped) via NoteObject
2015-04-21 18:27:52 +00:00
private uint trapCount ; // uint is so we can use beq/bne more efficiently than bgt
2014-07-30 15:32:25 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Gets the number of the field being processed.
/// </summary>
2015-04-21 18:27:52 +00:00
public int FieldNumber
{
get { return fieldNumber ; }
}
2014-07-08 12:58:53 +00:00
/// <summary>
/// Indicates the underlying proto serialization format on the wire.
/// </summary>
2015-04-21 18:27:52 +00:00
public WireType WireType
{
get { return wireType ; }
}
2014-07-08 12:58:53 +00:00
/// <summary>
/// Creates a new reader against a stream
/// </summary>
/// <param name="source">The source stream</param>
/// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
/// <param name="context">Additional context about this serialization operation</param>
2015-04-21 18:27:52 +00:00
public ProtoReader ( Stream source , TypeModel model , SerializationContext context )
2014-07-30 15:32:25 +00:00
{
Init ( this , source , model , context , TO_EOF ) ;
}
2014-07-08 12:58:53 +00:00
2014-07-30 15:32:25 +00:00
internal const int TO_EOF = - 1 ;
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Gets / sets a flag indicating whether strings should be checked for repetition; if
/// true, any repeated UTF-8 byte sequence will result in the same String instance, rather
/// than a second instance of the same string. Enabled by default. Note that this uses
/// a <i>custom</i> interner - the system-wide string interner is not used.
/// </summary>
2015-04-21 18:27:52 +00:00
public bool InternStrings
{
get { return internStrings ; }
set { internStrings = value ; }
}
2014-07-08 12:58:53 +00:00
/// <summary>
/// Creates a new reader against a stream
/// </summary>
/// <param name="source">The source stream</param>
/// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
/// <param name="context">Additional context about this serialization operation</param>
/// <param name="length">The number of bytes to read, or -1 to read until the end of the stream</param>
public ProtoReader ( Stream source , TypeModel model , SerializationContext context , int length )
2014-07-30 15:32:25 +00:00
{
Init ( this , source , model , context , length ) ;
}
2015-04-21 18:27:52 +00:00
private static void Init ( ProtoReader reader , Stream source , TypeModel model , SerializationContext context ,
int length )
2014-07-08 12:58:53 +00:00
{
if ( source = = null ) throw new ArgumentNullException ( "source" ) ;
if ( ! source . CanRead ) throw new ArgumentException ( "Cannot read from stream" , "source" ) ;
2014-07-30 15:32:25 +00:00
reader . source = source ;
reader . ioBuffer = BufferPool . GetBuffer ( ) ;
reader . model = model ;
bool isFixedLength = length > = 0 ;
reader . isFixedLength = isFixedLength ;
reader . dataRemaining = isFixedLength ? length : 0 ;
2014-07-08 12:58:53 +00:00
2015-04-21 18:27:52 +00:00
if ( context = = null )
{
context = SerializationContext . Default ;
}
else
{
context . Freeze ( ) ;
}
2014-07-30 15:32:25 +00:00
reader . context = context ;
reader . position = reader . available = reader . depth = reader . fieldNumber = reader . ioIndex = 0 ;
reader . blockEnd = int . MaxValue ;
reader . internStrings = true ;
reader . wireType = WireType . None ;
reader . trapCount = 1 ;
2015-04-21 18:27:52 +00:00
if ( reader . netCache = = null ) reader . netCache = new NetObjectCache ( ) ;
2014-07-08 12:58:53 +00:00
}
2014-07-30 15:32:25 +00:00
private SerializationContext context ;
2014-07-08 12:58:53 +00:00
/// <summary>
/// Addition information about this deserialization operation.
/// </summary>
2015-04-21 18:27:52 +00:00
public SerializationContext Context
{
get { return context ; }
}
2014-07-08 12:58:53 +00:00
/// <summary>
/// Releases resources used by the reader, but importantly <b>does not</b> Dispose the
/// underlying stream; in many typical use-cases the stream is used for different
/// processes, so it is assumed that the consumer will Dispose their stream separately.
/// </summary>
public void Dispose ( )
{
// importantly, this does **not** own the stream, and does not dispose it
source = null ;
model = null ;
BufferPool . ReleaseBufferToPool ( ref ioBuffer ) ;
2015-04-21 18:27:52 +00:00
if ( stringInterner ! = null ) stringInterner . Clear ( ) ;
if ( netCache ! = null ) netCache . Clear ( ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
internal int TryReadUInt32VariantWithoutMoving ( bool trimNegative , out uint value )
{
if ( available < 10 ) Ensure ( 10 , false ) ;
if ( available = = 0 )
{
value = 0 ;
return 0 ;
}
int readPos = ioIndex ;
value = ioBuffer [ readPos + + ] ;
if ( ( value & 0x80 ) = = 0 ) return 1 ;
value & = 0x7F ;
if ( available = = 1 ) throw EoF ( this ) ;
uint chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 7 ;
if ( ( chunk & 0x80 ) = = 0 ) return 2 ;
if ( available = = 2 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 14 ;
if ( ( chunk & 0x80 ) = = 0 ) return 3 ;
if ( available = = 3 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 21 ;
if ( ( chunk & 0x80 ) = = 0 ) return 4 ;
if ( available = = 4 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos ] ;
value | = chunk < < 28 ; // can only use 4 bits from this chunk
if ( ( chunk & 0xF0 ) = = 0 ) return 5 ;
if ( trimNegative // allow for -ve values
& & ( chunk & 0xF0 ) = = 0xF0
& & available > = 10
2015-04-21 18:27:52 +00:00
& & ioBuffer [ + + readPos ] = = 0xFF
& & ioBuffer [ + + readPos ] = = 0xFF
& & ioBuffer [ + + readPos ] = = 0xFF
& & ioBuffer [ + + readPos ] = = 0xFF
& & ioBuffer [ + + readPos ] = = 0x01 )
2014-07-08 12:58:53 +00:00
{
return 10 ;
}
throw AddErrorData ( new OverflowException ( ) , this ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private uint ReadUInt32Variant ( bool trimNegative )
{
uint value ;
int read = TryReadUInt32VariantWithoutMoving ( trimNegative , out value ) ;
if ( read > 0 )
{
ioIndex + = read ;
available - = read ;
position + = read ;
return value ;
}
throw EoF ( this ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private bool TryReadUInt32Variant ( out uint value )
{
int read = TryReadUInt32VariantWithoutMoving ( false , out value ) ;
if ( read > 0 )
{
ioIndex + = read ;
available - = read ;
position + = read ;
return true ;
}
return false ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads an unsigned 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public uint ReadUInt32 ( )
{
switch ( wireType )
{
case WireType . Variant :
return ReadUInt32Variant ( false ) ;
case WireType . Fixed32 :
if ( available < 4 ) Ensure ( 4 , true ) ;
position + = 4 ;
available - = 4 ;
2015-04-21 18:27:52 +00:00
return ( ( uint ) ioBuffer [ ioIndex + + ] )
| ( ( ( uint ) ioBuffer [ ioIndex + + ] ) < < 8 )
| ( ( ( uint ) ioBuffer [ ioIndex + + ] ) < < 16 )
| ( ( ( uint ) ioBuffer [ ioIndex + + ] ) < < 24 ) ;
2014-07-08 12:58:53 +00:00
case WireType . Fixed64 :
ulong val = ReadUInt64 ( ) ;
2015-04-21 18:27:52 +00:00
checked
{
return ( uint ) val ;
}
2014-07-08 12:58:53 +00:00
default :
throw CreateWireTypeException ( ) ;
}
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Returns the position of the current reader (note that this is not necessarily the same as the position
/// in the underlying stream, if multiple readers are used on the same stream)
/// </summary>
2015-04-21 18:27:52 +00:00
public int Position
{
get { return position ; }
}
2014-07-08 12:58:53 +00:00
internal void Ensure ( int count , bool strict )
{
Helpers . DebugAssert ( available < = count , "Asking for data without checking first" ) ;
if ( count > ioBuffer . Length )
{
BufferPool . ResizeAndFlushLeft ( ref ioBuffer , count , ioIndex , available ) ;
ioIndex = 0 ;
}
else if ( ioIndex + count > = ioBuffer . Length )
{
// need to shift the buffer data to the left to make space
Helpers . BlockCopy ( ioBuffer , ioIndex , ioBuffer , 0 , available ) ;
ioIndex = 0 ;
}
count - = available ;
int writePos = ioIndex + available , bytesRead ;
int canRead = ioBuffer . Length - writePos ;
if ( isFixedLength )
2015-04-21 18:27:52 +00:00
{
// throttle it if needed
2014-07-08 12:58:53 +00:00
if ( dataRemaining < canRead ) canRead = dataRemaining ;
}
while ( count > 0 & & canRead > 0 & & ( bytesRead = source . Read ( ioBuffer , writePos , canRead ) ) > 0 )
{
available + = bytesRead ;
count - = bytesRead ;
canRead - = bytesRead ;
writePos + = bytesRead ;
2015-04-21 18:27:52 +00:00
if ( isFixedLength )
{
dataRemaining - = bytesRead ;
}
2014-07-08 12:58:53 +00:00
}
if ( strict & & count > 0 )
{
throw EoF ( this ) ;
}
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a signed 16-bit integer from the stream: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public short ReadInt16 ( )
{
2015-04-21 18:27:52 +00:00
checked
{
return ( short ) ReadInt32 ( ) ;
}
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads an unsigned 16-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public ushort ReadUInt16 ( )
{
2015-04-21 18:27:52 +00:00
checked
{
return ( ushort ) ReadUInt32 ( ) ;
}
2014-07-08 12:58:53 +00:00
}
/// <summary>
/// Reads an unsigned 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public byte ReadByte ( )
{
2015-04-21 18:27:52 +00:00
checked
{
return ( byte ) ReadUInt32 ( ) ;
}
2014-07-08 12:58:53 +00:00
}
/// <summary>
/// Reads a signed 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public sbyte ReadSByte ( )
{
2015-04-21 18:27:52 +00:00
checked
{
return ( sbyte ) ReadInt32 ( ) ;
}
2014-07-08 12:58:53 +00:00
}
/// <summary>
/// Reads a signed 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public int ReadInt32 ( )
{
switch ( wireType )
{
case WireType . Variant :
2015-04-21 18:27:52 +00:00
return ( int ) ReadUInt32Variant ( true ) ;
2014-07-08 12:58:53 +00:00
case WireType . Fixed32 :
if ( available < 4 ) Ensure ( 4 , true ) ;
position + = 4 ;
available - = 4 ;
2015-04-21 18:27:52 +00:00
return ( ( int ) ioBuffer [ ioIndex + + ] )
| ( ( ( int ) ioBuffer [ ioIndex + + ] ) < < 8 )
| ( ( ( int ) ioBuffer [ ioIndex + + ] ) < < 16 )
| ( ( ( int ) ioBuffer [ ioIndex + + ] ) < < 24 ) ;
2014-07-08 12:58:53 +00:00
case WireType . Fixed64 :
long l = ReadInt64 ( ) ;
2015-04-21 18:27:52 +00:00
checked
{
return ( int ) l ;
}
2014-07-08 12:58:53 +00:00
case WireType . SignedVariant :
return Zag ( ReadUInt32Variant ( true ) ) ;
default :
throw CreateWireTypeException ( ) ;
}
}
2015-04-21 18:27:52 +00:00
private const long Int64Msb = ( ( long ) 1 ) < < 63 ;
private const int Int32Msb = ( ( int ) 1 ) < < 31 ;
2014-07-08 12:58:53 +00:00
private static int Zag ( uint ziggedValue )
{
2015-04-21 18:27:52 +00:00
int value = ( int ) ziggedValue ;
2014-07-08 12:58:53 +00:00
return ( - ( value & 0x01 ) ) ^ ( ( value > > 1 ) & ~ ProtoReader . Int32Msb ) ;
}
private static long Zag ( ulong ziggedValue )
{
2015-04-21 18:27:52 +00:00
long value = ( long ) ziggedValue ;
2014-07-08 12:58:53 +00:00
return ( - ( value & 0x01L ) ) ^ ( ( value > > 1 ) & ~ ProtoReader . Int64Msb ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a signed 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public long ReadInt64 ( )
{
switch ( wireType )
{
case WireType . Variant :
2015-04-21 18:27:52 +00:00
return ( long ) ReadUInt64Variant ( ) ;
2014-07-08 12:58:53 +00:00
case WireType . Fixed32 :
return ReadInt32 ( ) ;
case WireType . Fixed64 :
if ( available < 8 ) Ensure ( 8 , true ) ;
position + = 8 ;
available - = 8 ;
2015-04-21 18:27:52 +00:00
return ( ( long ) ioBuffer [ ioIndex + + ] )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 8 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 16 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 24 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 32 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 40 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 48 )
| ( ( ( long ) ioBuffer [ ioIndex + + ] ) < < 56 ) ;
2014-07-08 12:58:53 +00:00
case WireType . SignedVariant :
return Zag ( ReadUInt64Variant ( ) ) ;
default :
throw CreateWireTypeException ( ) ;
}
}
private int TryReadUInt64VariantWithoutMoving ( out ulong value )
{
if ( available < 10 ) Ensure ( 10 , false ) ;
if ( available = = 0 )
{
value = 0 ;
return 0 ;
}
int readPos = ioIndex ;
value = ioBuffer [ readPos + + ] ;
if ( ( value & 0x80 ) = = 0 ) return 1 ;
value & = 0x7F ;
if ( available = = 1 ) throw EoF ( this ) ;
ulong chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 7 ;
if ( ( chunk & 0x80 ) = = 0 ) return 2 ;
if ( available = = 2 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 14 ;
if ( ( chunk & 0x80 ) = = 0 ) return 3 ;
if ( available = = 3 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 21 ;
if ( ( chunk & 0x80 ) = = 0 ) return 4 ;
if ( available = = 4 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 28 ;
if ( ( chunk & 0x80 ) = = 0 ) return 5 ;
if ( available = = 5 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 35 ;
if ( ( chunk & 0x80 ) = = 0 ) return 6 ;
if ( available = = 6 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 42 ;
if ( ( chunk & 0x80 ) = = 0 ) return 7 ;
if ( available = = 7 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 49 ;
if ( ( chunk & 0x80 ) = = 0 ) return 8 ;
if ( available = = 8 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos + + ] ;
value | = ( chunk & 0x7F ) < < 56 ;
if ( ( chunk & 0x80 ) = = 0 ) return 9 ;
if ( available = = 9 ) throw EoF ( this ) ;
chunk = ioBuffer [ readPos ] ;
value | = chunk < < 63 ; // can only use 1 bit from this chunk
2015-04-21 18:27:52 +00:00
if ( ( chunk & ~ ( ulong ) 0x01 ) ! = 0 ) throw AddErrorData ( new OverflowException ( ) , this ) ;
2014-07-08 12:58:53 +00:00
return 10 ;
}
private ulong ReadUInt64Variant ( )
{
ulong value ;
int read = TryReadUInt64VariantWithoutMoving ( out value ) ;
if ( read > 0 )
{
ioIndex + = read ;
available - = read ;
position + = read ;
return value ;
}
throw EoF ( this ) ;
}
#if NO_GENERICS
private System . Collections . Hashtable stringInterner ;
private string Intern ( string value )
{
if ( value = = null ) return null ;
if ( value . Length = = 0 ) return "" ;
if ( stringInterner = = null )
{
stringInterner = new System . Collections . Hashtable ( ) ;
stringInterner . Add ( value , value ) ;
}
else if ( stringInterner . ContainsKey ( value ) )
{
value = ( string ) stringInterner [ value ] ;
}
else
{
stringInterner . Add ( value , value ) ;
}
return value ;
}
#else
2015-04-21 18:27:52 +00:00
private System . Collections . Generic . Dictionary < string , string > stringInterner ;
private string Intern ( string value )
2014-07-08 12:58:53 +00:00
{
if ( value = = null ) return null ;
if ( value . Length = = 0 ) return "" ;
string found ;
if ( stringInterner = = null )
{
stringInterner = new System . Collections . Generic . Dictionary < string , string > ( ) ;
2015-04-21 18:27:52 +00:00
stringInterner . Add ( value , value ) ;
2014-07-08 12:58:53 +00:00
}
else if ( stringInterner . TryGetValue ( value , out found ) )
{
value = found ;
}
else
{
stringInterner . Add ( value , value ) ;
}
return value ;
}
#endif
2015-04-21 18:27:52 +00:00
private static readonly UTF8Encoding encoding = new UTF8Encoding ( ) ;
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a string from the stream (using UTF8); supported wire-types: String
/// </summary>
public string ReadString ( )
{
if ( wireType = = WireType . String )
{
2015-04-21 18:27:52 +00:00
int bytes = ( int ) ReadUInt32Variant ( false ) ;
2014-07-08 12:58:53 +00:00
if ( bytes = = 0 ) return "" ;
if ( available < bytes ) Ensure ( bytes , true ) ;
#if MF
byte [ ] tmp ;
if ( ioIndex = = 0 & & bytes = = ioBuffer . Length ) {
// unlikely, but...
tmp = ioBuffer ;
} else {
tmp = new byte [ bytes ] ;
Helpers . BlockCopy ( ioBuffer , ioIndex , tmp , 0 , bytes ) ;
}
string s = new string ( encoding . GetChars ( tmp ) ) ;
#else
string s = encoding . GetString ( ioBuffer , ioIndex , bytes ) ;
#endif
2015-04-21 18:27:52 +00:00
if ( internStrings )
{
s = Intern ( s ) ;
}
2014-07-08 12:58:53 +00:00
available - = bytes ;
position + = bytes ;
ioIndex + = bytes ;
return s ;
}
throw CreateWireTypeException ( ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Throws an exception indication that the given value cannot be mapped to an enum.
/// </summary>
public void ThrowEnumException ( System . Type type , int value )
{
string desc = type = = null ? "<null>" : type . FullName ;
2015-04-21 18:27:52 +00:00
throw AddErrorData (
new ProtoException ( "No " + desc + " enum is mapped to the wire-value " + value . ToString ( ) ) , this ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private Exception CreateWireTypeException ( )
{
2015-04-21 18:27:52 +00:00
return
CreateException (
"Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see http://stackoverflow.com/q/2152978/23354" ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private Exception CreateException ( string message )
{
return AddErrorData ( new ProtoException ( message ) , this ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a double-precision number from the stream; supported wire-types: Fixed32, Fixed64
/// </summary>
public
#if ! FEAT_SAFE
2015-04-21 18:27:52 +00:00
unsafe
2014-07-08 12:58:53 +00:00
#endif
2015-04-21 18:27:52 +00:00
double ReadDouble ( )
2014-07-08 12:58:53 +00:00
{
switch ( wireType )
{
case WireType . Fixed32 :
return ReadSingle ( ) ;
case WireType . Fixed64 :
long value = ReadInt64 ( ) ;
#if FEAT_SAFE
return BitConverter . ToDouble ( BitConverter . GetBytes ( value ) , 0 ) ;
#else
2015-04-21 18:27:52 +00:00
return * ( double * ) & value ;
2014-07-08 12:58:53 +00:00
#endif
default :
throw CreateWireTypeException ( ) ;
}
}
/// <summary>
/// Reads (merges) a sub-message from the stream, internally calling StartSubItem and EndSubItem, and (in between)
/// parsing the message in accordance with the model associated with the reader
/// </summary>
public static object ReadObject ( object value , int key , ProtoReader reader )
{
#if FEAT_IKVM
throw new NotSupportedException ( ) ;
#else
return ReadTypedObject ( value , key , reader , null ) ;
#endif
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
#if ! FEAT_IKVM
internal static object ReadTypedObject ( object value , int key , ProtoReader reader , Type type )
{
if ( reader . model = = null )
{
2015-04-21 18:27:52 +00:00
throw AddErrorData (
new InvalidOperationException ( "Cannot deserialize sub-objects unless a model is provided" ) , reader ) ;
2014-07-08 12:58:53 +00:00
}
SubItemToken token = ProtoReader . StartSubItem ( reader ) ;
if ( key > = 0 )
{
value = reader . model . Deserialize ( key , value , reader ) ;
}
2015-04-21 18:27:52 +00:00
else if ( type ! = null & &
reader . model . TryDeserializeAuxiliaryType ( reader , DataFormat . Default , Serializer . ListItemTag , type ,
ref value , true , false , true , false ) )
2014-07-08 12:58:53 +00:00
{
// ok
}
else
{
TypeModel . ThrowUnexpectedType ( type ) ;
}
ProtoReader . EndSubItem ( token , reader ) ;
return value ;
}
#endif
/// <summary>
/// Makes the end of consuming a nested message in the stream; the stream must be either at the correct EndGroup
/// marker, or all fields of the sub-message must have been consumed (in either case, this means ReadFieldHeader
/// should return zero)
/// </summary>
public static void EndSubItem ( SubItemToken token , ProtoReader reader )
{
2014-07-30 15:32:25 +00:00
if ( reader = = null ) throw new ArgumentNullException ( "reader" ) ;
2014-07-08 12:58:53 +00:00
int value = token . value ;
switch ( reader . wireType )
{
case WireType . EndGroup :
if ( value > = 0 ) throw AddErrorData ( new ArgumentException ( "token" ) , reader ) ;
2015-04-21 18:27:52 +00:00
if ( - value ! = reader . fieldNumber )
throw reader . CreateException ( "Wrong group was ended" ) ; // wrong group ended!
2014-07-08 12:58:53 +00:00
reader . wireType = WireType . None ; // this releases ReadFieldHeader
reader . depth - - ;
break ;
// case WireType.None: // TODO reinstate once reads reset the wire-type
default :
if ( value < reader . position ) throw reader . CreateException ( "Sub-message not read entirely" ) ;
if ( reader . blockEnd ! = reader . position & & reader . blockEnd ! = int . MaxValue )
{
throw reader . CreateException ( "Sub-message not read correctly" ) ;
}
reader . blockEnd = value ;
reader . depth - - ;
break ;
/ * default :
throw reader . BorkedIt ( ) ; * /
}
}
/// <summary>
/// Begins consuming a nested message in the stream; supported wire-types: StartGroup, String
/// </summary>
/// <remarks>The token returned must be help and used when callining EndSubItem</remarks>
public static SubItemToken StartSubItem ( ProtoReader reader )
{
2014-07-30 15:32:25 +00:00
if ( reader = = null ) throw new ArgumentNullException ( "reader" ) ;
2014-07-08 12:58:53 +00:00
switch ( reader . wireType )
{
case WireType . StartGroup :
reader . wireType = WireType . None ; // to prevent glitches from double-calling
reader . depth + + ;
return new SubItemToken ( - reader . fieldNumber ) ;
case WireType . String :
2015-04-21 18:27:52 +00:00
int len = ( int ) reader . ReadUInt32Variant ( false ) ;
2014-07-08 12:58:53 +00:00
if ( len < 0 ) throw AddErrorData ( new InvalidOperationException ( ) , reader ) ;
int lastEnd = reader . blockEnd ;
reader . blockEnd = reader . position + len ;
reader . depth + + ;
return new SubItemToken ( lastEnd ) ;
default :
throw reader . CreateWireTypeException ( ) ; // throws
}
}
/// <summary>
/// Reads a field header from the stream, setting the wire-type and retuning the field number. If no
/// more fields are available, then 0 is returned. This methods respects sub-messages.
/// </summary>
public int ReadFieldHeader ( )
{
// at the end of a group the caller must call EndSubItem to release the
// reader (which moves the status to Error, since ReadFieldHeader must
// then be called)
2015-04-21 18:27:52 +00:00
if ( blockEnd < = position | | wireType = = WireType . EndGroup )
{
return 0 ;
}
2014-07-08 12:58:53 +00:00
uint tag ;
if ( TryReadUInt32Variant ( out tag ) )
{
2015-04-21 18:27:52 +00:00
wireType = ( WireType ) ( tag & 7 ) ;
fieldNumber = ( int ) ( tag > > 3 ) ;
if ( fieldNumber < 1 )
throw new ProtoException ( "Invalid field in source data: " + fieldNumber . ToString ( ) ) ;
2014-07-08 12:58:53 +00:00
}
else
{
wireType = WireType . None ;
fieldNumber = 0 ;
}
if ( wireType = = ProtoBuf . WireType . EndGroup )
{
if ( depth > 0 ) return 0 ; // spoof an end, but note we still set the field-number
2015-04-21 18:27:52 +00:00
throw new ProtoException (
"Unexpected end-group in source data; this usually means the source data is corrupt" ) ;
2014-07-08 12:58:53 +00:00
}
return fieldNumber ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Looks ahead to see whether the next field in the stream is what we expect
/// (typically; what we've just finished reading - for example ot read successive list items)
/// </summary>
public bool TryReadFieldHeader ( int field )
{
// check for virtual end of stream
2015-04-21 18:27:52 +00:00
if ( blockEnd < = position | | wireType = = WireType . EndGroup )
{
return false ;
}
2014-07-08 12:58:53 +00:00
uint tag ;
int read = TryReadUInt32VariantWithoutMoving ( false , out tag ) ;
WireType tmpWireType ; // need to catch this to exclude (early) any "end group" tokens
2015-04-21 18:27:52 +00:00
if ( read > 0 & & ( ( int ) tag > > 3 ) = = field
& & ( tmpWireType = ( WireType ) ( tag & 7 ) ) ! = WireType . EndGroup )
2014-07-08 12:58:53 +00:00
{
wireType = tmpWireType ;
fieldNumber = field ;
position + = read ;
ioIndex + = read ;
available - = read ;
return true ;
}
return false ;
}
/// <summary>
/// Get the TypeModel associated with this reader
/// </summary>
2015-04-21 18:27:52 +00:00
public TypeModel Model
{
get { return model ; }
}
2014-07-08 12:58:53 +00:00
/// <summary>
/// Compares the streams current wire-type to the hinted wire-type, updating the reader if necessary; for example,
/// a Variant may be updated to SignedVariant. If the hinted wire-type is unrelated then no change is made.
/// </summary>
public void Hint ( WireType wireType )
{
2015-04-21 18:27:52 +00:00
if ( this . wireType = = wireType )
{
} // fine; everything as we expect
else if ( ( ( int ) wireType & 7 ) = = ( int ) this . wireType )
{
// the underling type is a match; we're customising it with an extension
2014-07-08 12:58:53 +00:00
this . wireType = wireType ;
}
// note no error here; we're OK about using alternative data
}
/// <summary>
/// Verifies that the stream's current wire-type is as expected, or a specialized sub-type (for example,
/// SignedVariant) - in which case the current wire-type is updated. Otherwise an exception is thrown.
/// </summary>
public void Assert ( WireType wireType )
{
2015-04-21 18:27:52 +00:00
if ( this . wireType = = wireType )
{
} // fine; everything as we expect
else if ( ( ( int ) wireType & 7 ) = = ( int ) this . wireType )
{
// the underling type is a match; we're customising it with an extension
2014-07-08 12:58:53 +00:00
this . wireType = wireType ;
}
else
2015-04-21 18:27:52 +00:00
{
// nope; that is *not* what we were expecting!
2014-07-08 12:58:53 +00:00
throw CreateWireTypeException ( ) ;
}
}
/// <summary>
/// Discards the data for the current field.
/// </summary>
public void SkipField ( )
{
switch ( wireType )
{
case WireType . Fixed32 :
2015-04-21 18:27:52 +00:00
if ( available < 4 ) Ensure ( 4 , true ) ;
2014-07-08 12:58:53 +00:00
available - = 4 ;
ioIndex + = 4 ;
position + = 4 ;
return ;
case WireType . Fixed64 :
if ( available < 8 ) Ensure ( 8 , true ) ;
available - = 8 ;
ioIndex + = 8 ;
position + = 8 ;
return ;
case WireType . String :
2015-04-21 18:27:52 +00:00
int len = ( int ) ReadUInt32Variant ( false ) ;
2014-07-08 12:58:53 +00:00
if ( len < = available )
2015-04-21 18:27:52 +00:00
{
// just jump it!
2014-07-08 12:58:53 +00:00
available - = len ;
ioIndex + = len ;
position + = len ;
return ;
}
// everything remaining in the buffer is garbage
position + = len ; // assumes success, but if it fails we're screwed anyway
len - = available ; // discount anything we've got to-hand
ioIndex = available = 0 ; // note that we have no data in the buffer
if ( isFixedLength )
{
if ( len > dataRemaining ) throw EoF ( this ) ;
// else assume we're going to be OK
dataRemaining - = len ;
}
ProtoReader . Seek ( source , len , ioBuffer ) ;
return ;
case WireType . Variant :
case WireType . SignedVariant :
ReadUInt64Variant ( ) ; // and drop it
return ;
case WireType . StartGroup :
int originalFieldNumber = this . fieldNumber ;
depth + + ; // need to satisfy the sanity-checks in ReadFieldHeader
2015-04-21 18:27:52 +00:00
while ( ReadFieldHeader ( ) > 0 )
{
SkipField ( ) ;
}
2014-07-08 12:58:53 +00:00
depth - - ;
if ( wireType = = WireType . EndGroup & & fieldNumber = = originalFieldNumber )
2015-04-21 18:27:52 +00:00
{
// we expect to exit in a similar state to how we entered
2014-07-08 12:58:53 +00:00
wireType = ProtoBuf . WireType . None ;
return ;
}
throw CreateWireTypeException ( ) ;
case WireType . None : // treat as explicit errorr
case WireType . EndGroup : // treat as explicit error
default : // treat as implicit error
throw CreateWireTypeException ( ) ;
}
}
/// <summary>
/// Reads an unsigned 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public ulong ReadUInt64 ( )
{
switch ( wireType )
{
case WireType . Variant :
return ReadUInt64Variant ( ) ;
case WireType . Fixed32 :
return ReadUInt32 ( ) ;
case WireType . Fixed64 :
if ( available < 8 ) Ensure ( 8 , true ) ;
position + = 8 ;
available - = 8 ;
2015-04-21 18:27:52 +00:00
return ( ( ulong ) 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 ) ;
2014-07-08 12:58:53 +00:00
default :
throw CreateWireTypeException ( ) ;
}
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a single-precision number from the stream; supported wire-types: Fixed32, Fixed64
/// </summary>
public
#if ! FEAT_SAFE
2015-04-21 18:27:52 +00:00
unsafe
2014-07-08 12:58:53 +00:00
#endif
2015-04-21 18:27:52 +00:00
float ReadSingle ( )
2014-07-08 12:58:53 +00:00
{
switch ( wireType )
{
case WireType . Fixed32 :
2015-04-21 18:27:52 +00:00
{
int value = ReadInt32 ( ) ;
2014-07-08 12:58:53 +00:00
#if FEAT_SAFE
return BitConverter . ToSingle ( BitConverter . GetBytes ( value ) , 0 ) ;
#else
2015-04-21 18:27:52 +00:00
return * ( float * ) & value ;
2014-07-08 12:58:53 +00:00
#endif
2015-04-21 18:27:52 +00:00
}
2014-07-08 12:58:53 +00:00
case WireType . Fixed64 :
2015-04-21 18:27:52 +00:00
{
double value = ReadDouble ( ) ;
float f = ( float ) value ;
if ( Helpers . IsInfinity ( f )
& & ! Helpers . IsInfinity ( value ) )
2014-07-08 12:58:53 +00:00
{
2015-04-21 18:27:52 +00:00
throw AddErrorData ( new OverflowException ( ) , this ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
return f ;
}
2014-07-08 12:58:53 +00:00
default :
throw CreateWireTypeException ( ) ;
}
}
/// <summary>
/// Reads a boolean value from the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
/// <returns></returns>
public bool ReadBoolean ( )
{
switch ( ReadUInt32 ( ) )
{
2015-04-21 18:27:52 +00:00
case 0 :
return false ;
case 1 :
return true ;
default :
throw CreateException ( "Unexpected boolean value" ) ;
2014-07-08 12:58:53 +00:00
}
}
private static readonly byte [ ] EmptyBlob = new byte [ 0 ] ;
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a byte-sequence from the stream, appending them to an existing byte-sequence (which can be null); supported wire-types: String
/// </summary>
public static byte [ ] AppendBytes ( byte [ ] value , ProtoReader reader )
{
2014-07-30 15:32:25 +00:00
if ( reader = = null ) throw new ArgumentNullException ( "reader" ) ;
2014-07-08 12:58:53 +00:00
switch ( reader . wireType )
{
case WireType . String :
2015-04-21 18:27:52 +00:00
int len = ( int ) reader . ReadUInt32Variant ( false ) ;
2014-07-08 12:58:53 +00:00
reader . wireType = WireType . None ;
if ( len = = 0 ) return value = = null ? EmptyBlob : value ;
int offset ;
if ( value = = null | | value . Length = = 0 )
{
offset = 0 ;
value = new byte [ len ] ;
}
else
{
offset = value . Length ;
byte [ ] tmp = new byte [ value . Length + len ] ;
Helpers . BlockCopy ( value , 0 , tmp , 0 , value . Length ) ;
value = tmp ;
}
// value is now sized with the final length, and (if necessary)
// contains the old data up to "offset"
reader . position + = len ; // assume success
while ( len > reader . available )
{
if ( reader . available > 0 )
{
// copy what we *do* have
Helpers . BlockCopy ( reader . ioBuffer , reader . ioIndex , value , offset , reader . available ) ;
len - = reader . available ;
offset + = reader . available ;
reader . ioIndex = reader . available = 0 ; // we've drained the buffer
}
// now refill the buffer (without overflowing it)
int count = len > reader . ioBuffer . Length ? reader . ioBuffer . Length : len ;
if ( count > 0 ) reader . Ensure ( count , true ) ;
}
// at this point, we know that len <= available
if ( len > 0 )
2015-04-21 18:27:52 +00:00
{
// still need data, but we have enough buffered
2014-07-08 12:58:53 +00:00
Helpers . BlockCopy ( reader . ioBuffer , reader . ioIndex , value , offset , len ) ;
reader . ioIndex + = len ;
reader . available - = len ;
}
return value ;
default :
throw reader . CreateWireTypeException ( ) ;
}
}
2014-07-30 15:32:25 +00:00
//static byte[] ReadBytes(Stream stream, int length)
//{
// if (stream == null) throw new ArgumentNullException("stream");
// if (length < 0) throw new ArgumentOutOfRangeException("length");
// byte[] buffer = new byte[length];
// int offset = 0, read;
// while (length > 0 && (read = stream.Read(buffer, offset, length)) > 0)
// {
// length -= read;
// }
// if (length > 0) throw EoF(null);
// return buffer;
//}
2014-07-08 12:58:53 +00:00
private static int ReadByteOrThrow ( Stream source )
{
int val = source . ReadByte ( ) ;
if ( val < 0 ) throw EoF ( null ) ;
return val ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length
/// reader to be created.
/// </summary>
public static int ReadLengthPrefix ( Stream source , bool expectHeader , PrefixStyle style , out int fieldNumber )
{
int bytesRead ;
return ReadLengthPrefix ( source , expectHeader , style , out fieldNumber , out bytesRead ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a little-endian encoded integer. An exception is thrown if the data is not all available.
/// </summary>
public static int DirectReadLittleEndianInt32 ( Stream source )
{
return ReadByteOrThrow ( source )
2015-04-21 18:27:52 +00:00
| ( ReadByteOrThrow ( source ) < < 8 )
| ( ReadByteOrThrow ( source ) < < 16 )
| ( ReadByteOrThrow ( source ) < < 24 ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a big-endian encoded integer. An exception is thrown if the data is not all available.
/// </summary>
public static int DirectReadBigEndianInt32 ( Stream source )
{
return ( ReadByteOrThrow ( source ) < < 24 )
2015-04-21 18:27:52 +00:00
| ( ReadByteOrThrow ( source ) < < 16 )
| ( ReadByteOrThrow ( source ) < < 8 )
| ReadByteOrThrow ( source ) ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a varint encoded integer. An exception is thrown if the data is not all available.
/// </summary>
public static int DirectReadVarintInt32 ( Stream source )
{
uint val ;
int bytes = TryReadUInt32Variant ( source , out val ) ;
if ( bytes < = 0 ) throw EoF ( null ) ;
return ( int ) val ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a string (of a given lenth, in bytes) directly from the source into a pre-existing buffer. An exception is thrown if the data is not all available.
/// </summary>
public static void DirectReadBytes ( Stream source , byte [ ] buffer , int offset , int count )
{
int read ;
2014-07-30 15:32:25 +00:00
if ( source = = null ) throw new ArgumentNullException ( "source" ) ;
2015-04-21 18:27:52 +00:00
while ( count > 0 & & ( read = source . Read ( buffer , offset , count ) ) > 0 )
2014-07-08 12:58:53 +00:00
{
count - = read ;
offset + = read ;
}
if ( count > 0 ) throw EoF ( null ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a given number of bytes directly from the source. An exception is thrown if the data is not all available.
/// </summary>
public static byte [ ] DirectReadBytes ( Stream source , int count )
{
byte [ ] buffer = new byte [ count ] ;
DirectReadBytes ( source , buffer , 0 , count ) ;
return buffer ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Reads a string (of a given lenth, in bytes) directly from the source. An exception is thrown if the data is not all available.
/// </summary>
public static string DirectReadString ( Stream source , int length )
{
byte [ ] buffer = new byte [ length ] ;
DirectReadBytes ( source , buffer , 0 , length ) ;
return Encoding . UTF8 . GetString ( buffer , 0 , length ) ;
}
/// <summary>
/// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length
/// reader to be created.
/// </summary>
2015-04-21 18:27:52 +00:00
public static int ReadLengthPrefix ( Stream source , bool expectHeader , PrefixStyle style , out int fieldNumber ,
out int bytesRead )
2014-07-08 12:58:53 +00:00
{
fieldNumber = 0 ;
switch ( style )
{
case PrefixStyle . None :
bytesRead = 0 ;
return int . MaxValue ;
case PrefixStyle . Base128 :
uint val ;
int tmpBytesRead ;
bytesRead = 0 ;
if ( expectHeader )
{
tmpBytesRead = ProtoReader . TryReadUInt32Variant ( source , out val ) ;
bytesRead + = tmpBytesRead ;
if ( tmpBytesRead > 0 )
{
2015-04-21 18:27:52 +00:00
if ( ( val & 7 ) ! = ( uint ) WireType . String )
{
// got a header, but it isn't a string
2014-07-08 12:58:53 +00:00
throw new InvalidOperationException ( ) ;
}
2015-04-21 18:27:52 +00:00
fieldNumber = ( int ) ( val > > 3 ) ;
2014-07-08 12:58:53 +00:00
tmpBytesRead = ProtoReader . TryReadUInt32Variant ( source , out val ) ;
bytesRead + = tmpBytesRead ;
if ( bytesRead = = 0 )
2015-04-21 18:27:52 +00:00
{
// got a header, but no length
2014-07-08 12:58:53 +00:00
throw EoF ( null ) ;
}
2015-04-21 18:27:52 +00:00
return ( int ) val ;
2014-07-08 12:58:53 +00:00
}
else
2015-04-21 18:27:52 +00:00
{
// no header
2014-07-08 12:58:53 +00:00
bytesRead = 0 ;
return - 1 ;
}
}
// check for a length
tmpBytesRead = ProtoReader . TryReadUInt32Variant ( source , out val ) ;
bytesRead + = tmpBytesRead ;
2015-04-21 18:27:52 +00:00
return bytesRead < 0 ? - 1 : ( int ) val ;
2014-07-08 12:58:53 +00:00
case PrefixStyle . Fixed32 :
2015-04-21 18:27:52 +00:00
{
int b = source . ReadByte ( ) ;
if ( b < 0 )
2014-07-08 12:58:53 +00:00
{
2015-04-21 18:27:52 +00:00
bytesRead = 0 ;
return - 1 ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
bytesRead = 4 ;
return b
| ( ReadByteOrThrow ( source ) < < 8 )
| ( ReadByteOrThrow ( source ) < < 16 )
| ( ReadByteOrThrow ( source ) < < 24 ) ;
}
2014-07-08 12:58:53 +00:00
case PrefixStyle . Fixed32BigEndian :
2015-04-21 18:27:52 +00:00
{
int b = source . ReadByte ( ) ;
if ( b < 0 )
2014-07-08 12:58:53 +00:00
{
2015-04-21 18:27:52 +00:00
bytesRead = 0 ;
return - 1 ;
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
bytesRead = 4 ;
return ( b < < 24 )
| ( ReadByteOrThrow ( source ) < < 16 )
| ( ReadByteOrThrow ( source ) < < 8 )
| ReadByteOrThrow ( source ) ;
}
2014-07-08 12:58:53 +00:00
default :
throw new ArgumentOutOfRangeException ( "style" ) ;
}
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <returns>The number of bytes consumed; 0 if no data available</returns>
private static int TryReadUInt32Variant ( Stream source , out uint value )
{
value = 0 ;
int b = source . ReadByte ( ) ;
2015-04-21 18:27:52 +00:00
if ( b < 0 )
{
return 0 ;
}
value = ( uint ) b ;
if ( ( value & 0x80 ) = = 0 )
{
return 1 ;
}
2014-07-08 12:58:53 +00:00
value & = 0x7F ;
b = source . ReadByte ( ) ;
if ( b < 0 ) throw EoF ( null ) ;
2015-04-21 18:27:52 +00:00
value | = ( ( uint ) b & 0x7F ) < < 7 ;
2014-07-08 12:58:53 +00:00
if ( ( b & 0x80 ) = = 0 ) return 2 ;
b = source . ReadByte ( ) ;
if ( b < 0 ) throw EoF ( null ) ;
2015-04-21 18:27:52 +00:00
value | = ( ( uint ) b & 0x7F ) < < 14 ;
2014-07-08 12:58:53 +00:00
if ( ( b & 0x80 ) = = 0 ) return 3 ;
b = source . ReadByte ( ) ;
if ( b < 0 ) throw EoF ( null ) ;
2015-04-21 18:27:52 +00:00
value | = ( ( uint ) b & 0x7F ) < < 21 ;
2014-07-08 12:58:53 +00:00
if ( ( b & 0x80 ) = = 0 ) return 4 ;
b = source . ReadByte ( ) ;
if ( b < 0 ) throw EoF ( null ) ;
2015-04-21 18:27:52 +00:00
value | = ( uint ) b < < 28 ; // can only use 4 bits from this chunk
2014-07-08 12:58:53 +00:00
if ( ( b & 0xF0 ) = = 0 ) return 5 ;
throw new OverflowException ( ) ;
}
internal static void Seek ( Stream source , int count , byte [ ] buffer )
{
if ( source . CanSeek )
{
source . Seek ( count , SeekOrigin . Current ) ;
count = 0 ;
}
else if ( buffer ! = null )
{
int bytesRead ;
while ( count > buffer . Length & & ( bytesRead = source . Read ( buffer , 0 , buffer . Length ) ) > 0 )
{
count - = bytesRead ;
}
while ( count > 0 & & ( bytesRead = source . Read ( buffer , 0 , count ) ) > 0 )
{
count - = bytesRead ;
}
}
else // borrow a buffer
{
buffer = BufferPool . GetBuffer ( ) ;
try
{
int bytesRead ;
while ( count > buffer . Length & & ( bytesRead = source . Read ( buffer , 0 , buffer . Length ) ) > 0 )
{
count - = bytesRead ;
}
while ( count > 0 & & ( bytesRead = source . Read ( buffer , 0 , count ) ) > 0 )
{
count - = bytesRead ;
}
}
finally
{
BufferPool . ReleaseBufferToPool ( ref buffer ) ;
}
}
if ( count > 0 ) throw EoF ( null ) ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
internal static Exception AddErrorData ( Exception exception , ProtoReader source )
{
#if ! CF & & ! FX11 & & ! PORTABLE
if ( exception ! = null & & source ! = null & & ! exception . Data . Contains ( "protoSource" ) )
{
exception . Data . Add ( "protoSource" , string . Format ( "tag={0}; wire-type={1}; offset={2}; depth={3}" ,
source . fieldNumber , source . wireType , source . position , source . depth ) ) ;
}
#endif
return exception ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private static Exception EoF ( ProtoReader source )
{
return AddErrorData ( new EndOfStreamException ( ) , source ) ;
}
/// <summary>
/// Copies the current field into the instance as extension data
/// </summary>
public void AppendExtensionData ( IExtensible instance )
{
if ( instance = = null ) throw new ArgumentNullException ( "instance" ) ;
IExtension extn = instance . GetExtensionObject ( true ) ;
bool commit = false ;
// unusually we *don't* want "using" here; the "finally" does that, with
// the extension object being responsible for disposal etc
Stream dest = extn . BeginAppend ( ) ;
try
{
//TODO: replace this with stream-based, buffered raw copying
using ( ProtoWriter writer = new ProtoWriter ( dest , model , null ) )
{
AppendExtensionField ( writer ) ;
writer . Close ( ) ;
}
commit = true ;
}
2015-04-21 18:27:52 +00:00
finally
{
extn . EndAppend ( dest , commit ) ;
}
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
private void AppendExtensionField ( ProtoWriter writer )
{
//TODO: replace this with stream-based, buffered raw copying
ProtoWriter . WriteFieldHeader ( fieldNumber , wireType , writer ) ;
switch ( wireType )
{
case WireType . Fixed32 :
ProtoWriter . WriteInt32 ( ReadInt32 ( ) , writer ) ;
return ;
case WireType . Variant :
case WireType . SignedVariant :
case WireType . Fixed64 :
ProtoWriter . WriteInt64 ( ReadInt64 ( ) , writer ) ;
return ;
case WireType . String :
ProtoWriter . WriteBytes ( AppendBytes ( null , this ) , writer ) ;
return ;
case WireType . StartGroup :
SubItemToken readerToken = StartSubItem ( this ) ,
writerToken = ProtoWriter . StartSubItem ( null , writer ) ;
2015-04-21 18:27:52 +00:00
while ( ReadFieldHeader ( ) > 0 )
{
AppendExtensionField ( writer ) ;
}
2014-07-08 12:58:53 +00:00
EndSubItem ( readerToken , this ) ;
ProtoWriter . EndSubItem ( writerToken , writer ) ;
return ;
case WireType . None : // treat as explicit errorr
case WireType . EndGroup : // treat as explicit error
default : // treat as implicit error
throw CreateWireTypeException ( ) ;
}
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Indicates whether the reader still has data remaining in the current sub-item,
/// additionally setting the wire-type for the next field if there is more data.
/// This is used when decoding packed data.
/// </summary>
public static bool HasSubValue ( ProtoBuf . WireType wireType , ProtoReader source )
{
2014-07-30 15:32:25 +00:00
if ( source = = null ) throw new ArgumentNullException ( "source" ) ;
2014-07-08 12:58:53 +00:00
// check for virtual end of stream
2015-04-21 18:27:52 +00:00
if ( source . blockEnd < = source . position | | wireType = = WireType . EndGroup )
{
return false ;
}
2014-07-08 12:58:53 +00:00
source . wireType = wireType ;
return true ;
}
internal int GetTypeKey ( ref Type type )
{
return model . GetKey ( ref type ) ;
}
internal NetObjectCache NetCache
{
get { return netCache ; }
}
internal System . Type DeserializeType ( string value )
{
return TypeModel . DeserializeType ( model , value ) ;
}
internal void SetRootObject ( object value )
{
netCache . SetKeyedObject ( NetObjectCache . Root , value ) ;
trapCount - - ;
}
2015-04-21 18:27:52 +00:00
2014-07-08 12:58:53 +00:00
/// <summary>
/// Utility method, not intended for public use; this helps maintain the root object is complex scenarios
/// </summary>
public static void NoteObject ( object value , ProtoReader reader )
{
2014-07-30 15:32:25 +00:00
if ( reader = = null ) throw new ArgumentNullException ( "reader" ) ;
2015-04-21 18:27:52 +00:00
if ( reader . trapCount ! = 0 )
2014-07-08 12:58:53 +00:00
{
reader . netCache . RegisterTrappedObject ( value ) ;
reader . trapCount - - ;
}
}
/// <summary>
/// Reads a Type from the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String
/// </summary>
public System . Type ReadType ( )
{
return TypeModel . DeserializeType ( model , ReadString ( ) ) ;
}
internal void TrapNextObject ( int newObjectKey )
{
trapCount + + ;
netCache . SetKeyedObject ( newObjectKey , null ) ; // use null as a temp
}
internal void CheckFullyConsumed ( )
{
if ( isFixedLength )
{
if ( dataRemaining ! = 0 ) throw new ProtoException ( "Incorrect number of bytes consumed" ) ;
}
else
{
2015-04-21 18:27:52 +00:00
if ( available ! = 0 )
throw new ProtoException ( "Unconsumed data left in the buffer; this suggests corrupt input" ) ;
2014-07-08 12:58:53 +00:00
}
}
/// <summary>
/// Merge two objects using the details from the current reader; this is used to change the type
/// of objects when an inheritance relationship is discovered later than usual during deserilazation.
/// </summary>
public static object Merge ( ProtoReader parent , object from , object to )
{
2014-07-30 15:32:25 +00:00
if ( parent = = null ) throw new ArgumentNullException ( "parent" ) ;
2014-07-08 12:58:53 +00:00
TypeModel model = parent . Model ;
SerializationContext ctx = parent . Context ;
2015-04-21 18:27:52 +00:00
if ( model = = null )
throw new InvalidOperationException ( "Types cannot be merged unless a type-model has been specified" ) ;
2014-07-08 12:58:53 +00:00
using ( MemoryStream ms = new MemoryStream ( ) )
{
model . Serialize ( ms , from , ctx ) ;
ms . Position = 0 ;
return model . Deserialize ( ms , to , null ) ;
}
}
2014-07-30 15:32:25 +00:00
2015-04-21 18:27:52 +00:00
#region RECYCLER
2014-07-30 15:32:25 +00:00
internal static ProtoReader Create ( Stream source , TypeModel model , SerializationContext context , int len )
{
ProtoReader reader = GetRecycled ( ) ;
if ( reader = = null )
{
return new ProtoReader ( source , model , context , len ) ;
}
Init ( reader , source , model , context , len ) ;
return reader ;
}
#if ! PLAT_NO_THREADSTATIC
2015-04-21 18:27:52 +00:00
[ThreadStatic] private static ProtoReader lastReader ;
2014-07-30 15:32:25 +00:00
private static ProtoReader GetRecycled ( )
{
ProtoReader tmp = lastReader ;
lastReader = null ;
return tmp ;
}
2015-04-21 18:27:52 +00:00
2014-07-30 15:32:25 +00:00
internal static void Recycle ( ProtoReader reader )
{
2015-04-21 18:27:52 +00:00
if ( reader ! = null )
2014-07-30 15:32:25 +00:00
{
reader . Dispose ( ) ;
lastReader = reader ;
}
}
#elif ! PLAT_NO_INTERLOCKED
private static object lastReader ;
private static ProtoReader GetRecycled ( )
{
return ( ProtoReader ) System . Threading . Interlocked . Exchange ( ref lastReader , null ) ;
}
internal static void Recycle ( ProtoReader reader )
{
if ( reader ! = null )
{
reader . Dispose ( ) ;
System . Threading . Interlocked . Exchange ( ref lastReader , reader ) ;
}
}
#else
private static readonly object recycleLock = new object ( ) ;
private static ProtoReader lastReader ;
private static ProtoReader GetRecycled ( )
{
lock ( recycleLock )
{
ProtoReader tmp = lastReader ;
lastReader = null ;
return tmp ;
}
}
internal static void Recycle ( ProtoReader reader )
{
if ( reader ! = null )
{
reader . Dispose ( ) ;
lock ( recycleLock )
{
lastReader = reader ;
}
}
}
#endif
2015-04-21 18:27:52 +00:00
#endregion
2014-07-08 12:58:53 +00:00
}
2015-04-21 18:27:52 +00:00
}