mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 23:01:44 +01:00
1424 lines
64 KiB
C#
1424 lines
64 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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, IDisposable
|
|
{
|
|
/// <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 uint CapabilityIndex { get; set; }
|
|
|
|
SerializerState[]? _linkedStates;
|
|
bool _disposed;
|
|
|
|
/// <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;
|
|
}
|
|
|
|
internal void InheritFrom(SerializerState other)
|
|
{
|
|
SegmentIndex = other.SegmentIndex;
|
|
Offset = other.Offset;
|
|
WordsAllocated = other.WordsAllocated;
|
|
ListElementCount = other.ListElementCount;
|
|
StructDataCount = other.StructDataCount;
|
|
StructPtrCount = other.StructPtrCount;
|
|
Kind = other.Kind;
|
|
CapabilityIndex = other.CapabilityIndex;
|
|
_linkedStates = other._linkedStates;
|
|
}
|
|
|
|
/// <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();
|
|
|
|
if (Kind != ObjectKind.Nil)
|
|
{
|
|
static 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;
|
|
}
|
|
|
|
ts.InheritFrom(this);
|
|
}
|
|
|
|
if (Owner != null)
|
|
ts.Bind(Owner, OwnerSlot);
|
|
else
|
|
ts.Bind(MsgBuilder ?? throw Unbound());
|
|
|
|
return ts;
|
|
}
|
|
|
|
/// <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 != null ? 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 => IsAllocated ? SegmentSpan.Slice(Offset, (int)WordsAllocated) : Span<ulong>.Empty;
|
|
|
|
/// <summary>
|
|
/// The kind of object this state currently represents.
|
|
/// </summary>
|
|
public ObjectKind Kind { get; internal set; }
|
|
|
|
void AllocateWords(uint count)
|
|
{
|
|
if (count == 0)
|
|
{
|
|
SegmentIndex = 0;
|
|
Offset = 0;
|
|
}
|
|
else
|
|
{
|
|
if (Allocator == null)
|
|
throw Unbound();
|
|
|
|
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 be 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 Rpc.ConsumedCapability DecodeCapPointer(int offset)
|
|
{
|
|
if (Caps == null)
|
|
throw new InvalidOperationException("Capbility table not set");
|
|
|
|
if (!IsAllocated)
|
|
{
|
|
return Rpc.NullCapability.Instance;
|
|
}
|
|
|
|
WirePointer pointer = RawData[offset];
|
|
|
|
if (pointer.IsNull)
|
|
{
|
|
return Rpc.NullCapability.Instance;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
void EncodePointer(int offset, SerializerState target, bool allowCopy)
|
|
{
|
|
if (SegmentSpan[offset] != 0)
|
|
{
|
|
throw new InvalidOperationException("Won't replace an already allocated pointer to prevent memory leaks and security flaws");
|
|
}
|
|
|
|
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;
|
|
|
|
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 = 0;
|
|
farSpan[landingPadSlice.Offset + 1] = targetPtr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
targetPtr.Offset = target.Offset - (offset + 1);
|
|
SegmentSpan[offset] = targetPtr;
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
/// </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>
|
|
/// <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");
|
|
static InvalidOperationException Unbound() => new InvalidOperationException("This state is not bound to a MessageBuilder");
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the underyling object to be a capability.
|
|
/// </summary>
|
|
/// <param name="capabilityIndex">Capability table index, or null to encode a null pointer</param>
|
|
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
|
|
protected void SetCapability(uint? capabilityIndex)
|
|
{
|
|
if (capabilityIndex.HasValue)
|
|
{
|
|
if (Kind == ObjectKind.Nil)
|
|
{
|
|
VerifyNotYetAllocated();
|
|
|
|
Kind = ObjectKind.Capability;
|
|
CapabilityIndex = capabilityIndex.Value;
|
|
Allocate();
|
|
}
|
|
else if (Kind != ObjectKind.Capability || CapabilityIndex != capabilityIndex)
|
|
{
|
|
throw AlreadySet();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Kind != ObjectKind.Nil)
|
|
{
|
|
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)
|
|
{
|
|
var kind = bitsPerElement switch
|
|
{
|
|
0 => ObjectKind.ListOfEmpty,
|
|
1 => ObjectKind.ListOfBits,
|
|
8 => ObjectKind.ListOfBytes,
|
|
16 => ObjectKind.ListOfShorts,
|
|
32 => ObjectKind.ListOfInts,
|
|
64 => ObjectKind.ListOfLongs,
|
|
_ => 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)
|
|
{
|
|
if (text == null)
|
|
{
|
|
VerifyNotYetAllocated();
|
|
}
|
|
else
|
|
{
|
|
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));
|
|
|
|
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>
|
|
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 (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
|
|
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");
|
|
|
|
if (index < 0 || index >= _linkedStates!.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
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>
|
|
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 if <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");
|
|
|
|
if (index < 0 || index >= _linkedStates!.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
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");
|
|
|
|
if (index < 0 || index >= _linkedStates!.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
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>
|
|
public 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
|
|
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1).ToArray());
|
|
#else
|
|
return Encoding.UTF8.GetString(bytes.Slice(0, bytes.Length - 1));
|
|
#endif
|
|
}
|
|
|
|
/// <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();
|
|
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, null if capability is null</returns>
|
|
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
|
|
public uint? ProvideCapability(Rpc.ConsumedCapability capability)
|
|
{
|
|
if (capability == null || capability == Rpc.NullCapability.Instance)
|
|
return null;
|
|
|
|
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(capability.AsCapability());
|
|
}
|
|
|
|
/// <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">
|
|
/// <item><description>Low-level capability (<see cref="Rpc.ConsumedCapability"/>)</description></item>
|
|
/// <item><description>Proxy object (<see cref="Rpc.Proxy"/>). Note that the provision has "move semantics": SerializerState
|
|
/// takes ownership, so the Proxy object will be disposed.</description></item>
|
|
/// <item><description><see cref="Rpc.Skeleton"/> instance</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)
|
|
{
|
|
switch (obj)
|
|
{
|
|
case null:
|
|
return null;
|
|
case Rpc.Proxy proxy: using (proxy)
|
|
return ProvideCapability(proxy.ConsumedCap);
|
|
case Rpc.ConsumedCapability consumedCapability:
|
|
return ProvideCapability(consumedCapability);
|
|
case Rpc.Skeleton providedCapability:
|
|
return ProvideCapability(providedCapability);
|
|
default:
|
|
return ProvideCapability(Rpc.CapabilityReflection.CreateSkeletonInternal(obj));
|
|
}
|
|
}
|
|
|
|
/// <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>Object neither describes a struct, nor a list of pointers, nor a capability</description></item>
|
|
/// <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:
|
|
try
|
|
{
|
|
LinkToCapability(slot, ProvideCapability(obj));
|
|
}
|
|
catch (Exception exception) when (
|
|
exception is Rpc.InvalidCapabilityInterfaceException ||
|
|
exception is InvalidOperationException)
|
|
{
|
|
throw new InvalidOperationException("Object neither describes a struct, nor a list of pointers, nor a capability", exception);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal Rpc.ConsumedCapability StructReadRawCap(int index)
|
|
{
|
|
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
|
|
throw new InvalidOperationException("Allowed on structs only");
|
|
|
|
if (index < 0 || index >= StructPtrCount)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the capability table
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (Caps != null && !_disposed)
|
|
{
|
|
foreach (var cap in Caps)
|
|
{
|
|
cap.Release();
|
|
}
|
|
|
|
Caps.Clear();
|
|
_disposed = true;
|
|
}
|
|
}
|
|
}
|
|
} |