2014-07-08 12:58:53 +00:00
using System ;
using System.IO ;
using System.Text ;
using ProtoBuf.Meta ;
#if MF
using OverflowException = System . ApplicationException ;
#endif
#if FEAT_IKVM
using Type = IKVM . Reflection . Type ;
#endif
namespace ProtoBuf
{
/// <summary>
/// Represents an output stream for writing protobuf data.
///
/// Why is the API backwards (static methods with writer arguments)?
/// See: http://marcgravell.blogspot.com/2010/03/last-will-be-first-and-first-will-be.html
/// </summary>
public sealed class ProtoWriter : IDisposable
{
private Stream dest ;
TypeModel model ;
/// <summary>
/// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type).
/// </summary>
/// <param name="value">The object to write.</param>
/// <param name="key">The key that uniquely identifies the type within the model.</param>
/// <param name="writer">The destination.</param>
public static void WriteObject ( object value , int key , ProtoWriter writer )
{
#if FEAT_IKVM
throw new NotSupportedException ( ) ;
#else
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
if ( writer . model = = null )
{
throw new InvalidOperationException ( "Cannot serialize sub-objects unless a model is provided" ) ;
}
SubItemToken token = StartSubItem ( value , writer ) ;
if ( key > = 0 )
{
writer . model . Serialize ( key , value , writer ) ;
}
else if ( writer . model ! = null & & writer . model . TrySerializeAuxiliaryType ( writer , value . GetType ( ) , DataFormat . Default , Serializer . ListItemTag , value , false ) )
{
// all ok
}
else
{
TypeModel . ThrowUnexpectedType ( value . GetType ( ) ) ;
}
EndSubItem ( token , writer ) ;
#endif
}
/// <summary>
/// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type) - but the
/// caller is asserting that this relationship is non-recursive; no recursion check will be
/// performed.
/// </summary>
/// <param name="value">The object to write.</param>
/// <param name="key">The key that uniquely identifies the type within the model.</param>
/// <param name="writer">The destination.</param>
public static void WriteRecursionSafeObject ( object value , int key , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
if ( writer . model = = null )
{
throw new InvalidOperationException ( "Cannot serialize sub-objects unless a model is provided" ) ;
}
SubItemToken token = StartSubItem ( null , writer ) ;
writer . model . Serialize ( key , value , writer ) ;
EndSubItem ( token , writer ) ;
}
internal static void WriteObject ( object value , int key , ProtoWriter writer , PrefixStyle style , int fieldNumber )
{
#if FEAT_IKVM
throw new NotSupportedException ( ) ;
#else
if ( writer . model = = null )
{
throw new InvalidOperationException ( "Cannot serialize sub-objects unless a model is provided" ) ;
}
if ( writer . wireType ! = WireType . None ) throw ProtoWriter . CreateException ( writer ) ;
switch ( style )
{
case PrefixStyle . Base128 :
writer . wireType = WireType . String ;
writer . fieldNumber = fieldNumber ;
if ( fieldNumber > 0 ) WriteHeaderCore ( fieldNumber , WireType . String , writer ) ;
break ;
case PrefixStyle . Fixed32 :
case PrefixStyle . Fixed32BigEndian :
writer . fieldNumber = 0 ;
writer . wireType = WireType . Fixed32 ;
break ;
default :
throw new ArgumentOutOfRangeException ( "style" ) ;
}
SubItemToken token = StartSubItem ( value , writer , true ) ;
if ( key < 0 )
{
if ( ! writer . model . TrySerializeAuxiliaryType ( writer , value . GetType ( ) , DataFormat . Default , Serializer . ListItemTag , value , false ) )
{
TypeModel . ThrowUnexpectedType ( value . GetType ( ) ) ;
}
}
else
{
writer . model . Serialize ( key , value , writer ) ;
}
EndSubItem ( token , writer , style ) ;
#endif
}
internal int GetTypeKey ( ref Type type )
{
return model . GetKey ( ref type ) ;
}
private readonly NetObjectCache netCache = new NetObjectCache ( ) ;
internal NetObjectCache NetCache
{
get { return netCache ; }
}
private int fieldNumber , flushLock ;
WireType wireType ;
internal WireType WireType { get { return wireType ; } }
/// <summary>
/// Writes a field-header, indicating the format of the next data we plan to write.
/// </summary>
public static void WriteFieldHeader ( int fieldNumber , WireType wireType , ProtoWriter writer ) {
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
if ( writer . wireType ! = WireType . None ) throw new InvalidOperationException ( "Cannot write a " + wireType . ToString ( )
+ " header until the " + writer . wireType . ToString ( ) + " data has been written" ) ;
2014-07-08 12:58:53 +00:00
if ( fieldNumber < 0 ) throw new ArgumentOutOfRangeException ( "fieldNumber" ) ;
#if DEBUG
switch ( wireType )
{ // validate requested header-type
case WireType . Fixed32 :
case WireType . Fixed64 :
case WireType . String :
case WireType . StartGroup :
case WireType . SignedVariant :
case WireType . Variant :
break ; // fine
case WireType . None :
case WireType . EndGroup :
default :
2014-07-30 15:32:25 +00:00
throw new ArgumentException ( "Invalid wire-type: " + wireType . ToString ( ) , "wireType" ) ;
2014-07-08 12:58:53 +00:00
}
#endif
if ( writer . packedFieldNumber = = 0 ) {
writer . fieldNumber = fieldNumber ;
writer . wireType = wireType ;
WriteHeaderCore ( fieldNumber , wireType , writer ) ;
}
else if ( writer . packedFieldNumber = = fieldNumber )
{ // we'll set things up, but note we *don't* actually write the header here
switch ( wireType )
{
case WireType . Fixed32 :
case WireType . Fixed64 :
case WireType . Variant :
case WireType . SignedVariant :
break ; // fine
default :
2014-07-30 15:32:25 +00:00
throw new InvalidOperationException ( "Wire-type cannot be encoded as packed: " + wireType . ToString ( ) ) ;
2014-07-08 12:58:53 +00:00
}
writer . fieldNumber = fieldNumber ;
writer . wireType = wireType ;
}
else
{
2014-07-30 15:32:25 +00:00
throw new InvalidOperationException ( "Field mismatch during packed encoding; expected " + writer . packedFieldNumber . ToString ( ) + " but received " + fieldNumber . ToString ( ) ) ;
2014-07-08 12:58:53 +00:00
}
}
internal static void WriteHeaderCore ( int fieldNumber , WireType wireType , ProtoWriter writer )
{
uint header = ( ( ( uint ) fieldNumber ) < < 3 )
| ( ( ( uint ) wireType ) & 7 ) ;
WriteUInt32Variant ( header , writer ) ;
}
/// <summary>
/// Writes a byte-array to the stream; supported wire-types: String
/// </summary>
public static void WriteBytes ( byte [ ] data , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( data = = null ) throw new ArgumentNullException ( "data" ) ;
2014-07-08 12:58:53 +00:00
ProtoWriter . WriteBytes ( data , 0 , data . Length , writer ) ;
}
/// <summary>
/// Writes a byte-array to the stream; supported wire-types: String
/// </summary>
public static void WriteBytes ( byte [ ] data , int offset , int length , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( data = = null ) throw new ArgumentNullException ( "data" ) ;
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed32 :
if ( length ! = 4 ) throw new ArgumentException ( "length" ) ;
goto CopyFixedLength ; // ugly but effective
case WireType . Fixed64 :
if ( length ! = 8 ) throw new ArgumentException ( "length" ) ;
goto CopyFixedLength ; // ugly but effective
case WireType . String :
WriteUInt32Variant ( ( uint ) length , writer ) ;
writer . wireType = WireType . None ;
if ( length = = 0 ) return ;
if ( writer . flushLock ! = 0 | | length < = writer . ioBuffer . Length ) // write to the buffer
{
goto CopyFixedLength ; // ugly but effective
}
// writing data that is bigger than the buffer (and the buffer
// isn't currently locked due to a sub-object needing the size backfilled)
Flush ( writer ) ; // commit any existing data from the buffer
// now just write directly to the underlying stream
writer . dest . Write ( data , offset , length ) ;
writer . position + = length ; // since we've flushed offset etc is 0, and remains
// zero since we're writing directly to the stream
return ;
}
throw CreateException ( writer ) ;
CopyFixedLength : // no point duplicating this lots of times, and don't really want another stackframe
DemandSpace ( length , writer ) ;
Helpers . BlockCopy ( data , offset , writer . ioBuffer , writer . ioIndex , length ) ;
IncrementedAndReset ( length , writer ) ;
}
private static void CopyRawFromStream ( Stream source , ProtoWriter writer )
{
byte [ ] buffer = writer . ioBuffer ;
int space = buffer . Length - writer . ioIndex , bytesRead = 1 ; // 1 here to spoof case where already full
// try filling the buffer first
while ( space > 0 & & ( bytesRead = source . Read ( buffer , writer . ioIndex , space ) ) > 0 )
{
writer . ioIndex + = bytesRead ;
writer . position + = bytesRead ;
space - = bytesRead ;
}
if ( bytesRead < = 0 ) return ; // all done using just the buffer; stream exhausted
// at this point the stream still has data, but buffer is full;
if ( writer . flushLock = = 0 )
{
// flush the buffer and write to the underlying stream instead
Flush ( writer ) ;
while ( ( bytesRead = source . Read ( buffer , 0 , buffer . Length ) ) > 0 )
{
writer . dest . Write ( buffer , 0 , bytesRead ) ;
writer . position + = bytesRead ;
}
}
else
{
do
{
// need more space; resize (double) as necessary,
// requesting a reasonable minimum chunk each time
// (128 is the minimum; there may actually be much
// more space than this in the buffer)
DemandSpace ( 128 , writer ) ;
if ( ( bytesRead = source . Read ( writer . ioBuffer , writer . ioIndex ,
writer . ioBuffer . Length - writer . ioIndex ) ) < = 0 ) break ;
writer . position + = bytesRead ;
writer . ioIndex + = bytesRead ;
} while ( true ) ;
}
}
private static void IncrementedAndReset ( int length , ProtoWriter writer )
{
Helpers . DebugAssert ( length > = 0 ) ;
writer . ioIndex + = length ;
writer . position + = length ;
writer . wireType = WireType . None ;
}
int depth = 0 ;
const int RecursionCheckDepth = 25 ;
/// <summary>
/// Indicates the start of a nested record.
/// </summary>
/// <param name="instance">The instance to write.</param>
/// <param name="writer">The destination.</param>
/// <returns>A token representing the state of the stream; this token is given to EndSubItem.</returns>
public static SubItemToken StartSubItem ( object instance , ProtoWriter writer )
{
return StartSubItem ( instance , writer , false ) ;
}
MutableList recursionStack ;
private void CheckRecursionStackAndPush ( object instance )
{
int hitLevel ;
if ( recursionStack = = null ) { recursionStack = new MutableList ( ) ; }
else if ( instance ! = null & & ( hitLevel = recursionStack . IndexOfReference ( instance ) ) > = 0 )
{
#if DEBUG
Helpers . DebugWriteLine ( "Stack:" ) ;
foreach ( object obj in recursionStack )
{
Helpers . DebugWriteLine ( obj = = null ? "<null>" : obj . ToString ( ) ) ;
}
Helpers . DebugWriteLine ( instance = = null ? "<null>" : instance . ToString ( ) ) ;
#endif
throw new ProtoException ( "Possible recursion detected (offset: " + ( recursionStack . Count - hitLevel ) . ToString ( ) + " level(s)): " + instance . ToString ( ) ) ;
}
recursionStack . Add ( instance ) ;
}
private void PopRecursionStack ( ) { recursionStack . RemoveLast ( ) ; }
private static SubItemToken StartSubItem ( object instance , ProtoWriter writer , bool allowFixed )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
if ( + + writer . depth > RecursionCheckDepth )
{
writer . CheckRecursionStackAndPush ( instance ) ;
}
if ( writer . packedFieldNumber ! = 0 ) throw new InvalidOperationException ( "Cannot begin a sub-item while performing packed encoding" ) ;
switch ( writer . wireType )
{
case WireType . StartGroup :
writer . wireType = WireType . None ;
return new SubItemToken ( - writer . fieldNumber ) ;
case WireType . String :
#if DEBUG
if ( writer . model ! = null & & writer . model . ForwardsOnly )
{
throw new ProtoException ( "Should not be buffering data" ) ;
}
#endif
writer . wireType = WireType . None ;
DemandSpace ( 32 , writer ) ; // make some space in anticipation...
writer . flushLock + + ;
writer . position + + ;
return new SubItemToken ( writer . ioIndex + + ) ; // leave 1 space (optimistic) for length
case WireType . Fixed32 :
{
if ( ! allowFixed ) throw CreateException ( writer ) ;
DemandSpace ( 32 , writer ) ; // make some space in anticipation...
writer . flushLock + + ;
SubItemToken token = new SubItemToken ( writer . ioIndex ) ;
ProtoWriter . IncrementedAndReset ( 4 , writer ) ; // leave 4 space (rigid) for length
return token ;
}
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Indicates the end of a nested record.
/// </summary>
/// <param name="token">The token obtained from StartubItem.</param>
/// <param name="writer">The destination.</param>
public static void EndSubItem ( SubItemToken token , ProtoWriter writer )
{
EndSubItem ( token , writer , PrefixStyle . Base128 ) ;
}
private static void EndSubItem ( SubItemToken token , ProtoWriter writer , PrefixStyle style )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
if ( writer . wireType ! = WireType . None ) { throw CreateException ( writer ) ; }
int value = token . value ;
if ( writer . depth < = 0 ) throw CreateException ( writer ) ;
if ( writer . depth - - > RecursionCheckDepth )
{
writer . PopRecursionStack ( ) ;
}
writer . packedFieldNumber = 0 ; // ending the sub-item always wipes packed encoding
if ( value < 0 )
{ // group - very simple append
WriteHeaderCore ( - value , WireType . EndGroup , writer ) ;
writer . wireType = WireType . None ;
return ;
}
// so we're backfilling the length into an existing sequence
int len ;
switch ( style )
{
case PrefixStyle . Fixed32 :
len = ( int ) ( ( writer . ioIndex - value ) - 4 ) ;
ProtoWriter . WriteInt32ToBuffer ( len , writer . ioBuffer , value ) ;
break ;
case PrefixStyle . Fixed32BigEndian :
len = ( int ) ( ( writer . ioIndex - value ) - 4 ) ;
byte [ ] buffer = writer . ioBuffer ;
ProtoWriter . WriteInt32ToBuffer ( len , buffer , value ) ;
// and swap the byte order
byte b = buffer [ value ] ;
buffer [ value ] = buffer [ value + 3 ] ;
buffer [ value + 3 ] = b ;
b = buffer [ value + 1 ] ;
buffer [ value + 1 ] = buffer [ value + 2 ] ;
buffer [ value + 2 ] = b ;
break ;
case PrefixStyle . Base128 :
// string - complicated because we only reserved one byte;
// if the prefix turns out to need more than this then
// we need to shuffle the existing data
len = ( int ) ( ( writer . ioIndex - value ) - 1 ) ;
int offset = 0 ;
uint tmp = ( uint ) len ;
while ( ( tmp > > = 7 ) ! = 0 ) offset + + ;
if ( offset = = 0 )
{
writer . ioBuffer [ value ] = ( byte ) ( len & 0x7F ) ;
}
else
{
DemandSpace ( offset , writer ) ;
byte [ ] blob = writer . ioBuffer ;
Helpers . BlockCopy ( blob , value + 1 , blob , value + 1 + offset , len ) ;
tmp = ( uint ) len ;
do
{
blob [ value + + ] = ( byte ) ( ( tmp & 0x7F ) | 0x80 ) ;
} while ( ( tmp > > = 7 ) ! = 0 ) ;
blob [ value - 1 ] = ( byte ) ( blob [ value - 1 ] & ~ 0x80 ) ;
writer . position + = offset ;
writer . ioIndex + = offset ;
}
break ;
default :
throw new ArgumentOutOfRangeException ( "style" ) ;
}
// and this object is no longer a blockage - also flush if sensible
const int ADVISORY_FLUSH_SIZE = 1024 ;
if ( - - writer . flushLock = = 0 & & writer . ioIndex > = ADVISORY_FLUSH_SIZE )
{
ProtoWriter . Flush ( writer ) ;
}
}
/// <summary>
/// Creates a new writer against a stream
/// </summary>
/// <param name="dest">The destination stream</param>
/// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to serialize sub-objects</param>
/// <param name="context">Additional context about this serialization operation</param>
public ProtoWriter ( Stream dest , TypeModel model , SerializationContext context )
{
if ( dest = = null ) throw new ArgumentNullException ( "dest" ) ;
if ( ! dest . CanWrite ) throw new ArgumentException ( "Cannot write to stream" , "dest" ) ;
//if (model == null) throw new ArgumentNullException("model");
this . dest = dest ;
this . ioBuffer = BufferPool . GetBuffer ( ) ;
this . model = model ;
this . wireType = WireType . None ;
if ( context = = null ) { context = SerializationContext . Default ; }
else { context . Freeze ( ) ; }
this . context = context ;
}
private readonly SerializationContext context ;
/// <summary>
/// Addition information about this serialization operation.
/// </summary>
public SerializationContext Context { get { return context ; } }
void IDisposable . Dispose ( )
{
Dispose ( ) ;
}
private void Dispose ( )
{ // importantly, this does **not** own the stream, and does not dispose it
if ( dest ! = null )
{
Flush ( this ) ;
dest = null ;
}
model = null ;
BufferPool . ReleaseBufferToPool ( ref ioBuffer ) ;
}
private byte [ ] ioBuffer ;
private int ioIndex ;
2014-07-30 15:32:25 +00:00
// note that this is used by some of the unit tests and should not be removed
2014-07-08 12:58:53 +00:00
internal static int GetPosition ( ProtoWriter writer ) { return writer . position ; }
private int position ;
private static void DemandSpace ( int required , ProtoWriter writer )
{
// check for enough space
if ( ( writer . ioBuffer . Length - writer . ioIndex ) < required )
{
if ( writer . flushLock = = 0 )
{
Flush ( writer ) ; // try emptying the buffer
if ( ( writer . ioBuffer . Length - writer . ioIndex ) > = required ) return ;
}
// either can't empty the buffer, or that didn't help; need more space
BufferPool . ResizeAndFlushLeft ( ref writer . ioBuffer , required + writer . ioIndex , 0 , writer . ioIndex ) ;
}
}
/// <summary>
/// Flushes data to the underlying stream, and releases any resources. The underlying stream is *not* disposed
/// by this operation.
/// </summary>
public void Close ( )
{
if ( depth ! = 0 | | flushLock ! = 0 ) throw new InvalidOperationException ( "Unable to close stream in an incomplete state" ) ;
Dispose ( ) ;
}
internal void CheckDepthFlushlock ( )
{
if ( depth ! = 0 | | flushLock ! = 0 ) throw new InvalidOperationException ( "The writer is in an incomplete state" ) ;
}
/// <summary>
/// Get the TypeModel associated with this writer
/// </summary>
public TypeModel Model { get { return model ; } }
/// <summary>
/// Writes any buffered data (if possible) to the underlying stream.
/// </summary>
/// <param name="writer">The writer to flush</param>
/// <remarks>It is not always possible to fully flush, since some sequences
/// may require values to be back-filled into the byte-stream.</remarks>
internal static void Flush ( ProtoWriter writer )
{
if ( writer . flushLock = = 0 & & writer . ioIndex ! = 0 )
{
writer . dest . Write ( writer . ioBuffer , 0 , writer . ioIndex ) ;
writer . ioIndex = 0 ;
}
}
/// <summary>
/// Writes an unsigned 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
private static void WriteUInt32Variant ( uint value , ProtoWriter writer )
{
DemandSpace ( 5 , writer ) ;
int count = 0 ;
do {
writer . ioBuffer [ writer . ioIndex + + ] = ( byte ) ( ( value & 0x7F ) | 0x80 ) ;
count + + ;
} while ( ( value > > = 7 ) ! = 0 ) ;
writer . ioBuffer [ writer . ioIndex - 1 ] & = 0x7F ;
writer . position + = count ;
}
static readonly UTF8Encoding encoding = new UTF8Encoding ( ) ;
internal static uint Zig ( int value )
{
return ( uint ) ( ( value < < 1 ) ^ ( value > > 31 ) ) ;
}
internal static ulong Zig ( long value )
{
return ( ulong ) ( ( value < < 1 ) ^ ( value > > 63 ) ) ;
}
private static void WriteUInt64Variant ( ulong value , ProtoWriter writer )
{
DemandSpace ( 10 , writer ) ;
int count = 0 ;
do
{
writer . ioBuffer [ writer . ioIndex + + ] = ( byte ) ( ( value & 0x7F ) | 0x80 ) ;
count + + ;
} while ( ( value > > = 7 ) ! = 0 ) ;
writer . ioBuffer [ writer . ioIndex - 1 ] & = 0x7F ;
writer . position + = count ;
}
/// <summary>
/// Writes a string to the stream; supported wire-types: String
/// </summary>
public static void WriteString ( string value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
if ( writer . wireType ! = WireType . String ) throw CreateException ( writer ) ;
if ( value = = null ) throw new ArgumentNullException ( "value" ) ; // written header; now what?
int len = value . Length ;
if ( len = = 0 )
{
WriteUInt32Variant ( 0 , writer ) ;
writer . wireType = WireType . None ;
return ; // just a header
}
#if MF
byte [ ] bytes = encoding . GetBytes ( value ) ;
int actual = bytes . Length ;
writer . WriteUInt32Variant ( ( uint ) actual ) ;
writer . Ensure ( actual ) ;
Helpers . BlockCopy ( bytes , 0 , writer . ioBuffer , writer . ioIndex , actual ) ;
#else
int predicted = encoding . GetByteCount ( value ) ;
WriteUInt32Variant ( ( uint ) predicted , writer ) ;
DemandSpace ( predicted , writer ) ;
int actual = encoding . GetBytes ( value , 0 , value . Length , writer . ioBuffer , writer . ioIndex ) ;
Helpers . DebugAssert ( predicted = = actual ) ;
#endif
IncrementedAndReset ( actual , writer ) ;
}
/// <summary>
/// Writes an unsigned 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public static void WriteUInt64 ( ulong value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed64 :
ProtoWriter . WriteInt64 ( ( long ) value , writer ) ;
return ;
case WireType . Variant :
WriteUInt64Variant ( value , writer ) ;
writer . wireType = WireType . None ;
return ;
case WireType . Fixed32 :
checked { ProtoWriter . WriteUInt32 ( ( uint ) value , writer ) ; }
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Writes a signed 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public static void WriteInt64 ( long value , ProtoWriter writer )
{
byte [ ] buffer ;
int index ;
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed64 :
DemandSpace ( 8 , writer ) ;
buffer = writer . ioBuffer ;
index = writer . ioIndex ;
buffer [ index ] = ( byte ) value ;
buffer [ index + 1 ] = ( byte ) ( value > > 8 ) ;
buffer [ index + 2 ] = ( byte ) ( value > > 16 ) ;
buffer [ index + 3 ] = ( byte ) ( value > > 24 ) ;
buffer [ index + 4 ] = ( byte ) ( value > > 32 ) ;
buffer [ index + 5 ] = ( byte ) ( value > > 40 ) ;
buffer [ index + 6 ] = ( byte ) ( value > > 48 ) ;
buffer [ index + 7 ] = ( byte ) ( value > > 56 ) ;
IncrementedAndReset ( 8 , writer ) ;
return ;
case WireType . SignedVariant :
WriteUInt64Variant ( Zig ( value ) , writer ) ;
writer . wireType = WireType . None ;
return ;
case WireType . Variant :
if ( value > = 0 )
{
WriteUInt64Variant ( ( ulong ) value , writer ) ;
writer . wireType = WireType . None ;
}
else
{
DemandSpace ( 10 , writer ) ;
buffer = writer . ioBuffer ;
index = writer . ioIndex ;
buffer [ index ] = ( byte ) ( value | 0x80 ) ;
buffer [ index + 1 ] = ( byte ) ( ( int ) ( value > > 7 ) | 0x80 ) ;
buffer [ index + 2 ] = ( byte ) ( ( int ) ( value > > 14 ) | 0x80 ) ;
buffer [ index + 3 ] = ( byte ) ( ( int ) ( value > > 21 ) | 0x80 ) ;
buffer [ index + 4 ] = ( byte ) ( ( int ) ( value > > 28 ) | 0x80 ) ;
buffer [ index + 5 ] = ( byte ) ( ( int ) ( value > > 35 ) | 0x80 ) ;
buffer [ index + 6 ] = ( byte ) ( ( int ) ( value > > 42 ) | 0x80 ) ;
buffer [ index + 7 ] = ( byte ) ( ( int ) ( value > > 49 ) | 0x80 ) ;
buffer [ index + 8 ] = ( byte ) ( ( int ) ( value > > 56 ) | 0x80 ) ;
buffer [ index + 9 ] = 0x01 ; // sign bit
IncrementedAndReset ( 10 , writer ) ;
}
return ;
case WireType . Fixed32 :
checked { WriteInt32 ( ( int ) value , writer ) ; }
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public static void WriteUInt32 ( uint value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed32 :
ProtoWriter . WriteInt32 ( ( int ) value , writer ) ;
return ;
case WireType . Fixed64 :
ProtoWriter . WriteInt64 ( ( int ) value , writer ) ;
return ;
case WireType . Variant :
WriteUInt32Variant ( value , writer ) ;
writer . wireType = WireType . None ;
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Writes a signed 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public static void WriteInt16 ( short value , ProtoWriter writer )
{
ProtoWriter . WriteInt32 ( value , writer ) ;
}
/// <summary>
/// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public static void WriteUInt16 ( ushort value , ProtoWriter writer )
{
ProtoWriter . WriteUInt32 ( value , writer ) ;
}
/// <summary>
/// Writes an unsigned 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public static void WriteByte ( byte value , ProtoWriter writer )
{
ProtoWriter . WriteUInt32 ( value , writer ) ;
}
/// <summary>
/// Writes a signed 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public static void WriteSByte ( sbyte value , ProtoWriter writer )
{
ProtoWriter . WriteInt32 ( value , writer ) ;
}
private static void WriteInt32ToBuffer ( int value , byte [ ] buffer , int index )
{
buffer [ index ] = ( byte ) value ;
buffer [ index + 1 ] = ( byte ) ( value > > 8 ) ;
buffer [ index + 2 ] = ( byte ) ( value > > 16 ) ;
buffer [ index + 3 ] = ( byte ) ( value > > 24 ) ;
}
/// <summary>
/// Writes a signed 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
/// </summary>
public static void WriteInt32 ( int value , ProtoWriter writer )
{
byte [ ] buffer ;
int index ;
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed32 :
DemandSpace ( 4 , writer ) ;
WriteInt32ToBuffer ( value , writer . ioBuffer , writer . ioIndex ) ;
IncrementedAndReset ( 4 , writer ) ;
return ;
case WireType . Fixed64 :
DemandSpace ( 8 , writer ) ;
buffer = writer . ioBuffer ;
index = writer . ioIndex ;
buffer [ index ] = ( byte ) value ;
buffer [ index + 1 ] = ( byte ) ( value > > 8 ) ;
buffer [ index + 2 ] = ( byte ) ( value > > 16 ) ;
buffer [ index + 3 ] = ( byte ) ( value > > 24 ) ;
buffer [ index + 4 ] = buffer [ index + 5 ] =
buffer [ index + 6 ] = buffer [ index + 7 ] = 0 ;
IncrementedAndReset ( 8 , writer ) ;
return ;
case WireType . SignedVariant :
WriteUInt32Variant ( Zig ( value ) , writer ) ;
writer . wireType = WireType . None ;
return ;
case WireType . Variant :
if ( value > = 0 )
{
WriteUInt32Variant ( ( uint ) value , writer ) ;
writer . wireType = WireType . None ;
}
else
{
DemandSpace ( 10 , writer ) ;
buffer = writer . ioBuffer ;
index = writer . ioIndex ;
buffer [ index ] = ( byte ) ( value | 0x80 ) ;
buffer [ index + 1 ] = ( byte ) ( ( value > > 7 ) | 0x80 ) ;
buffer [ index + 2 ] = ( byte ) ( ( value > > 14 ) | 0x80 ) ;
buffer [ index + 3 ] = ( byte ) ( ( value > > 21 ) | 0x80 ) ;
buffer [ index + 4 ] = ( byte ) ( ( value > > 28 ) | 0x80 ) ;
buffer [ index + 5 ] = buffer [ index + 6 ] =
buffer [ index + 7 ] = buffer [ index + 8 ] = ( byte ) 0xFF ;
buffer [ index + 9 ] = ( byte ) 0x01 ;
IncrementedAndReset ( 10 , writer ) ;
}
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Writes a double-precision number to the stream; supported wire-types: Fixed32, Fixed64
/// </summary>
public
#if ! FEAT_SAFE
unsafe
#endif
static void WriteDouble ( double value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed32 :
float f = ( float ) value ;
if ( Helpers . IsInfinity ( f )
& & ! Helpers . IsInfinity ( value ) )
{
throw new OverflowException ( ) ;
}
ProtoWriter . WriteSingle ( f , writer ) ;
return ;
case WireType . Fixed64 :
#if FEAT_SAFE
ProtoWriter . WriteInt64 ( BitConverter . ToInt64 ( BitConverter . GetBytes ( value ) , 0 ) , writer ) ;
#else
ProtoWriter . WriteInt64 ( * ( long * ) & value , writer ) ;
#endif
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Writes a single-precision number to the stream; supported wire-types: Fixed32, Fixed64
/// </summary>
public
#if ! FEAT_SAFE
unsafe
#endif
static void WriteSingle ( float value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
switch ( writer . wireType )
{
case WireType . Fixed32 :
#if FEAT_SAFE
ProtoWriter . WriteInt32 ( BitConverter . ToInt32 ( BitConverter . GetBytes ( value ) , 0 ) , writer ) ;
#else
ProtoWriter . WriteInt32 ( * ( int * ) & value , writer ) ;
#endif
return ;
case WireType . Fixed64 :
ProtoWriter . WriteDouble ( ( double ) value , writer ) ;
return ;
default :
throw CreateException ( writer ) ;
}
}
/// <summary>
/// Throws an exception indicating that the given enum cannot be mapped to a serialized value.
/// </summary>
public static void ThrowEnumException ( ProtoWriter writer , object enumValue )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
string rhs = enumValue = = null ? "<null>" : ( enumValue . GetType ( ) . FullName + "." + enumValue . ToString ( ) ) ;
2014-07-30 15:32:25 +00:00
throw new ProtoException ( "No wire-value is mapped to the enum " + rhs + " at position " + writer . position . ToString ( ) ) ;
2014-07-08 12:58:53 +00:00
}
// general purpose serialization exception message
internal static Exception CreateException ( ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
return new ProtoException ( "Invalid serialization operation with wire-type " + writer . wireType . ToString ( ) + " at position " + writer . position . ToString ( ) ) ;
2014-07-08 12:58:53 +00:00
}
/// <summary>
/// Writes a boolean to the stream; supported wire-types: Variant, Fixed32, Fixed64
/// </summary>
public static void WriteBoolean ( bool value , ProtoWriter writer )
{
ProtoWriter . WriteUInt32 ( value ? ( uint ) 1 : ( uint ) 0 , writer ) ;
}
/// <summary>
/// Copies any extension data stored for the instance to the underlying stream
/// </summary>
public static void AppendExtensionData ( IExtensible instance , ProtoWriter writer )
{
if ( instance = = null ) throw new ArgumentNullException ( "instance" ) ;
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
// we expect the writer to be raw here; the extension data will have the
// header detail, so we'll copy it implicitly
if ( writer . wireType ! = WireType . None ) throw CreateException ( writer ) ;
IExtension extn = instance . GetExtensionObject ( false ) ;
if ( extn ! = null )
{
// unusually we *don't* want "using" here; the "finally" does that, with
// the extension object being responsible for disposal etc
Stream source = extn . BeginQuery ( ) ;
try
{
CopyRawFromStream ( source , writer ) ;
}
finally { extn . EndQuery ( source ) ; }
}
}
private int packedFieldNumber ;
/// <summary>
/// Used for packed encoding; indicates that the next field should be skipped rather than
/// a field header written. Note that the field number must match, else an exception is thrown
/// when the attempt is made to write the (incorrect) field. The wire-type is taken from the
/// subsequent call to WriteFieldHeader. Only primitive types can be packed.
/// </summary>
public static void SetPackedField ( int fieldNumber , ProtoWriter writer )
{
if ( fieldNumber < = 0 ) throw new ArgumentOutOfRangeException ( "fieldNumber" ) ;
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
writer . packedFieldNumber = fieldNumber ;
}
internal string SerializeType ( System . Type type )
{
return TypeModel . SerializeType ( model , type ) ;
}
/// <summary>
/// Specifies a known root object to use during reference-tracked serialization
/// </summary>
public void SetRootObject ( object value )
{
NetCache . SetKeyedObject ( NetObjectCache . Root , value ) ;
}
/// <summary>
/// Writes a Type to the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String
/// </summary>
public static void WriteType ( System . Type value , ProtoWriter writer )
{
2014-07-30 15:32:25 +00:00
if ( writer = = null ) throw new ArgumentNullException ( "writer" ) ;
2014-07-08 12:58:53 +00:00
WriteString ( writer . SerializeType ( value ) , writer ) ;
}
}
}