using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Capnp
{
///
/// 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.
///
public class SerializerState : IStructSerializer
{
///
/// Constructs a SerializerState instance for use in RPC context.
/// This particularly means that the capability table will be initialized.
///
/// Type of state (must inherit from SerializerState)
/// root object state
public static T CreateForRpc() 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 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;
///
/// Constructs an unbound serializer state.
///
public SerializerState()
{
Offset = -1;
ListElementCount = -1;
}
///
/// Constructs a serializer state which is bound to a particular message builder.
///
/// message builder to bind
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;
}
///
/// 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.
///
/// target serializer state type
/// serializer state instance
/// 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)
public TS Rewrap() 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;
}
///
/// 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.
///
public bool IsAllocated => Offset >= 0;
Span SegmentSpan => IsAllocated ? Allocator.Segments[(int)SegmentIndex].Span : Span.Empty;
Span FarSpan(uint index) => Allocator.Segments[(int)index].Span;
///
/// Given this state describes a struct and is allocated, returns the struct's data section.
///
public Span StructDataSection => SegmentSpan.Slice(Offset, StructDataCount);
///
/// Returns the allocated memory slice (given this state already is allocated). Note that this definition is somewhat
/// non-symmetric to DeserializerState.RawData. Never mind: You should not use it directly, anyway.
///
public Span 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;
}
///
/// 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).
///
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];
}
///
/// 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.
///
/// If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.
/// state to be linked
/// Whether to deep copy the target state if it belongs to a different message builder than this state.
/// is null
/// out of range
///
/// This state does neither describe a struct, nor a list of pointers
/// Another state is already linked to the specified position (sorry, no overwrite allowed)
/// This state and belong to different message builder, and is false
///
///
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;
}
///
/// Links a sub-item (struct field or list element) of this state to a capability.
///
/// If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.
/// capability index inside the capability table
///
/// This state does neither describe a struct, nor a list of pointers
/// Another state is already linked to the specified position (sorry, no overwrite allowed)
///
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");
}
///
/// Determines the underlying object to be a struct.
///
/// Desired size of the struct's data section, in words
/// Desired size of the struct's pointer section, in words
/// The object type was already set to something different
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();
}
}
///
/// Determines the underlying object to be a list of (primitive) values.
///
/// Element size in bits, must be 0 (void), 1 (bool), 8, 16, 32, or 64
/// Desired element count
/// The object type was already set to something different
/// outside allowed range,
/// negative or exceeding 2^29-1
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();
}
}
///
/// Determines the underlying object to be a list of pointers.
///
/// Desired element count
/// The object type was already set to something different
/// negative or exceeding 2^29-1
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();
}
}
///
/// Determines the underlying object to be a list of structs (fixed-width compound list).
///
/// Desired element count
/// Desired size of each struct's data section, in words
/// Desired size of each struct's pointer section, in words
/// The object type was already set to something different
/// negative, or total word count would exceed 2^29-1
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();
}
}
///
/// Determines the underlying object to be a list of bytes and encodes the given text.
///
/// text to encode
/// is null
/// Trying to obtain the UTF-8 encoding might throw this exception.
/// The object type was already set to something different
/// UTF-8 encoding exceeds 2^29-2 bytes
protected void WriteText(string text)
{
byte[] srcBytes = Encoding.UTF8.GetBytes(text);
SetListOfValues(8, srcBytes.Length + 1);
var srcSpan = new ReadOnlySpan(srcBytes);
var dstSpan = ListGetBytes();
dstSpan = dstSpan.Slice(0, dstSpan.Length - 1);
srcSpan.CopyTo(dstSpan);
}
///
/// 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.
///
/// Start bit relative to the struct's data section, little endian
/// Number of bits to write
/// Data bits to write
/// The object was not determined to be a struct
/// The data slice specified by and
/// is not completely within the struct's data section, misaligned, exceeds one word, or is negative
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);
}
}
///
/// 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.
///
/// Start bit relative to the struct's data section, little endian
/// Number of bits to read
/// Data bits which were read
/// The object was not determined to be a struct
/// The data slice specified by and
/// is not completely within the struct's data section, misaligned, exceeds one word, or is negative
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;
}
}
///
/// Constructs a new object at a struct field or list element, or returns the serializer state for an existing object.
///
/// Target state type
/// 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
/// Bound serializer state instance
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position was already built and is not compatible with the desired target serializer type.
///
/// is out of bounds.
public TS BuildPointer(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();
}
///
/// Returns an existing serializer state for a struct field or list element, or null if no such object exists.
///
/// Target state type
/// 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
/// Bound serializer state instance
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position is not compatible with the desired target serializer type.
///
/// is out of bounds.
public TS TryGetPointer(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();
}
///
/// Convenience method for ]]>
///
/// 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
/// Bound serializer state instance
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position was already built and is not compatible with the desired target serializer type.
///
/// is out of bounds.
public DynamicSerializerState BuildPointer(int index) => BuildPointer(index);
///
/// Convenience method for ]]>
///
/// 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
/// Bound serializer state instance
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position is not compatible with the desired target serializer type.
///
/// is out of bounds.
public SerializerState TryGetPointer(int index) => TryGetPointer(index);
///
/// Reads text from a struct field or list element.
///
/// 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
/// String to return in case of null
/// The decoded text
public string ReadText(int index, string defaultText = null)
{
var b = BuildPointer(index);
if (b.IsAllocated)
return b.ListReadAsText();
else
return defaultText;
}
///
/// Encodes text into a struct field or list element.
///
/// 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
/// Text to encode
/// is null
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position was already set.
///
/// is out of bounds.
public void WriteText(int index, string text)
{
BuildPointer(index).WriteText(text);
}
///
/// Encodes text into a struct field or list element, with fallback to a default text.
///
/// 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
/// Text to encode
/// Default text of > is null
/// Both and are null
///
/// The underlying object was not determined to be a struct or list of pointers.
/// Object at given position was already set.
///
/// is out of bounds.
public void WriteText(int index, string text, string defaultText)
{
BuildPointer(index).WriteText(text ?? defaultText);
}
///
/// Returns a state which represents a fixed-width composite list element.
///
/// Element index
/// Bound serializer state
/// Underlying object was not determined to be a fixed-width composite list.
/// is out of bounds.
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];
}
///
/// Returns a state which represents a fixed-width composite list element.
///
/// Target serializer state type
///
/// Bound serializer state
/// Underlying object was not determined to be a fixed-width composite list.
/// is out of bounds.
public TS ListBuildStruct(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 ListEnumerateStructs()
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);
}
///
/// Sets an element of a list of bits.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of bits.
/// is out of bounds.
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);
}
///
/// Sets the list-of-bits' content.
///
/// Content to set
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of bits.
/// is null.
/// The given element count does not match the underlying list's element count.
public void ListWriteValues(IReadOnlyList 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;
}
}
///
/// Sets an element of a list of bytes.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of bytes.
/// is out of bounds.
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(SegmentSpan.Slice(Offset))[index] = x;
}
///
/// Returns the content of a list of bytes.
///
/// The list bytes
/// The underlying object was not set to a list of bytes.
Span ListGetBytes()
{
if (Kind != ObjectKind.ListOfBytes)
throw new InvalidOperationException("This is not a list of bytes");
return MemoryMarshal.Cast(SegmentSpan.Slice(Offset)).Slice(0, ListElementCount);
}
///
/// Decodes a list of bytes as text.
///
/// The decoded text.
/// The underlying object was not set to a list of bytes.
/// Might theoretically be thrown during decoding.
public string ListReadAsText()
{
var bytes = ListGetBytes();
if (bytes.Length == 0) return string.Empty;
#if NETSTANDARD2_0
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1).ToArray());
#else
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1));
#endif
}
///
/// Sets an element of a list of (signed) bytes.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of bytes.
/// is out of bounds.
public void ListWriteValue(int index, sbyte value, sbyte defaultValue = 0)
{
ListWriteValue(index, unchecked((byte)value), unchecked((byte)defaultValue));
}
///
/// Sets an element of a list of 16 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 16 bit words.
/// is out of bounds.
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(SegmentSpan.Slice(Offset))[index] = x;
}
///
/// Sets an element of a list of 16 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 16 bit words.
/// is out of bounds.
public void ListWriteValue(int index, short value, short defaultValue = 0)
{
ListWriteValue(index, unchecked((ushort)value), unchecked((ushort)defaultValue));
}
///
/// Sets an element of a list of 32 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 32 bit words.
/// is out of bounds.
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(SegmentSpan.Slice(Offset))[index] = x;
}
///
/// Sets an element of a list of 32 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 32 bit words.
/// is out of bounds.
public void ListWriteValue(int index, int value, int defaultValue = 0)
{
ListWriteValue(index, unchecked((uint)value), unchecked((uint)defaultValue));
}
///
/// Sets an element of a list of 32 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 32 bit words.
/// is out of bounds.
public void ListWriteValue(int index, float value, float defaultValue = 0)
{
int rcastValue = value.ReplacementSingleToInt32Bits();
int rcastDefaultValue = defaultValue.ReplacementSingleToInt32Bits();
ListWriteValue(index, rcastValue, rcastDefaultValue);
}
///
/// Sets an element of a list of 64 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 64 bit words.
/// is out of bounds.
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;
}
///
/// Sets an element of a list of 64 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 64 bit words.
/// is out of bounds.
public void ListWriteValue(int index, long value, long defaultValue = 0)
{
ListWriteValue(index, unchecked((ulong)value), unchecked((ulong)defaultValue));
}
///
/// Sets an element of a list of 64 bit words.
///
/// Element index
/// Element value
/// Element default value (serialized value will be XORed with the default value)
/// The underlying object was not set to a list of 64 bit words.
/// is out of bounds.
public void ListWriteValue(int index, double value, double defaultValue = 0)
{
long rcastValue = BitConverter.DoubleToInt64Bits(value);
long rcastDefaultValue = BitConverter.DoubleToInt64Bits(defaultValue);
ListWriteValue(index, rcastValue, rcastDefaultValue);
}
///
/// Adds an entry to the capability table if the provided capability does not yet exist.
///
/// The low-level capability object to provide.
/// Index of the given capability in the capability table
/// The underlying message builder was not configured for capability table support.
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;
}
///
/// Adds an entry to the capability table if the provided capability does not yet exist.
///
/// The capability to provide, in terms of its skeleton.
/// Index of the given capability in the capability table
/// The underlying message builder was not configured for capability table support.
public uint ProvideCapability(Rpc.Skeleton capability)
{
return ProvideCapability(Rpc.LocalCapability.Create(capability));
}
///
/// Adds an entry to the capability table if the provided capability does not yet exist.
///
/// The capability, in one of the following forms:
/// Low-level capability object (Rpc.ConsumedCapability)
/// Proxy object (Rpc.Proxy)
/// Skeleton object (Rpc.Skeleton)
/// Capability interface implementation
///
/// Index of the given capability in the capability table
/// The underlying message builder was not configured for capability table support.
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));
}
///
/// Links a sub-item (struct field or list element) of this state to another object.
/// In contrast to , this method also accepts deserializer states, domain objects, capabilites, and lists thereof.
/// If necessary, it will perform a deep copy.
///
/// If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.
/// Object to be linked. Must be one of the following:
/// Another
/// A (will always deep copy)
/// An object implementing
/// A low-level capability object ()
/// A proxy object ()
/// A skeleton object ()
/// A capability interface implementation
/// A of one of the things listed here.
///
/// is out of range.
///
/// This state does neither describe a struct, nor a list of pointers
/// Another state is already linked to the specified position (sorry, no overwrite allowed)
///
public void LinkObject(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