1392 lines
62 KiB
C#
Raw Normal View History

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 + 63u) / 64);
break;
case ObjectKind.ListOfBytes:
AllocateWords(checked((uint)ListElementCount + 7u) / 8);
break;
case ObjectKind.ListOfInts:
AllocateWords(checked((uint)ListElementCount + 1u) / 2);
break;
case ObjectKind.ListOfLongs:
case ObjectKind.ListOfPointers:
AllocateWords(checked((uint)ListElementCount));
break;
case ObjectKind.ListOfShorts:
AllocateWords(checked((uint)ListElementCount + 3u) / 4);
break;
case ObjectKind.ListOfStructs:
AllocateWords(checked(1u + (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>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item>
/// <item><description>This state and <paramref name="target"/> belong to different message builder, and<paramref name="allowCopy"/> is false</description></item>
/// </list>
2019-06-12 21:56:55 +02:00
/// </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>
2019-06-12 21:56:55 +02:00
/// <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 = ~(((1ul << 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="count"/>
/// is not completely within the struct's data section, misaligned, exceeds one word, or <paramref name="count"/> is negative</exception>
2019-06-12 21:56:55 +02:00
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 = (1ul << 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);
/// <summary>
/// Reads text from 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="defaultText">String to return in case of null</param>
/// <returns>The decoded text</returns>
2019-06-12 21:56:55 +02:00
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] |= (1ul << relBitOffset);
else
SegmentSpan[Offset + wordIndex] &= ~(1ul << 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;
#if NETSTANDARD2_0
2019-06-22 18:43:30 -04:00
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1).ToArray());
#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)
{
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="obj">The capability, in one of the following forms:<list type="bullet">
2019-06-12 21:56:55 +02:00
/// <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>
2019-06-12 21:56:55 +02:00
/// <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>
2019-06-12 21:56:55 +02:00
/// <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);
}
}
}