2019-06-12 21:56:55 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Runtime.InteropServices ;
using System.Text ;
namespace Capnp
{
/// <summary>
/// Implements the heart of serialization. Exposes all functionality to encode serialized data.
/// Although it is public, you should not use it directly. Instead, use the reader, writer, and domain class adapters which are produced
/// by the code generator. Particularly, those writer classes are actually specializations of SerializerState, adding convenience methods
/// for accessing the struct's fields.
/// </summary>
public class SerializerState : IStructSerializer
{
/// <summary>
/// Constructs a SerializerState instance for use in RPC context.
/// This particularly means that the capability table will be initialized.
/// </summary>
/// <typeparam name="T">Type of state (must inherit from SerializerState)</typeparam>
/// <returns>root object state</returns>
public static T CreateForRpc < T > ( ) where T : SerializerState , new ( )
{
var mb = MessageBuilder . Create ( ) ;
mb . InitCapTable ( ) ;
var s = new T ( ) ;
s . Bind ( mb ) ;
return s ;
}
internal MessageBuilder MsgBuilder { get ; set ; }
internal ISegmentAllocator Allocator = > MsgBuilder ? . Allocator ;
internal List < Rpc . ConsumedCapability > Caps = > MsgBuilder ? . Caps ;
internal SerializerState Owner { get ; set ; }
internal int OwnerSlot { get ; set ; }
internal uint SegmentIndex { get ; set ; }
internal int Offset { get ; set ; }
internal uint WordsAllocated { get ; set ; }
internal int ListElementCount { get ; set ; }
internal ushort StructDataCount { get ; set ; }
internal ushort StructPtrCount { get ; set ; }
internal ObjectKind Kind { get ; set ; }
internal uint CapabilityIndex { get ; set ; }
SerializerState [ ] _linkedStates ;
/// <summary>
/// Constructs an unbound serializer state.
/// </summary>
public SerializerState ( )
{
Offset = - 1 ;
ListElementCount = - 1 ;
}
/// <summary>
/// Constructs a serializer state which is bound to a particular message builder.
/// </summary>
/// <param name="messageBuilder">message builder to bind</param>
public SerializerState ( MessageBuilder messageBuilder )
{
MsgBuilder = messageBuilder ? ? throw new ArgumentNullException ( nameof ( messageBuilder ) ) ;
Offset = - 1 ;
ListElementCount = - 1 ;
}
internal void Bind ( MessageBuilder messageBuilder )
{
MsgBuilder = messageBuilder ? ? throw new ArgumentNullException ( nameof ( messageBuilder ) ) ;
}
internal void Bind ( SerializerState owner , int ownerSlot )
{
Owner = owner ? ? throw new ArgumentNullException ( nameof ( owner ) ) ;
OwnerSlot = ownerSlot ;
MsgBuilder = owner . MsgBuilder ;
}
/// <summary>
/// Represents this state by a different serializer state specialization. This is similar to a type-cast: The underlying object remains the same,
/// but the specialization adds a particular "view" on that data.
/// </summary>
/// <typeparam name="TS">target serializer state type</typeparam>
/// <returns>serializer state instance</returns>
/// <exception cref="InvalidOperationException">The target serializer state is incompatible to this instance, because the instances do not agree on the
/// kind of underlying object (e.g. struct with particular data/pointer section size, list of something)</exception>
public TS Rewrap < TS > ( ) where TS : SerializerState , new ( )
{
if ( this is TS ts )
return ts ;
ts = new TS ( ) ;
InvalidOperationException InvalidWrap ( ) = >
new InvalidOperationException ( "Incompatible cast" ) ;
switch ( ts . Kind )
{
case ObjectKind . Struct :
case ObjectKind . ListOfStructs :
if ( ts . Kind ! = Kind | |
ts . StructDataCount ! = StructDataCount | |
ts . StructPtrCount ! = StructPtrCount )
throw InvalidWrap ( ) ;
break ;
case ObjectKind . Nil :
break ;
default :
if ( ts . Kind ! = Kind )
throw InvalidWrap ( ) ;
break ;
}
if ( Owner ! = null )
ts . Bind ( Owner , OwnerSlot ) ;
else
ts . Bind ( MsgBuilder ) ;
ts . SegmentIndex = SegmentIndex ;
ts . Offset = Offset ;
ts . ListElementCount = ListElementCount ;
ts . StructDataCount = StructDataCount ;
ts . StructPtrCount = StructPtrCount ;
ts . Kind = Kind ;
ts . CapabilityIndex = CapabilityIndex ;
ts . _linkedStates = _linkedStates ;
return ts ;
}
internal void InheritFrom ( SerializerState other )
{
SegmentIndex = other . SegmentIndex ;
Offset = other . Offset ;
ListElementCount = other . ListElementCount ;
StructDataCount = other . StructDataCount ;
StructPtrCount = other . StructPtrCount ;
Kind = other . Kind ;
CapabilityIndex = other . CapabilityIndex ;
_linkedStates = other . _linkedStates ;
}
/// <summary>
/// Whether storage space for the underlying object was already allocated. Note that allocation happens
/// lazily, i.e. constructing a SerializerState and binding it to a MessageBuilder does NOT yet result in allocation.
/// </summary>
public bool IsAllocated = > Offset > = 0 ;
Span < ulong > SegmentSpan = > IsAllocated ? Allocator . Segments [ ( int ) SegmentIndex ] . Span : Span < ulong > . Empty ;
Span < ulong > FarSpan ( uint index ) = > Allocator . Segments [ ( int ) index ] . Span ;
/// <summary>
/// Given this state describes a struct and is allocated, returns the struct's data section.
/// </summary>
public Span < ulong > StructDataSection = > SegmentSpan . Slice ( Offset , StructDataCount ) ;
/// <summary>
/// Returns the allocated memory slice (given this state already is allocated). Note that this definition is somewhat
/// non-symmetric to <code>DeserializerState.RawData</code>. Never mind: You should not use it directly, anyway.
/// </summary>
public Span < ulong > RawData = > SegmentSpan . Slice ( Offset , ( int ) WordsAllocated ) ;
void AllocateWords ( uint count )
{
if ( count = = 0 )
{
SegmentIndex = 0 ;
Offset = 0 ;
}
else if ( Owner ? . Kind = = ObjectKind . ListOfStructs )
{
Owner . Allocate ( ) ;
SegmentIndex = Owner . SegmentIndex ;
Offset = Owner . Offset + OwnerSlot + 1 ;
}
else
{
SegmentIndex = Owner ? . SegmentIndex ? ? SegmentIndex ;
Allocator . Allocate ( count , SegmentIndex , out var slice , false ) ;
SegmentIndex = slice . SegmentIndex ;
Offset = slice . Offset ;
}
WordsAllocated = count ;
}
/// <summary>
/// Allocates storage for the underlying object. Does nothing if it is already allocated. From the point the object is allocated, its type cannot by changed
/// anymore (e.g. changing from struct to list, or modifying the struct's section sizes).
/// </summary>
public void Allocate ( )
{
if ( ! IsAllocated )
{
switch ( Kind )
{
case ObjectKind . ListOfBits :
AllocateWords ( checked ( ( uint ) ListElementCount + 63 u ) / 64 ) ;
break ;
case ObjectKind . ListOfBytes :
AllocateWords ( checked ( ( uint ) ListElementCount + 7 u ) / 8 ) ;
break ;
case ObjectKind . ListOfInts :
AllocateWords ( checked ( ( uint ) ListElementCount + 1 u ) / 2 ) ;
break ;
case ObjectKind . ListOfLongs :
case ObjectKind . ListOfPointers :
AllocateWords ( checked ( ( uint ) ListElementCount ) ) ;
break ;
case ObjectKind . ListOfShorts :
AllocateWords ( checked ( ( uint ) ListElementCount + 3 u ) / 4 ) ;
break ;
case ObjectKind . ListOfStructs :
AllocateWords ( checked ( 1 u + ( uint ) ListElementCount * ( uint ) ( StructDataCount + StructPtrCount ) ) ) ;
var tag = default ( WirePointer ) ;
tag . BeginStruct ( StructDataCount , StructPtrCount ) ;
tag . ListOfStructsElementCount = ListElementCount ;
SegmentSpan [ Offset ] = tag ;
break ;
case ObjectKind . Struct :
AllocateWords ( ( uint ) ( StructDataCount + StructPtrCount ) ) ;
break ;
default :
AllocateWords ( 0 ) ;
break ;
}
Owner ? . Link ( OwnerSlot , this ) ;
}
}
internal void EncodePointer ( int offset , SerializerState target , bool allowCopy )
{
if ( target = = null )
throw new ArgumentNullException ( nameof ( target ) ) ;
if ( ! target . IsAllocated )
throw new InvalidOperationException ( "Target must be allocated before a pointer can be built" ) ;
try
{
if ( SegmentSpan [ offset ] ! = 0 )
throw new InvalidOperationException ( "Won't replace an already allocated pointer to prevent memory leaks and security flaws" ) ;
}
catch ( IndexOutOfRangeException )
{
throw new ArgumentOutOfRangeException ( nameof ( offset ) ) ;
}
if ( target . Allocator ! = null & &
target . Allocator ! = Allocator )
{
if ( allowCopy )
{
Allocate ( ) ;
var targetCopy = new DynamicSerializerState ( MsgBuilder ) ;
Reserializing . DeepCopy ( target , targetCopy ) ;
target = targetCopy ;
}
else
{
throw new InvalidOperationException ( "target was allocated on a different segment allocator" ) ;
}
}
else
{
Allocate ( ) ;
}
WirePointer targetPtr = default ( WirePointer ) ;
switch ( target . Kind )
{
case ObjectKind . ListOfBits :
targetPtr . BeginList ( ListKind . ListOfBits , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfBytes :
targetPtr . BeginList ( ListKind . ListOfBytes , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfEmpty :
targetPtr . BeginList ( ListKind . ListOfEmpty , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfInts :
targetPtr . BeginList ( ListKind . ListOfInts , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfLongs :
targetPtr . BeginList ( ListKind . ListOfLongs , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfPointers :
targetPtr . BeginList ( ListKind . ListOfPointers , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfShorts :
targetPtr . BeginList ( ListKind . ListOfShorts , target . ListElementCount ) ;
break ;
case ObjectKind . ListOfStructs :
int wordCount = target . ListElementCount * ( target . StructDataCount + target . StructPtrCount ) ;
targetPtr . BeginList ( ListKind . ListOfStructs , wordCount ) ;
break ;
case ObjectKind . Capability :
targetPtr . SetCapability ( target . CapabilityIndex ) ;
SegmentSpan [ offset ] = targetPtr ;
return ;
case ObjectKind . Struct :
targetPtr . BeginStruct ( target . StructDataCount , target . StructPtrCount ) ;
if ( target . StructDataCount = = 0 & & target . StructPtrCount = = 0 )
{
targetPtr . Offset = - 1 ;
SegmentSpan [ offset ] = targetPtr ;
return ;
}
break ;
case ObjectKind . Nil :
SegmentSpan [ offset ] = 0 ;
return ;
default :
throw new NotImplementedException ( ) ;
}
if ( SegmentIndex ! = target . SegmentIndex )
{
WirePointer farPtr = default ;
if ( Allocator . Allocate ( 1 , target . SegmentIndex , out var landingPadSlice , true ) )
{
farPtr . SetFarPointer ( target . SegmentIndex , landingPadSlice . Offset , false ) ;
SegmentSpan [ offset ] = farPtr ;
targetPtr . Offset = target . Offset - ( landingPadSlice . Offset + 1 ) ;
FarSpan ( target . SegmentIndex ) [ landingPadSlice . Offset ] = targetPtr ;
}
else
{
Allocator . Allocate ( 2 , 0 , out landingPadSlice , false ) ;
farPtr . SetFarPointer ( landingPadSlice . SegmentIndex , landingPadSlice . Offset , true ) ;
SegmentSpan [ offset ] = farPtr ;
WirePointer farPtr2 = default ;
farPtr2 . SetFarPointer ( target . SegmentIndex , target . Offset , false ) ;
var farSpan = FarSpan ( landingPadSlice . SegmentIndex ) ;
farSpan [ landingPadSlice . Offset ] = farPtr2 ;
targetPtr . Offset = target . Offset ;
farSpan [ landingPadSlice . Offset + 1 ] = targetPtr ;
}
}
else
{
targetPtr . Offset = target . Offset - ( offset + 1 ) ;
SegmentSpan [ offset ] = targetPtr ;
}
}
internal Rpc . ConsumedCapability DecodeCapPointer ( int offset )
{
if ( offset < 0 )
throw new IndexOutOfRangeException ( nameof ( offset ) ) ;
if ( Caps = = null )
throw new InvalidOperationException ( "Capbility table not set" ) ;
WirePointer pointer = RawData [ offset ] ;
if ( pointer . Kind ! = PointerKind . Other )
{
throw new Rpc . RpcException (
"Expected a capability pointer, but got something different" ) ;
}
if ( pointer . CapabilityIndex > = Caps . Count )
{
throw new Rpc . RpcException (
"Capability index out of range" ) ;
}
return Caps [ ( int ) pointer . CapabilityIndex ] ;
}
/// <summary>
/// Links a sub-item (struct field or list element) of this state to another state. Usually, this operation is not necessary, since objects are constructed top-down.
/// However, there might be some advanced scenarios where you want to reference the same object twice (also interesting for designing amplification attacks).
/// The Cap'n Proto serialization intrinsically supports this, since messages are object graphs, not trees.
/// </summary>
/// <param name="slot">If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.</param>
/// <param name="target">state to be linked</param>
/// <param name="allowCopy">Whether to deep copy the target state if it belongs to a different message builder than this state.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> out of range</exception>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item></list>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// <item><description>This state and <paramref name="target"/> belong to different message builder, and<paramref name="allowCopy"/> is false</description></item></list>
/// </exception>
protected void Link ( int slot , SerializerState target , bool allowCopy = true )
{
if ( target = = null )
throw new ArgumentNullException ( nameof ( target ) ) ;
if ( slot < 0 )
throw new ArgumentOutOfRangeException ( nameof ( slot ) ) ;
if ( ! IsAllocated )
{
SegmentIndex = target . SegmentIndex ;
Allocate ( ) ;
}
if ( ! target . IsAllocated )
{
target . Allocate ( ) ;
}
switch ( Kind )
{
case ObjectKind . Struct :
if ( slot > = StructPtrCount )
throw new ArgumentOutOfRangeException ( nameof ( slot ) ) ;
EncodePointer ( Offset + StructDataCount + slot , target , allowCopy ) ;
break ;
case ObjectKind . ListOfPointers :
if ( slot > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( slot ) ) ;
EncodePointer ( Offset + slot , target , allowCopy ) ;
break ;
default :
throw new InvalidOperationException ( "This object cannot own pointers to sub-objects" ) ;
}
_linkedStates [ slot ] = target ;
}
/// <summary>
/// Links a sub-item (struct field or list element) of this state to a capability.
/// </summary>
/// <param name="slot">If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.</param>
/// <param name="capabilityIndex">capability index inside the capability table</param>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item></list>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception>
protected void LinkToCapability ( int slot , uint capabilityIndex )
{
var cstate = new SerializerState ( ) ;
cstate . SetCapability ( capabilityIndex ) ;
Link ( slot , cstate ) ;
}
static InvalidOperationException AlreadySet ( ) = > new InvalidOperationException ( "The object type was already set" ) ;
void VerifyNotYetAllocated ( )
{
if ( IsAllocated )
throw new InvalidOperationException ( "Not permitted since the state is already allocated" ) ;
}
/// <summary>
/// Determines the underlying object to be a struct.
/// </summary>
/// <param name="dataCount">Desired size of the struct's data section, in words</param>
/// <param name="ptrCount">Desired size of the struct's pointer section, in words</param>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
protected void SetStruct ( ushort dataCount , ushort ptrCount )
{
if ( Kind = = ObjectKind . Nil )
{
VerifyNotYetAllocated ( ) ;
Kind = ObjectKind . Struct ;
StructDataCount = dataCount ;
StructPtrCount = ptrCount ;
_linkedStates = new SerializerState [ ptrCount ] ;
}
else if ( Kind ! = ObjectKind . Struct | | StructDataCount ! = dataCount | | StructPtrCount ! = ptrCount )
{
throw AlreadySet ( ) ;
}
}
internal void SetCapability ( uint capabilityIndex )
{
if ( Kind = = ObjectKind . Nil )
{
VerifyNotYetAllocated ( ) ;
Kind = ObjectKind . Capability ;
CapabilityIndex = capabilityIndex ;
Allocate ( ) ;
}
else if ( Kind ! = ObjectKind . Capability | | CapabilityIndex ! = capabilityIndex )
{
throw AlreadySet ( ) ;
}
}
/// <summary>
/// Determines the underlying object to be a list of (primitive) values.
/// </summary>
/// <param name="bitsPerElement">Element size in bits, must be 0 (void), 1 (bool), 8, 16, 32, or 64</param>
/// <param name="totalCount">Desired element count</param>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bitsPerElement"/> outside allowed range,
/// <paramref name="totalCount"/> negative or exceeding 2^29-1</exception>
protected void SetListOfValues ( byte bitsPerElement , int totalCount )
{
ObjectKind kind ;
switch ( bitsPerElement )
{
case 0 :
kind = ObjectKind . ListOfEmpty ;
break ;
case 1 :
kind = ObjectKind . ListOfBits ;
break ;
case 8 :
kind = ObjectKind . ListOfBytes ;
break ;
case 16 :
kind = ObjectKind . ListOfShorts ;
break ;
case 32 :
kind = ObjectKind . ListOfInts ;
break ;
case 64 :
kind = ObjectKind . ListOfLongs ;
break ;
default :
throw new ArgumentOutOfRangeException ( nameof ( bitsPerElement ) ) ;
}
if ( Kind = = ObjectKind . Nil )
{
if ( totalCount < 0 )
throw new ArgumentOutOfRangeException ( nameof ( totalCount ) ) ;
VerifyNotYetAllocated ( ) ;
Kind = kind ;
ListElementCount = totalCount ;
Allocate ( ) ;
}
else if ( Kind ! = kind | | ListElementCount ! = totalCount )
{
throw AlreadySet ( ) ;
}
}
/// <summary>
/// Determines the underlying object to be a list of pointers.
/// </summary>
/// <param name="totalCount">Desired element count</param>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="totalCount"/> negative or exceeding 2^29-1</exception>
protected void SetListOfPointers ( int totalCount )
{
if ( Kind = = ObjectKind . Nil )
{
if ( totalCount < 0 )
throw new ArgumentOutOfRangeException ( nameof ( totalCount ) ) ;
VerifyNotYetAllocated ( ) ;
Kind = ObjectKind . ListOfPointers ;
ListElementCount = totalCount ;
_linkedStates = new SerializerState [ totalCount ] ;
Allocate ( ) ;
}
else if ( Kind ! = ObjectKind . ListOfPointers | | ListElementCount ! = totalCount )
{
throw AlreadySet ( ) ;
}
}
/// <summary>
/// Determines the underlying object to be a list of structs (fixed-width compound list).
/// </summary>
/// <param name="totalCount">Desired element count</param>
/// <param name="dataCount">Desired size of each struct's data section, in words</param>
/// <param name="ptrCount">Desired size of each struct's pointer section, in words</param>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="totalCount"/> negative, or total word count would exceed 2^29-1</exception>
protected void SetListOfStructs ( int totalCount , ushort dataCount , ushort ptrCount )
{
if ( Kind = = ObjectKind . Nil )
{
if ( totalCount < 0 )
throw new ArgumentOutOfRangeException ( nameof ( totalCount ) ) ;
VerifyNotYetAllocated ( ) ;
Kind = ObjectKind . ListOfStructs ;
ListElementCount = totalCount ;
StructDataCount = dataCount ;
StructPtrCount = ptrCount ;
_linkedStates = new SerializerState [ totalCount ] ;
Allocate ( ) ;
}
else if ( Kind ! = ObjectKind . ListOfStructs | |
ListElementCount ! = totalCount | |
StructDataCount ! = dataCount | |
StructPtrCount ! = ptrCount )
{
throw AlreadySet ( ) ;
}
}
/// <summary>
/// Determines the underlying object to be a list of bytes and encodes the given text.
/// </summary>
/// <param name="text">text to encode</param>
/// <exception cref="ArgumentNullException"><paramref name="text"/> is null</exception>
/// <exception cref="EncoderFallbackException">Trying to obtain the UTF-8 encoding might throw this exception.</exception>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
/// <exception cref="ArgumentOutOfRangeException">UTF-8 encoding exceeds 2^29-2 bytes</exception>
protected void WriteText ( string text )
{
byte [ ] srcBytes = Encoding . UTF8 . GetBytes ( text ) ;
SetListOfValues ( 8 , srcBytes . Length + 1 ) ;
var srcSpan = new ReadOnlySpan < byte > ( srcBytes ) ;
var dstSpan = ListGetBytes ( ) ;
dstSpan = dstSpan . Slice ( 0 , dstSpan . Length - 1 ) ;
srcSpan . CopyTo ( dstSpan ) ;
}
/// <summary>
/// Writes data (up to 64 bits) into the underlying struct's data section.
/// The write operation must be aligned to fit within a single word.
/// </summary>
/// <param name="bitOffset">Start bit relative to the struct's data section, little endian</param>
/// <param name="bitCount">Number of bits to write</param>
/// <param name="value">Data bits to write</param>
/// <exception cref="InvalidOperationException">The object was not determined to be a struct</exception>
/// <exception cref="ArgumentOutOfRangeException">The data slice specified by <paramref name="bitOffset"/> and <paramref name="bitCount"/>
/// is not completely within the struct's data section, misaligned, exceeds one word, or <paramref name="bitCount"/> is negative</exception>
public void StructWriteData ( ulong bitOffset , int bitCount , ulong value )
{
if ( Kind ! = ObjectKind . Struct )
throw new InvalidOperationException ( "This is not a struct" ) ;
Allocate ( ) ;
int index = checked ( ( int ) ( bitOffset / 64 ) ) ;
int relBitOffset = ( int ) ( bitOffset % 64 ) ;
var data = StructDataSection ;
if ( relBitOffset + bitCount > 64 )
throw new ArgumentOutOfRangeException ( nameof ( bitCount ) ) ;
ulong word = data [ index ] ;
if ( bitCount = = 64 )
{
data [ index ] = value ;
}
else
{
ulong mask = ~ ( ( ( 1 ul < < bitCount ) - 1 ) < < relBitOffset ) ;
data [ index ] = data [ index ] & mask | ( value < < relBitOffset ) ;
}
}
/// <summary>
/// Reads data (up to 64 bits) from the underlying struct's data section.
/// The write operation must be aligned to fit within a single word.
/// </summary>
/// <param name="bitOffset">Start bit relative to the struct's data section, little endian</param>
/// <param name="count">Number of bits to read</param>
/// <returns>Data bits which were read</returns>
/// <exception cref="InvalidOperationException">The object was not determined to be a struct</exception>
/// <exception cref="ArgumentOutOfRangeException">The data slice specified by <paramref name="bitOffset"/> and <paramref name="bitCount"/>
/// is not completely within the struct's data section, misaligned, exceeds one word, or <paramref name="bitCount"/> is negative</exception>
public ulong StructReadData ( ulong bitOffset , int count )
{
if ( Kind ! = ObjectKind . Struct )
throw new InvalidOperationException ( "This is not a struct" ) ;
if ( ! IsAllocated )
return 0 ;
int index = checked ( ( int ) ( bitOffset / 64 ) ) ;
int relBitOffset = ( int ) ( bitOffset % 64 ) ;
var data = StructDataSection ;
if ( index > = data . Length )
return 0 ; // Assume backwards-compatible change
if ( relBitOffset + count > 64 )
throw new ArgumentOutOfRangeException ( nameof ( count ) ) ;
ulong word = data [ index ] ;
if ( count = = 64 )
{
return word ;
}
else
{
ulong mask = ( 1 ul < < count ) - 1 ;
return ( word > > relBitOffset ) & mask ;
}
}
/// <summary>
/// Constructs a new object at a struct field or list element, or returns the serializer state for an existing object.
/// </summary>
/// <typeparam name="TS">Target state type</typeparam>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <returns>Bound serializer state instance</returns>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position was already built and is not compatible with the desired target serializer type.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public TS BuildPointer < TS > ( int index ) where TS : SerializerState , new ( )
{
if ( Kind ! = ObjectKind . Struct & & Kind ! = ObjectKind . ListOfPointers )
throw new InvalidOperationException ( "This is not a struct or list of pointers" ) ;
ref var state = ref _linkedStates [ index ] ;
if ( state = = null )
{
state = new TS ( ) ;
state . Bind ( this , index ) ;
}
return state . Rewrap < TS > ( ) ;
}
/// <summary>
/// Returns an existing serializer state for a struct field or list element, or null if no such object exists.
/// </summary>
/// <typeparam name="TS">Target state type</typeparam>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <returns>Bound serializer state instance</returns>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position is not compatible with the desired target serializer type.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public TS TryGetPointer < TS > ( int index ) where TS : SerializerState , new ( )
{
if ( Kind ! = ObjectKind . Struct & & Kind ! = ObjectKind . ListOfPointers )
throw new InvalidOperationException ( "This is not a struct or list of pointers" ) ;
var state = _linkedStates [ index ] ;
if ( state = = null ) return null ;
return state . Rewrap < TS > ( ) ;
}
/// <summary>
/// Convenience method for <code><![CDATA[BuildPointer<DynamicSerializerState>]]></code>
/// </summary>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <returns>Bound serializer state instance</returns>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position was already built and is not compatible with the desired target serializer type.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public DynamicSerializerState BuildPointer ( int index ) = > BuildPointer < DynamicSerializerState > ( index ) ;
/// <summary>
/// Convenience method for <code><![CDATA[TryGetPointer<SerializerState>]]></code>
/// </summary>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <returns>Bound serializer state instance</returns>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position is not compatible with the desired target serializer type.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public SerializerState TryGetPointer ( int index ) = > TryGetPointer < SerializerState > ( index ) ;
public string ReadText ( int index , string defaultText = null )
{
var b = BuildPointer ( index ) ;
if ( b . IsAllocated )
return b . ListReadAsText ( ) ;
else
return defaultText ;
}
/// <summary>
/// Encodes text into a struct field or list element.
/// </summary>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <param name="text">Text to encode</param>
/// <exception cref="ArgumentNullException"><paramref name="text"/>is null</exception>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position was already set.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void WriteText ( int index , string text )
{
BuildPointer ( index ) . WriteText ( text ) ;
}
/// <summary>
/// Encodes text into a struct field or list element, with fallback to a default text.
/// </summary>
/// <param name="index">If the underlying object is a struct: index into the struct's pointer section.
/// If the underlying object is a list of pointers: Element index</param>
/// <param name="text">Text to encode</param>
/// <param name="defaultText">Default text of <paramref name="text"/>> is null</param>
/// <exception cref="ArgumentNullException">Both <paramref name="text"/> and <paramref name="defaultText"/> are null</exception>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>The underlying object was not determined to be a struct or list of pointers.</description></item>
/// <item><description>Object at given position was already set.</description></item>
/// </list></exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void WriteText ( int index , string text , string defaultText )
{
BuildPointer ( index ) . WriteText ( text ? ? defaultText ) ;
}
/// <summary>
/// Returns a state which represents a fixed-width composite list element.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Bound serializer state</returns>
/// <exception cref="InvalidOperationException">Underlying object was not determined to be a fixed-width composite list.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/>is out of bounds.</exception>
public SerializerState ListBuildStruct ( int index )
{
if ( Kind ! = ObjectKind . ListOfStructs )
throw new InvalidOperationException ( "This is not a list of structs" ) ;
ref var state = ref _linkedStates [ index ] ;
if ( state = = null )
{
state = new SerializerState ( MsgBuilder ) ;
state . SetStruct ( StructDataCount , StructPtrCount ) ;
state . SegmentIndex = SegmentIndex ;
state . Offset = Offset + 1 + index * ( StructDataCount + StructPtrCount ) ;
}
return _linkedStates [ index ] ;
}
/// <summary>
/// Returns a state which represents a fixed-width composite list element.
/// </summary>
/// <typeparam name="TS">Target serializer state type</typeparam>
/// <param name="index"></param>
/// <returns>Bound serializer state</returns>
/// <exception cref="InvalidOperationException">Underlying object was not determined to be a fixed-width composite list.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/>is out of bounds.</exception>
public TS ListBuildStruct < TS > ( int index )
where TS : SerializerState , new ( )
{
if ( Kind ! = ObjectKind . ListOfStructs )
throw new InvalidOperationException ( "This is not a list of structs" ) ;
ref var state = ref _linkedStates [ index ] ;
if ( state = = null )
{
state = new TS ( ) ;
state . Bind ( MsgBuilder ) ;
state . SetStruct ( StructDataCount , StructPtrCount ) ;
state . SegmentIndex = SegmentIndex ;
int stride = StructDataCount + StructPtrCount ;
state . Offset = Offset + stride * index + 1 ;
}
return ( TS ) state ;
}
internal IReadOnlyList < TS > ListEnumerateStructs < TS > ( )
where TS : SerializerState , new ( )
{
if ( Kind ! = ObjectKind . ListOfStructs )
throw new InvalidOperationException ( "This is not a list of structs" ) ;
if ( ListElementCount < 0 )
throw new InvalidOperationException ( "Define element count first" ) ;
int minOffset = Offset + 1 ;
int maxOffset = minOffset + ListElementCount ;
for ( int offset = minOffset ; offset < maxOffset ; offset + + )
{
ref var state = ref _linkedStates [ offset - minOffset ] ;
if ( state = = null )
{
state = new TS ( ) ;
state . Bind ( MsgBuilder ) ;
state . SetStruct ( StructDataCount , StructPtrCount ) ;
state . SegmentIndex = SegmentIndex ;
state . Offset = offset ;
}
}
return _linkedStates . LazyListSelect ( ts = > ( TS ) ts ) ;
}
/// <summary>
/// Sets an element of a list of bits.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bits.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , bool value , bool defaultValue = false )
{
if ( Kind ! = ObjectKind . ListOfBits )
throw new InvalidOperationException ( "This is not a list of bits" ) ;
if ( index < 0 | | index > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
bool bit = value ! = defaultValue ;
int wordIndex = index / 64 ;
int relBitOffset = index % 64 ;
if ( bit )
SegmentSpan [ Offset + wordIndex ] | = ( 1 ul < < relBitOffset ) ;
else
SegmentSpan [ Offset + wordIndex ] & = ~ ( 1 ul < < relBitOffset ) ;
}
/// <summary>
/// Sets the list-of-bits' content.
/// </summary>
/// <param name="values">Content to set</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bits.</exception>
/// <exception cref="ArgumentNullException"><paramref name="values"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">The given element count does not match the underlying list's element count.</exception>
public void ListWriteValues ( IReadOnlyList < bool > values , bool defaultValue = false )
{
if ( Kind ! = ObjectKind . ListOfBits )
throw new InvalidOperationException ( "This is not a list of bits" ) ;
if ( values = = null )
throw new ArgumentNullException ( nameof ( values ) ) ;
if ( values . Count ! = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( values ) ) ;
int i , w = Offset ;
ulong v ;
for ( i = 0 ; i < ListElementCount - 63 ; i + = 64 )
{
v = 0 ;
for ( int j = 63 ; j > = 0 ; j - - )
{
v < < = 1 ;
if ( values [ i + j ] ) v | = 1 ;
}
if ( defaultValue ) v = ~ v ;
SegmentSpan [ w + + ] = v ;
}
if ( i < ListElementCount )
{
v = 0 ;
for ( int k = ListElementCount - 1 ; k > = i ; k - - )
{
v < < = 1 ;
if ( values [ k ] ) v | = 1 ;
}
if ( defaultValue ) v = ~ v ;
SegmentSpan [ w ] = v ;
}
}
/// <summary>
/// Sets an element of a list of bytes.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , byte value , byte defaultValue = 0 )
{
if ( Kind ! = ObjectKind . ListOfBytes )
throw new InvalidOperationException ( "This is not a list of bytes" ) ;
if ( index < 0 | | index > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
byte x = ( byte ) ( value ^ defaultValue ) ;
MemoryMarshal . Cast < ulong , byte > ( SegmentSpan . Slice ( Offset ) ) [ index ] = x ;
}
/// <summary>
/// Returns the content of a list of bytes.
/// </summary>
/// <returns>The list bytes</returns>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
Span < byte > ListGetBytes ( )
{
if ( Kind ! = ObjectKind . ListOfBytes )
throw new InvalidOperationException ( "This is not a list of bytes" ) ;
return MemoryMarshal . Cast < ulong , byte > ( SegmentSpan . Slice ( Offset ) ) . Slice ( 0 , ListElementCount ) ;
}
/// <summary>
/// Decodes a list of bytes as text.
/// </summary>
/// <returns>The decoded text.</returns>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
/// <exception cref="DecoderFallbackException">Might theoretically be thrown during decoding.</exception>
public string ListReadAsText ( )
{
var bytes = ListGetBytes ( ) ;
if ( bytes . Length = = 0 ) return string . Empty ;
2019-07-04 22:05:37 +02:00
#if NETSTANDARD2_0
2019-06-22 18:43:30 -04:00
return Encoding . UTF8 . GetString ( bytes . Slice ( 0 , bytes . Length - 1 ) . ToArray ( ) ) ;
2019-07-04 22:05:37 +02:00
#else
return Encoding . UTF8 . GetString ( bytes . Slice ( 0 , bytes . Length - 1 ) ) ;
#endif
2019-06-12 21:56:55 +02:00
}
/// <summary>
/// Sets an element of a list of (signed) bytes.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of bytes.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , sbyte value , sbyte defaultValue = 0 )
{
ListWriteValue ( index , unchecked ( ( byte ) value ) , unchecked ( ( byte ) defaultValue ) ) ;
}
/// <summary>
/// Sets an element of a list of 16 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 16 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , ushort value , ushort defaultValue = 0 )
{
if ( Kind ! = ObjectKind . ListOfShorts )
throw new InvalidOperationException ( "This is not a list of 16-bit values" ) ;
if ( index < 0 | | index > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
Allocate ( ) ;
ushort x = ( ushort ) ( value ^ defaultValue ) ;
MemoryMarshal . Cast < ulong , ushort > ( SegmentSpan . Slice ( Offset ) ) [ index ] = x ;
}
/// <summary>
/// Sets an element of a list of 16 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 16 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , short value , short defaultValue = 0 )
{
ListWriteValue ( index , unchecked ( ( ushort ) value ) , unchecked ( ( ushort ) defaultValue ) ) ;
}
/// <summary>
/// Sets an element of a list of 32 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , uint value , uint defaultValue = 0 )
{
if ( Kind ! = ObjectKind . ListOfInts )
throw new InvalidOperationException ( "This is not a list of 32-bit values" ) ;
if ( index < 0 | | index > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
Allocate ( ) ;
uint x = value ^ defaultValue ;
MemoryMarshal . Cast < ulong , uint > ( SegmentSpan . Slice ( Offset ) ) [ index ] = x ;
}
/// <summary>
/// Sets an element of a list of 32 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , int value , int defaultValue = 0 )
{
ListWriteValue ( index , unchecked ( ( uint ) value ) , unchecked ( ( uint ) defaultValue ) ) ;
}
/// <summary>
/// Sets an element of a list of 32 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 32 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , float value , float defaultValue = 0 )
{
2019-07-04 22:05:37 +02:00
int rcastValue = value . ReplacementSingleToInt32Bits ( ) ;
int rcastDefaultValue = defaultValue . ReplacementSingleToInt32Bits ( ) ;
2019-06-12 21:56:55 +02:00
ListWriteValue ( index , rcastValue , rcastDefaultValue ) ;
}
/// <summary>
/// Sets an element of a list of 64 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , ulong value , ulong defaultValue = 0 )
{
if ( Kind ! = ObjectKind . ListOfLongs )
throw new InvalidOperationException ( "This is not a list of 64-bit values" ) ;
if ( index < 0 | | index > = ListElementCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
Allocate ( ) ;
ulong x = value ^ defaultValue ;
SegmentSpan . Slice ( Offset ) [ index ] = x ;
}
/// <summary>
/// Sets an element of a list of 64 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , long value , long defaultValue = 0 )
{
ListWriteValue ( index , unchecked ( ( ulong ) value ) , unchecked ( ( ulong ) defaultValue ) ) ;
}
/// <summary>
/// Sets an element of a list of 64 bit words.
/// </summary>
/// <param name="index">Element index</param>
/// <param name="value">Element value</param>
/// <param name="defaultValue">Element default value (serialized value will be XORed with the default value)</param>
/// <exception cref="InvalidOperationException">The underlying object was not set to a list of 64 bit words.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of bounds.</exception>
public void ListWriteValue ( int index , double value , double defaultValue = 0 )
{
long rcastValue = BitConverter . DoubleToInt64Bits ( value ) ;
long rcastDefaultValue = BitConverter . DoubleToInt64Bits ( defaultValue ) ;
ListWriteValue ( index , rcastValue , rcastDefaultValue ) ;
}
/// <summary>
/// Adds an entry to the capability table if the provided capability does not yet exist.
/// </summary>
/// <param name="capability">The low-level capability object to provide.</param>
/// <returns>Index of the given capability in the capability table</returns>
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability ( Rpc . ConsumedCapability capability )
{
if ( Caps = = null )
throw new InvalidOperationException ( "Underlying MessageBuilder was not enabled to support capabilities" ) ;
int index = Caps . IndexOf ( capability ) ;
if ( index < 0 )
{
index = Caps . Count ;
Caps . Add ( capability ) ;
capability ? . AddRef ( ) ;
}
return ( uint ) index ;
}
/// <summary>
/// Adds an entry to the capability table if the provided capability does not yet exist.
/// </summary>
/// <param name="capability">The capability to provide, in terms of its skeleton.</param>
/// <returns>Index of the given capability in the capability table</returns>
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability ( Rpc . Skeleton capability )
{
return ProvideCapability ( Rpc . LocalCapability . Create ( capability ) ) ;
}
/// <summary>
/// Adds an entry to the capability table if the provided capability does not yet exist.
/// </summary>
/// <param name="capability">The capability, in one of the following forms:<list type="bullet">
/// <item><description>Low-level capability object (<code>Rpc.ConsumedCapability</code>)</description></item>
/// <item><description>Proxy object (<code>Rpc.Proxy</code>)</description></item>
/// <item><description>Skeleton object (<code>Rpc.Skeleton</code>)</description></item>
/// <item><description>Capability interface implementation</description></item>
/// </list></param>
/// <returns>Index of the given capability in the capability table</returns>
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability ( object obj )
{
if ( obj = = null )
return ProvideCapability ( default ( Rpc . ConsumedCapability ) ) ;
else if ( obj is Rpc . Proxy proxy )
return ProvideCapability ( proxy . ConsumedCap ) ;
else if ( obj is Rpc . ConsumedCapability consumedCapability )
return ProvideCapability ( consumedCapability ) ;
else if ( obj is Rpc . Skeleton providedCapability )
return ProvideCapability ( providedCapability ) ;
else
return ProvideCapability ( Rpc . Skeleton . GetOrCreateSkeleton ( obj , false ) ) ;
}
/// <summary>
/// Links a sub-item (struct field or list element) of this state to another object.
/// In contrast to <see cref="Link(int, SerializerState, bool)"/>, this method also accepts deserializer states, domain objects, capabilites, and lists thereof.
/// If necessary, it will perform a deep copy.
/// </summary>
/// <param name="slot">If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.</param>
/// <param name="obj">Object to be linked. Must be one of the following:<list type="bullet">
/// <item><description>Another <see cref="SerializerState"/></description></item>
/// <item><description>A <see cref="DeserializerState"/> (will always deep copy)</description></item>
/// <item><description>An object implementing <see cref="ICapnpSerializable"/></description></item>
/// <item><description>A low-level capability object (<see cref="Rpc.ConsumedCapability"/>)</description></item>
/// <item><description>A proxy object (<see cref="Rpc.Proxy"/>)</description></item>
/// <item><description>A skeleton object (<see cref="Rpc.Skeleton"/>)</description></item>
/// <item><description>A capability interface implementation</description></item>
/// <item><description>A <see cref="IReadOnlyList{T}"/> of one of the things listed here.</description></item>
/// </list></param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item></list>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception>
public void LinkObject < T > ( int slot , T obj )
{
switch ( obj )
{
case SerializerState s :
Link ( slot , s ) ;
break ;
case DeserializerState d :
Reserializing . DeepCopy ( d , BuildPointer ( slot ) ) ;
break ;
case ICapnpSerializable serializable :
serializable . Serialize ( BuildPointer ( slot ) ) ;
break ;
case IReadOnlyList < object > list :
{
var builder = BuildPointer ( slot ) ;
builder . SetListOfPointers ( list . Count ) ;
int i = 0 ;
foreach ( var item in list )
{
builder . LinkObject ( i + + , item ) ;
}
}
break ;
default :
if ( Rpc . CapabilityReflection . IsValidCapabilityInterface ( typeof ( T ) ) )
{
LinkToCapability ( slot , ProvideCapability ( obj ) ) ;
}
break ;
}
}
internal Rpc . ConsumedCapability StructReadRawCap ( int index )
{
if ( Kind ! = ObjectKind . Struct & & Kind ! = ObjectKind . Nil )
throw new InvalidOperationException ( "Allowed on structs only" ) ;
if ( index > = StructPtrCount )
return null ;
return DecodeCapPointer ( index + StructDataCount ) ;
}
/// <summary>
/// Reads a struct field as capability and returns a proxy to that capability.
/// </summary>
/// <typeparam name="T">Desired capability interface</typeparam>
/// <param name="slot">Index into this struct's pointer table.</param>
/// <returns>The proxy instance</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
/// <exception cref="ArgumentException">The desired interface does not qualify as capability interface (<see cref="Rpc.ProxyAttribute")/></exception>
/// <exception cref="InvalidOperationException">This state does not represent a struct.</exception>
public T ReadCap < T > ( int slot ) where T : class
{
var cap = StructReadRawCap ( slot ) ;
return Rpc . CapabilityReflection . CreateProxy < T > ( cap ) as T ;
}
/// <summary>
/// Reads a struct field as capability and returns a bare (generic) proxy to that capability.
/// </summary>
/// <param name="slot">Index into this struct's pointer table.</param>
/// <returns>The proxy instance</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> is out of range.</exception>
/// <exception cref="InvalidOperationException">This state does not represent a struct.</exception>
public Rpc . BareProxy ReadCap ( int slot )
{
var cap = StructReadRawCap ( slot ) ;
return new Rpc . BareProxy ( cap ) ;
}
}
}