using System; using System.Collections.Generic; 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, IDisposable { /// /// 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 uint CapabilityIndex { get; set; } SerializerState[]? _linkedStates; bool _disposed; /// /// 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; } 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; } /// /// 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(); 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; } /// /// 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 != null ? 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 => IsAllocated ? SegmentSpan.Slice(Offset, (int)WordsAllocated) : Span.Empty; /// /// The kind of object this state currently represents. /// 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; } /// /// 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). /// 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 null; } WirePointer pointer = RawData[offset]; if (pointer.IsNull) { return null; } 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 = target.Offset; farSpan[landingPadSlice.Offset + 1] = targetPtr; } } else { targetPtr.Offset = target.Offset - (offset + 1); SegmentSpan[offset] = targetPtr; } } /// /// 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"); 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"); } /// /// 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(); } } 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(); } } } /// /// 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) { 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(); } } /// /// 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) { if (text == null) { VerifyNotYetAllocated(); } else { 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)); 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 if > 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, null if capability is null /// The underlying message builder was not configured for capability table support. public uint? ProvideCapability(Rpc.ConsumedCapability? capability) { if (capability == null) 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; } /// /// 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))!.Value; } /// /// 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 () /// Proxy object (). Note that the provision has "move semantics": SerializerState /// takes ownership, so the Proxy object will be disposed. /// instance /// 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) { 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.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 list: { var builder = BuildPointer(slot); builder.SetListOfPointers(list.Count); int i = 0; foreach (var item in list) { builder.LinkObject(i++, item); } } break; default: if (Rpc.CapabilityReflection.IsValidCapabilityInterface(typeof(T))) { LinkToCapability(slot, ProvideCapability(obj)); } break; } } internal Rpc.ConsumedCapability? StructReadRawCap(int index) { if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil) throw new InvalidOperationException("Allowed on structs only"); if (index < 0 || index >= StructPtrCount) throw new ArgumentOutOfRangeException(nameof(index)); return DecodeCapPointer(index + StructDataCount); } /// /// Reads a struct field as capability and returns a proxy to that capability. /// /// Desired capability interface /// Index into this struct's pointer table. /// The proxy instance /// is out of range. /// The desired interface does not qualify as capability interface () /// This state does not represent a struct. public T? ReadCap(int slot) where T : class { var cap = StructReadRawCap(slot); return Rpc.CapabilityReflection.CreateProxy(cap) as T; } /// /// Reads a struct field as capability and returns a bare (generic) proxy to that capability. /// /// Index into this struct's pointer table. /// The proxy instance /// is out of range. /// This state does not represent a struct. public Rpc.BareProxy ReadCap(int slot) { var cap = StructReadRawCap(slot); return new Rpc.BareProxy(cap); } /// /// Releases the capability table /// public void Dispose() { if (Caps != null && !_disposed) { foreach (var cap in Caps) { cap?.Release(); } Caps.Clear(); _disposed = true; } } } }