Initial commit

This commit is contained in:
Christian Köllner 2019-06-12 21:56:55 +02:00
parent d85a0a019a
commit cbf2144ef4
162 changed files with 101263 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

View File

@ -0,0 +1,24 @@
using System.Threading;
using System.Threading.Tasks;
using Capnp.Rpc;
namespace Capnp.Net.Runtime.Tests
{
class CallContext
{
public CallContext(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken ct)
{
InterfaceId = interfaceId;
MethodId = methodId;
Args = args;
Ct = ct;
Result = new TaskCompletionSource<AnswerOrCounterquestion>();
}
public ulong InterfaceId { get; }
public ushort MethodId { get; }
public DeserializerState Args { get; }
public CancellationToken Ct { get; }
public TaskCompletionSource<AnswerOrCounterquestion> Result { get; }
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,357 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class DeserializationTests
{
[TestMethod]
public void ListOfBytesAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(8, 10);
ds.ListWriteValue(1, (byte)0x11);
ds.ListWriteValue(2, (byte)0x22);
ds.ListWriteValue(3, (byte)0x33);
ds.ListWriteValue(4, (byte)0x44);
ds.ListWriteValue(5, (byte)0x55);
ds.ListWriteValue(6, (byte)0x66);
ds.ListWriteValue(7, (byte)0x77);
ds.ListWriteValue(8, (byte)0x88);
ds.ListWriteValue(9, (byte)0x99);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(10, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual((byte)0x00, asListOfStructs[0].ReadDataByte(0));
Assert.AreEqual((byte)0x11, asListOfStructs[1].ReadDataByte(0));
Assert.AreEqual((byte)0x22, asListOfStructs[2].ReadDataByte(0));
Assert.AreEqual((byte)0x33, asListOfStructs[3].ReadDataByte(0));
Assert.AreEqual((byte)0x44, asListOfStructs[4].ReadDataByte(0));
Assert.AreEqual((byte)0x55, asListOfStructs[5].ReadDataByte(0));
Assert.AreEqual((byte)0x66, asListOfStructs[6].ReadDataByte(0));
Assert.AreEqual((byte)0x77, asListOfStructs[7].ReadDataByte(0));
Assert.AreEqual((byte)0x88, asListOfStructs[8].ReadDataByte(0));
Assert.AreEqual((byte)0x99, asListOfStructs[9].ReadDataByte(0));
}
[TestMethod]
public void ListOfSBytesAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(8, 10);
ds.ListWriteValue(1, (sbyte)-1);
ds.ListWriteValue(2, (sbyte)-2);
ds.ListWriteValue(3, (sbyte)-3);
ds.ListWriteValue(4, (sbyte)-4);
ds.ListWriteValue(5, (sbyte)-5);
ds.ListWriteValue(6, (sbyte)-6);
ds.ListWriteValue(7, (sbyte)-7);
ds.ListWriteValue(8, (sbyte)-8);
ds.ListWriteValue(9, (sbyte)-9);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(10, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual((sbyte)0, asListOfStructs[0].ReadDataSByte(0));
Assert.AreEqual((sbyte)-1, asListOfStructs[1].ReadDataSByte(0));
Assert.AreEqual((sbyte)-2, asListOfStructs[2].ReadDataSByte(0));
Assert.AreEqual((sbyte)-3, asListOfStructs[3].ReadDataSByte(0));
Assert.AreEqual((sbyte)-4, asListOfStructs[4].ReadDataSByte(0));
Assert.AreEqual((sbyte)-5, asListOfStructs[5].ReadDataSByte(0));
Assert.AreEqual((sbyte)-6, asListOfStructs[6].ReadDataSByte(0));
Assert.AreEqual((sbyte)-7, asListOfStructs[7].ReadDataSByte(0));
Assert.AreEqual((sbyte)-8, asListOfStructs[8].ReadDataSByte(0));
Assert.AreEqual((sbyte)-9, asListOfStructs[9].ReadDataSByte(0));
}
[TestMethod]
public void ListOfUShortsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(16, 3);
ds.ListWriteValue(1, (ushort)0x1111);
ds.ListWriteValue(2, (ushort)0x2222);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(3, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual((ushort)0x0000, asListOfStructs[0].ReadDataUShort(0));
Assert.AreEqual((ushort)0x1111, asListOfStructs[1].ReadDataUShort(0));
Assert.AreEqual((ushort)0x2222, asListOfStructs[2].ReadDataUShort(0));
}
[TestMethod]
public void ListOfShortsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(16, 3);
ds.ListWriteValue(1, (short)-0x1111);
ds.ListWriteValue(2, (short)-0x2222);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(3, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual((short)0, asListOfStructs[0].ReadDataShort(0));
Assert.AreEqual((short)-0x1111, asListOfStructs[1].ReadDataShort(0));
Assert.AreEqual((short)-0x2222, asListOfStructs[2].ReadDataShort(0));
}
[TestMethod]
public void ListOfUIntsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(32, 2);
ds.ListWriteValue(1, uint.MaxValue);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(2, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual(0u, asListOfStructs[0].ReadDataUInt(0));
Assert.AreEqual(uint.MaxValue, asListOfStructs[1].ReadDataUInt(0));
}
[TestMethod]
public void ListOfIntsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(32, 2);
ds.ListWriteValue(0, int.MinValue);
ds.ListWriteValue(1, int.MaxValue);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(2, asListOfStructs.Count);
Assert.AreEqual(ObjectKind.Value, asListOfStructs[0].Kind);
Assert.AreEqual(int.MinValue, asListOfStructs[0].ReadDataInt(0));
Assert.AreEqual(int.MaxValue, asListOfStructs[1].ReadDataInt(0));
}
[TestMethod]
public void ListOfULongsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(64, 2);
ds.ListWriteValue(1, ulong.MaxValue);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(2, asListOfStructs.Count);
Assert.AreEqual(0ul, asListOfStructs[0].ReadDataULong(0));
Assert.AreEqual(ulong.MaxValue, asListOfStructs[1].ReadDataULong(0));
}
[TestMethod]
public void ListOfLongsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(64, 2);
ds.ListWriteValue(0, long.MinValue);
ds.ListWriteValue(1, long.MaxValue);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(2, asListOfStructs.Count);
Assert.AreEqual(long.MinValue, asListOfStructs[0].ReadDataLong(0));
Assert.AreEqual(long.MaxValue, asListOfStructs[1].ReadDataLong(0));
}
[TestMethod]
public void ListOfFloatsAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(32, 6);
ds.ListWriteValue(0, 1.0f);
ds.ListWriteValue(1, float.MinValue);
ds.ListWriteValue(2, float.MaxValue);
ds.ListWriteValue(3, float.NaN);
ds.ListWriteValue(4, float.NegativeInfinity);
ds.ListWriteValue(5, float.PositiveInfinity);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(6, asListOfStructs.Count);
Assert.AreEqual(1.0f, asListOfStructs[0].ReadDataFloat(0));
Assert.AreEqual(float.MinValue, asListOfStructs[1].ReadDataFloat(0));
Assert.AreEqual(float.MaxValue, asListOfStructs[2].ReadDataFloat(0));
Assert.AreEqual(float.NaN, asListOfStructs[3].ReadDataFloat(0));
Assert.AreEqual(float.NegativeInfinity, asListOfStructs[4].ReadDataFloat(0));
Assert.AreEqual(float.PositiveInfinity, asListOfStructs[5].ReadDataFloat(0));
}
[TestMethod]
public void ListOfDoublesAsListOfStructs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfValues(64, 6);
ds.ListWriteValue(0, 1.0);
ds.ListWriteValue(1, double.MinValue);
ds.ListWriteValue(2, double.MaxValue);
ds.ListWriteValue(3, double.NaN);
ds.ListWriteValue(4, double.NegativeInfinity);
ds.ListWriteValue(5, double.PositiveInfinity);
DeserializerState d = ds;
var asListOfStructs = d.RequireList().Cast(_ => _);
Assert.AreEqual(6, asListOfStructs.Count);
Assert.AreEqual(1.0, asListOfStructs[0].ReadDataDouble(0));
Assert.AreEqual(double.MinValue, asListOfStructs[1].ReadDataDouble(0));
Assert.AreEqual(double.MaxValue, asListOfStructs[2].ReadDataDouble(0));
Assert.AreEqual(double.NaN, asListOfStructs[3].ReadDataDouble(0));
Assert.AreEqual(double.NegativeInfinity, asListOfStructs[4].ReadDataDouble(0));
Assert.AreEqual(double.PositiveInfinity, asListOfStructs[5].ReadDataDouble(0));
}
[TestMethod]
public void ListOfStructsAsListOfBytes()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(3, 1, 0);
ds.ListBuildStruct(1).WriteData(0, (byte)0x11);
ds.ListBuildStruct(2).WriteData(0, (byte)0x22);
DeserializerState d = ds;
var asListOfBytes = d.RequireList().CastByte();
Assert.AreEqual(3, asListOfBytes.Count);
Assert.AreEqual(0, asListOfBytes[0]);
Assert.AreEqual((byte)0x11, asListOfBytes[1]);
Assert.AreEqual((byte)0x22, asListOfBytes[2]);
}
[TestMethod]
public void ListOfStructsAsListOfSBytes()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(1).WriteData(0, sbyte.MinValue);
DeserializerState d = ds;
var asListOfSBytes = d.RequireList().CastSByte();
Assert.AreEqual(2, asListOfSBytes.Count);
Assert.AreEqual((sbyte)0, asListOfSBytes[0]);
Assert.AreEqual(sbyte.MinValue, asListOfSBytes[1]);
}
[TestMethod]
public void ListOfStructsAsListOfUShorts()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(1).WriteData(0, ushort.MaxValue);
DeserializerState d = ds;
var asListOfUShorts = d.RequireList().CastUShort();
Assert.AreEqual(2, asListOfUShorts.Count);
Assert.AreEqual((ushort)0, asListOfUShorts[0]);
Assert.AreEqual(ushort.MaxValue, asListOfUShorts[1]);
}
[TestMethod]
public void ListOfStructsAsListOfShorts()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(0).WriteData(0, short.MinValue);
ds.ListBuildStruct(1).WriteData(0, short.MaxValue);
DeserializerState d = ds;
var asListOfShorts = d.RequireList().CastShort();
Assert.AreEqual(2, asListOfShorts.Count);
Assert.AreEqual(short.MinValue, asListOfShorts[0]);
Assert.AreEqual(short.MaxValue, asListOfShorts[1]);
}
[TestMethod]
public void ListOfStructsAsListOfUInts()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(1).WriteData(0, uint.MaxValue);
DeserializerState d = ds;
var asListOfUInts = d.RequireList().CastUInt();
Assert.AreEqual(2, asListOfUInts.Count);
Assert.AreEqual(0u, asListOfUInts[0]);
Assert.AreEqual(uint.MaxValue, asListOfUInts[1]);
}
[TestMethod]
public void ListOfStructsAsListOfInts()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(0).WriteData(0, int.MinValue);
ds.ListBuildStruct(1).WriteData(0, int.MaxValue);
DeserializerState d = ds;
var asListOfInts = d.RequireList().CastInt();
Assert.AreEqual(2, asListOfInts.Count);
Assert.AreEqual(int.MinValue, asListOfInts[0]);
Assert.AreEqual(int.MaxValue, asListOfInts[1]);
}
[TestMethod]
public void ListOfStructsAsListOfULongs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(1).WriteData(0, ulong.MaxValue);
DeserializerState d = ds;
var asListOfULongs = d.RequireList().CastULong();
Assert.AreEqual(2, asListOfULongs.Count);
Assert.AreEqual(0ul, asListOfULongs[0]);
Assert.AreEqual(ulong.MaxValue, asListOfULongs[1]);
}
[TestMethod]
public void ListOfStructsAsListOfLongs()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(0).WriteData(0, long.MinValue);
ds.ListBuildStruct(1).WriteData(0, long.MaxValue);
DeserializerState d = ds;
var asListOfLongs = d.RequireList().CastLong();
Assert.AreEqual(2, asListOfLongs.Count);
Assert.AreEqual(long.MinValue, asListOfLongs[0]);
Assert.AreEqual(long.MaxValue, asListOfLongs[1]);
}
[TestMethod]
public void ListOfStructsAsListOfFloats()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(0).WriteData(0, float.NaN);
ds.ListBuildStruct(1).WriteData(0, float.PositiveInfinity);
DeserializerState d = ds;
var asListOfFloats = d.RequireList().CastFloat();
Assert.AreEqual(2, asListOfFloats.Count);
Assert.AreEqual(float.NaN, asListOfFloats[0]);
Assert.AreEqual(float.PositiveInfinity, asListOfFloats[1]);
}
[TestMethod]
public void ListOfStructsAsListOfDoubles()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(2, 1, 0);
ds.ListBuildStruct(0).WriteData(0, double.NegativeInfinity);
ds.ListBuildStruct(1).WriteData(0, double.MaxValue);
DeserializerState d = ds;
var asListOfDoubles = d.RequireList().CastDouble();
Assert.AreEqual(2, asListOfDoubles.Count);
Assert.AreEqual(double.NegativeInfinity, asListOfDoubles[0]);
Assert.AreEqual(double.MaxValue, asListOfDoubles[1]);
}
}
}

View File

@ -0,0 +1,927 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class DynamicSerializerStateTests
{
[TestMethod]
public void Struct()
{
var mb = MessageBuilder.Create(8);
var ds = new DynamicSerializerState(mb);
var alloc = mb.Allocator;
ds.SetStruct(3, 2);
Assert.IsFalse(ds.IsAllocated);
Assert.ThrowsException<InvalidOperationException>(() => ds.SetListOfPointers(1));
Assert.ThrowsException<InvalidOperationException>(() => ds.SetListOfStructs(3, 2, 1));
Assert.ThrowsException<InvalidOperationException>(() => ds.SetListOfValues(8, 3));
Assert.ThrowsException<InvalidOperationException>(() => ds.SetStruct(2, 3));
ds.SetStruct(3, 2);
ds.Allocate();
Assert.IsTrue(ds.IsAllocated);
Assert.AreEqual(3, ds.StructDataSection.Length);
ds.StructWriteData(0, 16, 0x4321);
ds.StructWriteData(64, 32, 0x87654321);
ds.StructWriteData(128, 64, 0x1234567812345678);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ds.StructWriteData(191, 2, 1));
var ds2 = ds.BuildPointer(0);
ds2.SetStruct(1, 0);
ds2.WriteData(0, 1.23);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ds.Link(2, ds));
Assert.AreEqual(1, alloc.Segments.Count);
Assert.AreEqual(7, alloc.Segments[0].Length);
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.Struct, d.Kind);
Assert.AreEqual(3, d.StructDataCount);
Assert.AreEqual(2, d.StructPtrCount);
Assert.AreEqual(0x4321, d.ReadDataUShort(0));
Assert.AreEqual(0x87654321, d.ReadDataUInt(64));
Assert.IsTrue(0x1234567812345678 == d.ReadDataULong(128));
var p0 = d.StructReadPointer(0);
Assert.AreEqual(ObjectKind.Struct, p0.Kind);
Assert.AreEqual(1.23, p0.ReadDataDouble(0));
var p1 = d.StructReadPointer(1);
Assert.AreEqual(ObjectKind.Nil, p1.Kind);
}
[TestMethod]
public void StructWithPrimitives()
{
var mb = MessageBuilder.Create(8);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetStruct(10, 0);
ds.WriteData(0, ulong.MaxValue - 1, ulong.MaxValue);
ds.WriteData(64, long.MaxValue - 1, -123);
ds.WriteData(128, uint.MaxValue - 1, 123);
ds.WriteData(160, int.MaxValue - 1, -123);
ds.WriteData(192, (ushort)(ushort.MaxValue - 1), (ushort)321);
ds.WriteData(208, (short)(short.MaxValue - 1), (short)321);
ds.WriteData(224, (byte)(byte.MaxValue - 1), (byte)111);
ds.WriteData(232, (sbyte)(sbyte.MaxValue - 1), (sbyte)(-111));
ds.WriteData(240, false, false);
ds.WriteData(241, false, true);
ds.WriteData(242, true, false);
ds.WriteData(243, true, true);
ds.WriteData(256, 12.34, 0.5);
ds.WriteData(320, 1.2f, 0.5f);
DeserializerState d = ds;
Assert.AreEqual(ulong.MaxValue - 1, d.ReadDataULong(0, ulong.MaxValue));
Assert.AreEqual(long.MaxValue - 1, d.ReadDataLong(64, -123));
Assert.AreEqual(uint.MaxValue - 1, d.ReadDataUInt(128, 123));
Assert.AreEqual(int.MaxValue - 1, d.ReadDataInt(160, -123));
Assert.AreEqual(ushort.MaxValue - 1, d.ReadDataUShort(192, 321));
Assert.AreEqual(short.MaxValue - 1, d.ReadDataShort(208, 321));
Assert.AreEqual(byte.MaxValue - 1, d.ReadDataByte(224, 111));
Assert.AreEqual(sbyte.MaxValue - 1, d.ReadDataSByte(232, -111));
Assert.AreEqual(false, d.ReadDataBool(240, false));
Assert.AreEqual(false, d.ReadDataBool(241, true));
Assert.AreEqual(true, d.ReadDataBool(242, false));
Assert.AreEqual(true, d.ReadDataBool(243, true));
Assert.AreEqual(12.34, d.ReadDataDouble(256, 0.5));
Assert.AreEqual(1.2f, d.ReadDataFloat(320, 0.5f));
}
[TestMethod]
public void StructRecursion()
{
var mb = MessageBuilder.Create(8);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetStruct(1, 1);
ds.WriteData(0, 123);
ds.Link(0, ds, false);
DeserializerState d = ds;
Assert.AreEqual(123, d.ReadDataInt(0));
Assert.AreEqual(123, d.StructReadPointer(0).ReadDataInt(0));
Assert.ThrowsException<DeserializationException>(() =>
{
for (int i = 0; i < 64000000; i++)
{
d = d.StructReadPointer(0);
}
});
}
[TestMethod]
public void StructWithLists()
{
var mb = MessageBuilder.Create(8);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
{
ds.SetStruct(0, 2);
ds.WriteText(0, "Lorem ipsum");
var los = ds.BuildPointer(1);
los.SetListOfStructs(3, 1, 1);
var e0 = los.ListBuildStruct(0);
e0.WriteData(0, long.MinValue);
e0.WriteText(0, long.MinValue.ToString());
var e1 = los.ListBuildStruct(1);
e1.WriteData(0, 0L);
e1.WriteText(0, 0L.ToString());
var e2 = los.ListBuildStruct(2);
e2.WriteData(0, long.MaxValue);
e2.WriteText(0, long.MaxValue.ToString());
}
{
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.Struct, d.Kind);
Assert.AreEqual("Lorem ipsum", d.ReadText(0));
var los = d.ReadListOfStructs(1, _ => _);
Assert.AreEqual(3, los.Count);
Assert.AreEqual(long.MinValue, los[0].ReadDataLong(0));
Assert.AreEqual(long.MinValue.ToString(), los[0].ReadText(0));
Assert.AreEqual(0L, los[1].ReadDataLong(0));
Assert.AreEqual(0L.ToString(), los[1].ReadText(0));
Assert.AreEqual(long.MaxValue, los[2].ReadDataLong(0));
Assert.AreEqual(long.MaxValue.ToString(), los[2].ReadText(0));
}
}
[TestMethod]
public void MultiSegmentStruct()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetStruct(1, 2);
ds.WriteData(0, 815);
string s0 = "Lorem ipsum dolor sit amet";
ds.WriteText(0, s0);
string s1 = "All men are created equal";
ds.WriteText(1, s1);
Assert.IsTrue(alloc.Segments.Count >= 3);
DeserializerState d = ds;
Assert.AreEqual(815, d.ReadDataInt(0));
Assert.AreEqual(s0, d.ReadText(0));
Assert.AreEqual(s1, d.ReadText(1));
}
[TestMethod]
public void ListOfEmpty()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetListOfValues(0, int.MaxValue);
Assert.ThrowsException<InvalidOperationException>(
() => ds.ListWriteValue(0, 1, 0));
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.ListOfEmpty, d.Kind);
Assert.AreEqual(int.MaxValue, d.ListElementCount);
}
[TestMethod]
public void ListOfBits()
{
bool ValueGen(int index)
{
return (index % 3) == 0;
}
DynamicSerializerState CreateListBulk(int count)
{
var mb = MessageBuilder.Create(8);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetListOfValues(1, count);
var list = new List<bool>();
for (int i = 0; i < count; i++)
{
list.Add(ValueGen(i));
}
ds.ListWriteValues(list);
return ds;
}
DynamicSerializerState CreateListSingle(int count)
{
var mb = MessageBuilder.Create(8);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetListOfValues(1, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, ValueGen(i));
}
return ds;
}
void VerifyList(DeserializerState d, int count)
{
Assert.AreEqual(ObjectKind.ListOfBits, d.Kind);
Assert.AreEqual(count, d.ListElementCount);
var rlist = d.RequireList().CastBool();
Assert.AreEqual(count, rlist.Count);
for (int i = 0; i < count; i++)
{
bool expected = ValueGen(i);
bool actual = rlist[i];
Assert.AreEqual(expected, actual);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = rlist[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = rlist[count];
});
}
int c = 123;
VerifyList(CreateListBulk(c), c);
VerifyList(CreateListSingle(c), c);
}
[TestMethod]
public void ListOfSBytes()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 100;
ds.SetListOfValues(8, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (sbyte)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var sbytes = d.RequireList().CastSByte();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((sbyte)i, sbytes[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = sbytes[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = sbytes[count];
});
}
[TestMethod]
public void ListOfBytes()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 277;
ds.SetListOfValues(8, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (byte)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var bytes = d.RequireList().CastByte();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((byte)i, bytes[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = bytes[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = bytes[count];
});
}
[TestMethod]
public void ListOfShorts()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 333;
ds.SetListOfValues(16, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (short)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var shorts = d.RequireList().CastShort();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((short)i, shorts[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = shorts[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = shorts[count];
});
}
[TestMethod]
public void ListOfUShorts()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 234;
ds.SetListOfValues(16, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (ushort)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var ushorts = d.RequireList().CastUShort();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((ushort)i, ushorts[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ushorts[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ushorts[count];
});
}
[TestMethod]
public void ListOfInts()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 1234;
ds.SetListOfValues(32, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var ints = d.RequireList().CastInt();
for (int i = 0; i < count; i++)
{
Assert.AreEqual(i, ints[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ints[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ints[count];
});
}
[TestMethod]
public void ListOfUInts()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 321;
ds.SetListOfValues(32, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (uint)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var uints = d.RequireList().CastUInt();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((uint)i, uints[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = uints[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = uints[count];
});
}
[TestMethod]
public void ListOfLongs()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 432;
ds.SetListOfValues(64, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (long)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var longs = d.RequireList().CastLong();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((long)i, longs[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = longs[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = longs[count];
});
}
[TestMethod]
public void ListOfULongs()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 345;
ds.SetListOfValues(64, count);
for (int i = 0; i < count; i++)
{
ds.ListWriteValue(i, (ulong)i);
}
DeserializerState d = ds;
Assert.AreEqual(count, d.ListElementCount);
var ulongs = d.RequireList().CastULong();
for (int i = 0; i < count; i++)
{
Assert.AreEqual((ulong)i, ulongs[i]);
}
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ulongs[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = ulongs[count];
});
}
[TestMethod]
public void ListOfPointers()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 77;
ds.SetListOfPointers(count);
var dse0 = ds.BuildPointer(0);
dse0.SetStruct(1, 0);
dse0.WriteData(0, 654);
var dse1 = new DynamicSerializerState(mb);
dse1.SetStruct(1, 0);
dse1.WriteData(0, 555);
ds.Link(1, dse1);
ds.Link(2, dse1);
var dse3 = ds.BuildPointer(3);
dse3.SetListOfValues(32, 10);
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.ListOfPointers, d.Kind);
Assert.AreEqual(count, d.ListElementCount);
var pointers = d.RequireList().Cast(_ => _);
Assert.AreEqual(ObjectKind.Struct, pointers[0].Kind);
Assert.AreEqual(654, pointers[0].ReadDataInt(0));
Assert.AreEqual(ObjectKind.Struct, pointers[1].Kind);
Assert.AreEqual(555, pointers[1].ReadDataInt(0));
Assert.AreEqual(ObjectKind.Struct, pointers[2].Kind);
Assert.AreEqual(555, pointers[2].ReadDataInt(0));
Assert.AreEqual(ObjectKind.ListOfInts, pointers[3].Kind);
Assert.AreEqual(10, pointers[3].ListElementCount);
Assert.AreEqual(ObjectKind.Nil, pointers[4].Kind);
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = pointers[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = pointers[count];
});
}
[TestMethod]
public void ListOfStructs()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int count = 23;
ds.SetListOfStructs(count, 2, 1);
var dse0 = ds.ListBuildStruct(0);
dse0.WriteData(0, 1);
dse0.WriteData(64, 2);
var dse1 = ds.ListBuildStruct(1);
dse1.WriteData(0, 3);
dse1.WriteData(64, 4);
Assert.ThrowsException<InvalidOperationException>(
() => ds.Link(2, ds));
Assert.ThrowsException<InvalidOperationException>(
() => ds.BuildPointer(2));
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.ListOfStructs, d.Kind);
Assert.AreEqual(count, d.ListElementCount);
var pointers = d.RequireList().Cast(_ => _);
Assert.AreEqual(ObjectKind.Struct, pointers[0].Kind);
Assert.AreEqual(1, pointers[0].ReadDataInt(0));
Assert.AreEqual(2, pointers[0].ReadDataInt(64));
Assert.AreEqual(ObjectKind.Struct, pointers[1].Kind);
Assert.AreEqual(3, pointers[1].ReadDataInt(0));
Assert.AreEqual(4, pointers[1].ReadDataInt(64));
Assert.AreEqual(ObjectKind.Struct, pointers[2].Kind);
Assert.AreEqual(0, pointers[2].ReadDataInt(0));
Assert.AreEqual(0, pointers[2].ReadDataInt(64));
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = pointers[-1];
});
Assert.ThrowsException<IndexOutOfRangeException>(() =>
{
var dummy = pointers[count];
});
}
[TestMethod]
public void Capability()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
ds.SetStruct(0, 1);
ds.LinkToCapability(0, 13);
DeserializerState d = ds;
Assert.AreEqual(ObjectKind.Struct, d.Kind);
var p = d.StructReadPointer(0);
Assert.AreEqual(ObjectKind.Capability, p.Kind);
Assert.AreEqual(13u, p.CapabilityIndex);
}
[TestMethod]
public void Abuse()
{
var mb = MessageBuilder.Create(1);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
ds.SetListOfPointers(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
ds.SetListOfStructs(-10, 100, 200));
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
ds.SetListOfValues(2, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
ds.SetListOfValues(1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
ds.SetListOfValues(65, 1));
}
[TestMethod]
public void SharedPointer()
{
var mb = MessageBuilder.Create(128);
var alloc = mb.Allocator;
var ds0 = new DynamicSerializerState(mb);
ds0.SetStruct(1, 0);
ds0.WriteData(0, 123);
var ds1 = new DynamicSerializerState(mb);
ds1.SetStruct(0, 1);
ds1.Link(0, ds0);
var ds2 = new DynamicSerializerState(mb);
ds2.SetStruct(0, 1);
ds2.Link(0, ds0);
DeserializerState d1 = ds1;
Assert.AreEqual(ObjectKind.Struct, d1.Kind);
var e1 = d1.StructReadPointer(0);
Assert.AreEqual(ObjectKind.Struct, e1.Kind);
Assert.AreEqual(123, e1.ReadDataInt(0));
DeserializerState d2 = ds2;
Assert.AreEqual(ObjectKind.Struct, d2.Kind);
var e2 = d2.StructReadPointer(0);
Assert.AreEqual(ObjectKind.Struct, e2.Kind);
Assert.AreEqual(123, e2.ReadDataInt(0));
}
[TestMethod]
public void DeepCopy()
{
var mb = MessageBuilder.Create(128);
var alloc1 = mb.Allocator;
var ds1 = new DynamicSerializerState(mb);
ds1.SetStruct(10, 18);
for (int i = 0; i < 10; i++)
{
ds1.WriteData(64 * (ulong)i, i);
var el = ds1.BuildPointer(i);
el.SetStruct(1, 0);
el.WriteData(0, -i);
}
var los1 = ds1.BuildPointer(10);
los1.SetListOfStructs(1, 1, 0);
var el1 = los1.ListBuildStruct(0);
el1.WriteData(0, 111);
var lov1 = ds1.BuildPointer(11);
lov1.SetListOfValues(1, 7);
lov1.ListWriteValue(2, true);
lov1.ListWriteValue(3, true);
lov1.ListWriteValue(5, true);
var lovu8 = ds1.BuildPointer(12);
lovu8.SetListOfValues(8, 5);
lovu8.ListWriteValue(1, (byte)0x55);
lovu8.ListWriteValue(2, (byte)0xaa);
var lovu16 = ds1.BuildPointer(13);
lovu16.SetListOfValues(16, 3);
lovu16.ListWriteValue(0, (ushort)0x1111);
lovu16.ListWriteValue(1, (ushort)0x2222);
lovu16.ListWriteValue(2, (ushort)0x3333);
var lovu32 = ds1.BuildPointer(14);
lovu32.SetListOfValues(32, 9);
lovu32.ListWriteValue(1, 1u);
lovu32.ListWriteValue(2, 4u);
lovu32.ListWriteValue(3, 9u);
lovu32.ListWriteValue(4, 16u);
lovu32.ListWriteValue(5, 25u);
lovu32.ListWriteValue(6, 36u);
lovu32.ListWriteValue(7, 49u);
lovu32.ListWriteValue(8, 64u);
var lov64 = ds1.BuildPointer(15);
lov64.SetListOfValues(64, 2);
lov64.ListWriteValue(0, long.MinValue);
lov64.ListWriteValue(1, long.MaxValue);
var lop1 = ds1.BuildPointer(16);
lop1.SetListOfPointers(1);
lop1.LinkToCapability(0, 19);
var loe1 = ds1.BuildPointer(17);
loe1.SetListOfValues(0, 100);
var mb2 = MessageBuilder.Create(128);
var alloc2 = mb2.Allocator;
var ds2 = new DynamicSerializerState(mb2);
ds2.SetStruct(0, 3);
Assert.ThrowsException<InvalidOperationException>(() =>
ds2.Link(0, ds1, false));
ds2.Link(0, ds1, true);
var lop = ds2.BuildPointer(1);
lop.SetListOfPointers(1);
lop.Link(0, ds1);
ds2.Link(2, el1, true);
void VerifyBigStruct(DeserializerState ds)
{
Assert.AreEqual(ObjectKind.Struct, ds.Kind);
Assert.AreEqual<ushort>(10, ds.StructDataCount);
Assert.AreEqual<ushort>(18, ds.StructPtrCount);
for (int i = 0; i < 10; i++)
{
Assert.AreEqual(i, ds.ReadDataInt(64 * (ulong)i));
var el = ds.StructReadPointer(i);
Assert.AreEqual(ObjectKind.Struct, el.Kind);
Assert.AreEqual(-i, el.ReadDataInt(0));
}
var elx = ds.StructReadPointer(10);
Assert.AreEqual(ObjectKind.ListOfStructs, elx.Kind);
Assert.AreEqual(1, elx.ListElementCount);
var el0 = elx.RequireList().Cast(_ => _)[0];
Assert.AreEqual(111, el0.ReadDataInt(0));
var e11 = ds.StructReadPointer(11);
Assert.AreEqual(ObjectKind.ListOfBits, e11.Kind);
Assert.AreEqual(7, e11.ListElementCount);
Assert.IsTrue(e11.RequireList().CastBool()[2]);
Assert.IsTrue(e11.RequireList().CastBool()[3]);
Assert.IsTrue(e11.RequireList().CastBool()[5]);
var e12 = ds.StructReadPointer(12);
Assert.AreEqual(ObjectKind.ListOfBytes, e12.Kind);
Assert.AreEqual(5, e12.ListElementCount);
Assert.AreEqual((byte)0x55, e12.RequireList().CastByte()[1]);
Assert.AreEqual((byte)0xaa, e12.RequireList().CastByte()[2]);
var e13 = ds.StructReadPointer(13);
Assert.AreEqual(ObjectKind.ListOfShorts, e13.Kind);
Assert.AreEqual(3, e13.ListElementCount);
Assert.AreEqual((ushort)0x1111, e13.RequireList().CastUShort()[0]);
Assert.AreEqual((ushort)0x2222, e13.RequireList().CastUShort()[1]);
Assert.AreEqual((ushort)0x3333, e13.RequireList().CastUShort()[2]);
var e14 = ds.StructReadPointer(14);
Assert.AreEqual(ObjectKind.ListOfInts, e14.Kind);
Assert.AreEqual(9, e14.ListElementCount);
Assert.AreEqual((uint)0, e14.RequireList().CastUInt()[0]);
Assert.AreEqual((uint)1, e14.RequireList().CastUInt()[1]);
Assert.AreEqual((uint)4, e14.RequireList().CastUInt()[2]);
Assert.AreEqual((uint)9, e14.RequireList().CastUInt()[3]);
Assert.AreEqual((uint)16, e14.RequireList().CastUInt()[4]);
Assert.AreEqual((uint)25, e14.RequireList().CastUInt()[5]);
Assert.AreEqual((uint)36, e14.RequireList().CastUInt()[6]);
Assert.AreEqual((uint)49, e14.RequireList().CastUInt()[7]);
Assert.AreEqual((uint)64, e14.RequireList().CastUInt()[8]);
var e15 = ds.StructReadPointer(15);
Assert.AreEqual(ObjectKind.ListOfLongs, e15.Kind);
Assert.AreEqual(2, e15.ListElementCount);
Assert.AreEqual(long.MinValue, e15.RequireList().CastLong()[0]);
Assert.AreEqual(long.MaxValue, e15.RequireList().CastLong()[1]);
var e16 = ds.StructReadPointer(16);
Assert.AreEqual(ObjectKind.ListOfPointers, e16.Kind);
Assert.AreEqual(1, e16.ListElementCount);
var cap = e16.RequireList().Cast(_ => _)[0];
Assert.AreEqual(ObjectKind.Capability, cap.Kind);
Assert.AreEqual(19u, cap.CapabilityIndex);
var e17 = ds.StructReadPointer(17);
Assert.AreEqual(ObjectKind.ListOfEmpty, e17.Kind);
Assert.AreEqual(100, e17.ListElementCount);
}
DeserializerState d = ds2;
Assert.AreEqual(ObjectKind.Struct, d.Kind);
var p0 = d.StructReadPointer(0);
VerifyBigStruct(p0);
var p1 = d.StructReadPointer(1);
Assert.AreEqual(ObjectKind.ListOfPointers, p1.Kind);
var p1el0 = p1.RequireList().Cast(_ => _)[0];
VerifyBigStruct(p1el0);
var p2 = d.StructReadPointer(2);
Assert.AreEqual(ObjectKind.Struct, p2.Kind);
Assert.AreEqual(111, p2.ReadDataInt(0));
}
[TestMethod]
public void List2D()
{
var mb = MessageBuilder.Create(128);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int w = 4;
int h = 5;
ds.SetListOfPointers(w);
for (int i = 0; i < w; i++)
{
var p = ds.BuildPointer(i);
p.SetListOfValues(32, h);
for (int j = 0; j < h; j++)
{
p.ListWriteValue(j, i - j);
}
}
DeserializerState d = ds;
var matrix = d.RequireList().Cast2D<int>();
Assert.AreEqual(matrix.Count, w);
for (int i = 0; i < w; i++)
{
var v = matrix[i];
Assert.AreEqual(h, v.Count);
for (int j = 0; j < h; j++)
{
Assert.AreEqual(i - j, v[j]);
}
}
}
[TestMethod]
public void List3D()
{
var mb = MessageBuilder.Create(128);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int d0 = 3;
int d1 = 2;
int d2 = 4;
ds.SetListOfPointers(d0);
for (int i = 0; i < d0; i++)
{
var p = ds.BuildPointer(i);
p.SetListOfPointers(d1);
for (int j = 0; j < d1; j++)
{
var q = p.BuildPointer(j);
q.SetListOfValues(32, d2);
for (int k = 0; k < d2; k++)
{
q.ListWriteValue(k, i ^ j ^ k);
}
}
}
DeserializerState d = ds;
var qube = d.RequireList().Cast3D<int>();
Assert.AreEqual(qube.Count, d0);
for (int i = 0; i < d0; i++)
{
var matrix = qube[i];
Assert.AreEqual(d1, matrix.Count);
for (int j = 0; j < d1; j++)
{
var vector = matrix[j];
for (int k = 0; k < d2; k++)
{
Assert.AreEqual(i ^ j ^ k, vector[k]);
}
}
}
}
[TestMethod]
public void List4D()
{
var mb = MessageBuilder.Create(128);
var alloc = mb.Allocator;
var ds = new DynamicSerializerState(mb);
int d0 = 3;
int d1 = 2;
int d2 = 4;
int d3 = 5;
ds.SetListOfPointers(d0);
for (int i = 0; i < d0; i++)
{
var p = ds.BuildPointer(i);
p.SetListOfPointers(d1);
for (int j = 0; j < d1; j++)
{
var q = p.BuildPointer(j);
q.SetListOfPointers(d2);
for (int k = 0; k < d2; k++)
{
var r = q.BuildPointer(k);
r.SetListOfValues(32, d3);
for (int l = 0; l < d3; l++)
{
r.ListWriteValue(l, (float)(i * j + k * l));
}
}
}
}
DeserializerState d = ds;
var hqube = (IReadOnlyList<object>) d.RequireList().CastND<float>(4);
Assert.AreEqual(hqube.Count, d0);
for (int i = 0; i < d0; i++)
{
var qube = (IReadOnlyList<object>)hqube[i];
Assert.AreEqual(d1, qube.Count);
for (int j = 0; j < d1; j++)
{
var matrix = (IReadOnlyList<object>)qube[j];
Assert.AreEqual(d2, matrix.Count);
for (int k = 0; k < d2; k++)
{
var vector = (IReadOnlyList<float>)matrix[k];
Assert.AreEqual(d3, vector.Count);
for (int l = 0; l < d3; l++)
{
Assert.AreEqual((float)(i * j + k * l), vector[l]);
}
}
}
}
}
}
}

View File

@ -0,0 +1,184 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class FramePumpTests
{
class MyStruct : SerializerState
{
public MyStruct()
{
SetStruct(0, 1);
}
}
[TestMethod]
public void PipedFramePump()
{
int UnpackFrame(WireFrame frame)
{
int count = frame.Segments.Count;
for (int i = 0; i < count; i++)
{
Assert.AreEqual(i + 1, frame.Segments[i].Length);
}
return count;
}
WireFrame PackFrame(int value)
{
var segments = new Memory<ulong>[value];
for (int i = 0; i < value; i++)
{
ulong[] a = new ulong[i + 1];
segments[i] = new Memory<ulong>(a);
}
return new WireFrame(segments);
}
Thread rxRunner = null;
using (var server = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None))
using (var client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle))
using (var bc = new BlockingCollection<int>(8))
{
server.ReadMode = PipeTransmissionMode.Byte;
client.ReadMode = PipeTransmissionMode.Byte;
using (var txPump = new FramePump(server))
using (var rxPump = new FramePump(client))
{
rxRunner = new Thread(() =>
{
rxPump.Run();
});
rxPump.FrameReceived += f => bc.Add(UnpackFrame(f));
rxRunner.Start();
for (int i = 0; i < 100; i++)
{
txPump.Send(PackFrame(1));
txPump.Send(PackFrame(8));
txPump.Send(PackFrame(2));
txPump.Send(PackFrame(7));
txPump.Send(PackFrame(3));
txPump.Send(PackFrame(6));
txPump.Send(PackFrame(4));
txPump.Send(PackFrame(5));
Assert.IsTrue(SpinWait.SpinUntil(() => bc.Count == 8, 500));
Assert.AreEqual(1, bc.Take());
Assert.AreEqual(8, bc.Take());
Assert.AreEqual(2, bc.Take());
Assert.AreEqual(7, bc.Take());
Assert.AreEqual(3, bc.Take());
Assert.AreEqual(6, bc.Take());
Assert.AreEqual(4, bc.Take());
Assert.AreEqual(5, bc.Take());
}
}
}
Assert.IsTrue(rxRunner.Join(500));
}
[TestMethod]
public void FramePumpDeferredProcessing()
{
int UnpackAndVerifyFrame(WireFrame frame, int expectedCount)
{
int count = frame.Segments.Count;
Assert.AreEqual(expectedCount, count);
for (int i = 0; i < count; i++)
{
int length = frame.Segments[i].Length;
Assert.AreEqual(expectedCount - i, length);
for (int j = 0; j < length; j++)
{
Assert.AreEqual((ulong)(length - j), frame.Segments[i].Span[j]);
}
}
return count;
}
WireFrame PackFrame(int value)
{
var segments = new Memory<ulong>[value];
for (int i = 0; i < value; i++)
{
ulong[] a = new ulong[value - i];
segments[i] = new Memory<ulong>(a);
for (int j = 0; j < a.Length; j++)
{
a[j] = (ulong)(a.Length - j);
}
}
return new WireFrame(segments);
}
Thread rxRunner = null;
using (var server = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None))
using (var client = new AnonymousPipeClientStream(PipeDirection.In, server.ClientSafePipeHandle))
using (var bc = new BlockingCollection<WireFrame>(8))
{
server.ReadMode = PipeTransmissionMode.Byte;
client.ReadMode = PipeTransmissionMode.Byte;
using (var txPump = new FramePump(server))
using (var rxPump = new FramePump(client))
{
rxRunner = new Thread(() =>
{
rxPump.Run();
});
rxPump.FrameReceived += bc.Add;
rxRunner.Start();
txPump.Send(PackFrame(1));
txPump.Send(PackFrame(8));
txPump.Send(PackFrame(2));
txPump.Send(PackFrame(7));
txPump.Send(PackFrame(3));
txPump.Send(PackFrame(6));
txPump.Send(PackFrame(4));
txPump.Send(PackFrame(5));
Assert.IsTrue(SpinWait.SpinUntil(() => bc.Count == 8, 500));
UnpackAndVerifyFrame(bc.Take(), 1);
UnpackAndVerifyFrame(bc.Take(), 8);
UnpackAndVerifyFrame(bc.Take(), 2);
UnpackAndVerifyFrame(bc.Take(), 7);
UnpackAndVerifyFrame(bc.Take(), 3);
UnpackAndVerifyFrame(bc.Take(), 6);
UnpackAndVerifyFrame(bc.Take(), 4);
UnpackAndVerifyFrame(bc.Take(), 5);
}
}
Assert.IsTrue(rxRunner.Join(500));
}
}
}

View File

@ -0,0 +1,71 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class General
{
[TestMethod]
public void AwaitOrderTest()
{
// This test verifies an execution order assumption about the .NET environment:
// When I register multiple continuations on the same Task, using the await
// keyword, I expect all continuations be executed in the same order they were
// registered. Despite I could not find any official statement on this behavior,
// the Capnp.Net.Runtime implementation relies on that assumption. Should that
// assumption turn out to be wrong, you might observe RPCs which are executed in
// a different order than they were requested.
int returnCounter = 0;
async Task ExpectCount(Task task, int count)
{
await task;
Assert.AreEqual(count, returnCounter++);
}
var tcs = new TaskCompletionSource<int>();
var tasks =
from i in Enumerable.Range(0, 100)
select ExpectCount(tcs.Task, i);
tcs.SetResult(0);
Task.WhenAll(tasks).Wait();
}
[TestMethod]
public void AwaitOrderTest2()
{
int returnCounter = 0;
async Task ExpectCount(Task task, int count)
{
await task;
Assert.AreEqual(count, returnCounter++);
}
var tcs = new TaskCompletionSource<int>();
var cts = new CancellationTokenSource();
var tasks =
from i in Enumerable.Range(0, 100)
select ExpectCount(tcs.Task.ContinueWith(
t => t,
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current), i);
tcs.SetResult(0);
Task.WhenAll(tasks).Wait();
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Capnp.Net.Runtime.Tests
{
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(IntPtr a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
private IntPtr handle;
private bool disposed;
public Job()
{
handle = CreateJobObject(IntPtr.Zero, null);
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) { }
Close();
disposed = true;
}
public void Close()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr processHandle)
{
return AssignProcessToJobObject(handle, processHandle);
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public UInt32 LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public Int32 bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion
}

View File

@ -0,0 +1,65 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class MessageBuilderTests
{
class Struct2D0P : SerializerState
{
public Struct2D0P()
{
SetStruct(2, 0);
}
}
class Struct0D1P : SerializerState
{
public Struct0D1P()
{
SetStruct(0, 1);
}
}
[TestMethod]
public void BuildDynamicMessage()
{
var mb = MessageBuilder.Create(128);
Assert.IsNull(mb.Root);
var root = mb.BuildRoot<Struct2D0P>();
Assert.IsNotNull(root);
Assert.AreSame(root, mb.Root);
root.WriteData(0, long.MinValue);
root.WriteData(64, long.MaxValue);
var frame = mb.Frame;
var droot = DeserializerState.CreateRoot(frame);
Assert.AreEqual(ObjectKind.Struct, droot.Kind);
Assert.AreEqual(2, droot.StructDataCount);
Assert.AreEqual(long.MinValue, droot.ReadDataLong(0));
Assert.AreEqual(long.MaxValue, droot.ReadDataLong(64));
}
[TestMethod]
public void SmallSegments()
{
WireFrame frame;
for (int i = 1; i <= 8; i++)
{
{
var mb = MessageBuilder.Create(128);
var root = mb.BuildRoot<Struct0D1P>();
var p = root.BuildPointer(0);
p.SetListOfValues(64, i);
frame = mb.Frame;
}
{
var root = DeserializerState.CreateRoot(frame);
Assert.AreEqual(i, root.StructReadPointer(0).ListElementCount);
}
}
}
}
}

View File

@ -0,0 +1,24 @@
using System.Threading;
using System.Threading.Tasks;
using Capnp.Rpc;
namespace Capnp.Net.Runtime.Tests
{
class ProvidedCapabilityMock : Skeleton
{
readonly TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>
_call = new TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>();
public override Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId,
DeserializerState args, CancellationToken cancellationToken = default(CancellationToken))
{
_call.SetResult((interfaceId, methodId, args, cancellationToken));
return Return.Task;
}
public Task<(ulong, ushort, DeserializerState, CancellationToken)> WhenCalled =>
_call.Task;
public TaskCompletionSource<AnswerOrCounterquestion> Return { get; } = new TaskCompletionSource<AnswerOrCounterquestion>();
}
}

View File

@ -0,0 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks.Dataflow;
namespace Capnp.Net.Runtime.Tests
{
class ProvidedCapabilityMultiCallMock : Skeleton
{
readonly BufferBlock<CallContext> _ccs = new BufferBlock<CallContext>();
public override Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId,
DeserializerState args, CancellationToken cancellationToken = default(CancellationToken))
{
var cc = new CallContext(interfaceId, methodId, args, cancellationToken);
Assert.IsTrue(_ccs.Post(cc));
return cc.Result.Task;
}
public Task<CallContext> WhenCalled => _ccs.ReceiveAsync();
}
}

View File

@ -0,0 +1,369 @@
using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class RpcSchemaTests
{
[TestMethod]
public void MessageAbort()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Abort;
Assert.AreEqual(Message.WHICH.Abort, w.which);
w.Abort.Reason = "reason";
Assert.AreEqual("reason", w.Abort.Reason);
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
Assert.AreEqual(Message.WHICH.Abort, r.which);
Assert.AreEqual("reason", r.Abort.Reason);
}
[TestMethod]
public void MessageAccept()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Accept;
Assert.AreEqual(Message.WHICH.Accept, w.which);
w.Accept.Embargo = true;
Assert.IsTrue(w.Accept.Embargo);
w.Accept.Provision.SetStruct(2, 0);
w.Accept.Provision.WriteData(0, long.MinValue);
w.Accept.Provision.WriteData(64, long.MaxValue);
Assert.AreEqual(long.MinValue, w.Accept.Provision.ReadDataLong(0));
Assert.AreEqual(long.MaxValue, w.Accept.Provision.ReadDataLong(64));
w.Accept.QuestionId = 0x87654321u;
Assert.AreEqual(0x87654321u, w.Accept.QuestionId);
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
Assert.AreEqual(Message.WHICH.Accept, r.which);
Assert.IsTrue(r.Accept.Embargo);
Assert.AreEqual(ObjectKind.Struct, r.Accept.Provision.Kind);
Assert.AreEqual(long.MinValue, r.Accept.Provision.ReadDataLong(0));
Assert.AreEqual(long.MaxValue, r.Accept.Provision.ReadDataLong(64));
Assert.AreEqual(0x87654321u, r.Accept.QuestionId);
}
[TestMethod]
public void MessageBootstrap()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Bootstrap;
Assert.AreEqual(Message.WHICH.Bootstrap, w.which);
w.Bootstrap.QuestionId = 0xaa55aa55u;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
Assert.AreEqual(Message.WHICH.Bootstrap, r.which);
Assert.AreEqual(0xaa55aa55u, r.Bootstrap.QuestionId);
}
[TestMethod]
public void MessageCall()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Call;
Assert.AreEqual(Message.WHICH.Call, w.which);
w.Call.AllowThirdPartyTailCall = true;
w.Call.InterfaceId = ulong.MaxValue;
w.Call.MethodId = 0x1111;
w.Call.Params.CapTable.Init(6);
w.Call.Params.CapTable[0].which = CapDescriptor.WHICH.None;
w.Call.Params.CapTable[1].which = CapDescriptor.WHICH.ReceiverAnswer;
w.Call.Params.CapTable[1].ReceiverAnswer.QuestionId = 0x12345678u;
w.Call.Params.CapTable[1].ReceiverAnswer.Transform.Init(2);
w.Call.Params.CapTable[1].ReceiverAnswer.Transform[0].which = PromisedAnswer.Op.WHICH.GetPointerField;
w.Call.Params.CapTable[1].ReceiverAnswer.Transform[0].GetPointerField = 0x2222;
w.Call.Params.CapTable[1].ReceiverAnswer.Transform[1].which = PromisedAnswer.Op.WHICH.Noop;
w.Call.Params.CapTable[2].which = CapDescriptor.WHICH.ReceiverHosted;
w.Call.Params.CapTable[2].ReceiverHosted = 12345678u;
w.Call.Params.CapTable[3].which = CapDescriptor.WHICH.SenderHosted;
w.Call.Params.CapTable[3].SenderHosted = 23456789u;
w.Call.Params.CapTable[4].which = CapDescriptor.WHICH.SenderPromise;
w.Call.Params.CapTable[4].SenderPromise = 34567890u;
w.Call.Params.CapTable[5].which = CapDescriptor.WHICH.ThirdPartyHosted;
w.Call.Params.CapTable[5].ThirdPartyHosted.Id.SetStruct(1, 0);
w.Call.Params.CapTable[5].ThirdPartyHosted.Id.WriteData(0, double.Epsilon);
w.Call.Params.CapTable[5].ThirdPartyHosted.VineId = 111111u;
Assert.AreEqual(CapDescriptor.WHICH.None, w.Call.Params.CapTable[0].which);
Assert.AreEqual(CapDescriptor.WHICH.ReceiverAnswer, w.Call.Params.CapTable[1].which);
Assert.AreEqual(CapDescriptor.WHICH.ReceiverHosted, w.Call.Params.CapTable[2].which);
Assert.AreEqual(CapDescriptor.WHICH.SenderHosted, w.Call.Params.CapTable[3].which);
Assert.AreEqual(CapDescriptor.WHICH.SenderPromise, w.Call.Params.CapTable[4].which);
Assert.AreEqual(CapDescriptor.WHICH.ThirdPartyHosted, w.Call.Params.CapTable[5].which);
var content = w.Call.Params.Content.Rewrap<DynamicSerializerState>();
content.SetStruct(1, 0);
content.WriteData(0, double.PositiveInfinity);
w.Call.QuestionId = 0x77777777u;
w.Call.SendResultsTo.which = Call.sendResultsTo.WHICH.ThirdParty;
w.Call.SendResultsTo.ThirdParty.SetStruct(1, 0);
w.Call.SendResultsTo.ThirdParty.WriteData(0, double.NegativeInfinity);
w.Call.Target.which = MessageTarget.WHICH.PromisedAnswer;
w.Call.Target.PromisedAnswer.QuestionId = 5555555u;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
Assert.AreEqual(Message.WHICH.Call, r.which);
Assert.IsTrue(r.Call.AllowThirdPartyTailCall);
Assert.AreEqual(ulong.MaxValue, r.Call.InterfaceId);
Assert.AreEqual((ushort)0x1111, r.Call.MethodId);
var capTable = r.Call.Params.CapTable;
Assert.AreEqual(6, capTable.Count);
Assert.AreEqual(CapDescriptor.WHICH.None, capTable[0].which);
Assert.AreEqual(CapDescriptor.WHICH.ReceiverAnswer, capTable[1].which);
Assert.AreEqual(0x12345678u, capTable[1].ReceiverAnswer.QuestionId);
var transform = capTable[1].ReceiverAnswer.Transform;
Assert.AreEqual(2, transform.Count);
Assert.AreEqual(PromisedAnswer.Op.WHICH.GetPointerField, transform[0].which);
Assert.AreEqual((ushort)0x2222, transform[0].GetPointerField);
Assert.AreEqual(PromisedAnswer.Op.WHICH.Noop, transform[1].which);
Assert.AreEqual(CapDescriptor.WHICH.ReceiverHosted, capTable[2].which);
Assert.AreEqual(12345678u, capTable[2].ReceiverHosted);
Assert.AreEqual(CapDescriptor.WHICH.SenderHosted, capTable[3].which);
Assert.AreEqual(23456789u, capTable[3].SenderHosted);
Assert.AreEqual(CapDescriptor.WHICH.SenderPromise, capTable[4].which);
Assert.AreEqual(34567890u, capTable[4].SenderPromise);
Assert.AreEqual(CapDescriptor.WHICH.ThirdPartyHosted, capTable[5].which);
var tph = capTable[5].ThirdPartyHosted;
Assert.AreEqual(ObjectKind.Struct, tph.Id.Kind);
Assert.AreEqual(double.Epsilon, tph.Id.ReadDataDouble(0));
Assert.AreEqual(111111u, tph.VineId);
Assert.AreEqual(ObjectKind.Struct, r.Call.Params.Content.Kind);
Assert.AreEqual(double.PositiveInfinity, r.Call.Params.Content.ReadDataDouble(0));
Assert.AreEqual(0x77777777u, r.Call.QuestionId);
var srt = r.Call.SendResultsTo;
Assert.AreEqual(Call.sendResultsTo.WHICH.ThirdParty, srt.which);
Assert.AreEqual(ObjectKind.Struct, srt.ThirdParty.Kind);
Assert.AreEqual(double.NegativeInfinity, srt.ThirdParty.ReadDataDouble(0));
Assert.AreEqual(MessageTarget.WHICH.PromisedAnswer, r.Call.Target.which);
Assert.AreEqual(5555555u, r.Call.Target.PromisedAnswer.QuestionId);
}
[TestMethod]
public void MessageDisembargo()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Disembargo;
var ctx = w.Disembargo.Context;
ctx.which = Disembargo.context.WHICH.SenderLoopback;
ctx.SenderLoopback = 1234567u;
var tgt = w.Disembargo.Target;
tgt.which = MessageTarget.WHICH.PromisedAnswer;
tgt.PromisedAnswer.QuestionId = 7654321u;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Disembargo, r.which);
Assert.AreEqual(Disembargo.context.WHICH.SenderLoopback, r.Disembargo.Context.which);
Assert.AreEqual(1234567u, r.Disembargo.Context.SenderLoopback);
Assert.AreEqual(MessageTarget.WHICH.PromisedAnswer, r.Disembargo.Target.which);
Assert.AreEqual(7654321u, r.Disembargo.Target.PromisedAnswer.QuestionId);
}
}
[TestMethod]
public void MessageFinish()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Finish;
w.Finish.QuestionId = 0xaaaaaaaa;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Finish, r.which);
Assert.AreEqual(0xaaaaaaaa, r.Finish.QuestionId);
}
}
[TestMethod]
public void MessageJoin()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Join;
w.Join.KeyPart.SetStruct(2, 0);
w.Join.KeyPart.WriteData(0, long.MinValue);
w.Join.KeyPart.WriteData(64, long.MaxValue);
w.Join.QuestionId = 0x88888888;
w.Join.Target.which = MessageTarget.WHICH.ImportedCap;
w.Join.Target.ImportedCap = 0x99999999;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Join, r.which);
Assert.AreEqual(ObjectKind.Struct, r.Join.KeyPart.Kind);
Assert.AreEqual(long.MinValue, r.Join.KeyPart.ReadDataLong(0));
Assert.AreEqual(long.MaxValue, r.Join.KeyPart.ReadDataLong(64));
Assert.AreEqual(0x88888888, r.Join.QuestionId);
Assert.AreEqual(MessageTarget.WHICH.ImportedCap, r.Join.Target.which);
Assert.AreEqual(0x99999999, r.Join.Target.ImportedCap);
}
}
[TestMethod]
public void MessageProvide()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Provide;
w.Provide.QuestionId = 0xbbbbbbbb;
w.Provide.Recipient.SetStruct(1, 0);
w.Provide.Recipient.WriteData(0, -1);
w.Provide.Target.which = MessageTarget.WHICH.PromisedAnswer;
w.Provide.Target.PromisedAnswer.QuestionId = 0xcccccccc;
w.Provide.Target.PromisedAnswer.Transform.Init(1);
w.Provide.Target.PromisedAnswer.Transform[0].which = PromisedAnswer.Op.WHICH.Noop;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Provide, r.which);
Assert.AreEqual(0xbbbbbbbb, r.Provide.QuestionId);
Assert.AreEqual(-1, r.Provide.Recipient.ReadDataInt(0));
Assert.AreEqual(MessageTarget.WHICH.PromisedAnswer, r.Provide.Target.which);
Assert.AreEqual(0xcccccccc, r.Provide.Target.PromisedAnswer.QuestionId);
Assert.AreEqual(1, r.Provide.Target.PromisedAnswer.Transform.Count);
Assert.AreEqual(PromisedAnswer.Op.WHICH.Noop, r.Provide.Target.PromisedAnswer.Transform[0].which);
}
}
[TestMethod]
public void MessageRelease()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Release;
w.Release.Id = 0xdddddddd;
w.Release.ReferenceCount = 27;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Release, r.which);
Assert.AreEqual(0xdddddddd, r.Release.Id);
Assert.AreEqual(27u, r.Release.ReferenceCount);
}
}
[TestMethod]
public void MessageResolve()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Resolve;
w.Resolve.which = Resolve.WHICH.Cap;
w.Resolve.Cap.which = CapDescriptor.WHICH.SenderHosted;
w.Resolve.Cap.SenderHosted = 0xeeeeeeee;
w.Resolve.PromiseId = 0x11111111;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Resolve, r.which);
Assert.AreEqual(CapDescriptor.WHICH.SenderHosted, r.Resolve.Cap.which);
Assert.AreEqual(0xeeeeeeee, r.Resolve.Cap.SenderHosted);
Assert.AreEqual(0x11111111u, r.Resolve.PromiseId);
}
}
[TestMethod]
public void MessageReturn()
{
var mb = MessageBuilder.Create();
{
var w = mb.BuildRoot<Message.WRITER>();
w.which = Message.WHICH.Return;
w.Return.which = Return.WHICH.Results;
w.Return.Results.CapTable.Init(1);
w.Return.Results.CapTable[0].which = CapDescriptor.WHICH.SenderHosted;
w.Return.Results.CapTable[0].SenderHosted = 0x22222222;
var content = w.Return.Results.Content.Rewrap<DynamicSerializerState>();
content.SetStruct(2, 0);
content.WriteData(0, double.MinValue);
content.WriteData(64, double.MaxValue);
Assert.IsTrue(w.Return.ReleaseParamCaps);
w.Return.ReleaseParamCaps = false;
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Return, r.which);
Assert.AreEqual(Return.WHICH.Results, r.Return.which);
Assert.AreEqual(1, r.Return.Results.CapTable.Count);
Assert.AreEqual(CapDescriptor.WHICH.SenderHosted, r.Return.Results.CapTable[0].which);
Assert.AreEqual(0x22222222u, r.Return.Results.CapTable[0].SenderHosted);
Assert.AreEqual(double.MinValue, r.Return.Results.Content.ReadDataDouble(0));
Assert.AreEqual(double.MaxValue,
r.Return.Results.Content.ReadDataDouble(64));
Assert.IsFalse(r.Return.ReleaseParamCaps);
}
}
[TestMethod]
public void MessageUnimplemented()
{
var mb = MessageBuilder.Create();
{
var u = mb.BuildRoot<Message.WRITER>();
u.which = Message.WHICH.Unimplemented;
var w = u.Unimplemented;
w.which = Message.WHICH.Resolve;
w.Resolve.which = Resolve.WHICH.Exception;
w.Resolve.Exception.Reason = "reason";
}
var r = Message.READER.create(DeserializerState.CreateRoot(mb.Frame));
{
Assert.AreEqual(Message.WHICH.Unimplemented, r.which);
Assert.AreEqual(Message.WHICH.Resolve, r.Unimplemented.which);
Assert.AreEqual(Resolve.WHICH.Exception, r.Unimplemented.Resolve.which);
Assert.AreEqual("reason", r.Unimplemented.Resolve.Exception.Reason);
}
}
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class SegmentAllocatorTests
{
[TestMethod]
public void BasicSegmentAllocator()
{
var alloc = new SegmentAllocator(128);
Assert.IsTrue(alloc.Allocate(1, 0, out var slice1, false));
Assert.AreEqual(0u, slice1.SegmentIndex);
Assert.AreEqual(0, slice1.Offset);
Assert.IsTrue(alloc.Allocate(1, 1, out var slice2, false));
Assert.AreEqual(0u, slice2.SegmentIndex);
Assert.AreEqual(1, slice2.Offset);
Assert.IsTrue(alloc.Allocate(127, 0, out var slice3, false));
Assert.AreEqual(1u, slice3.SegmentIndex);
Assert.AreEqual(0, slice3.Offset);
Assert.IsFalse(alloc.Allocate(127, 0, out var slice4, true));
Assert.IsFalse(alloc.Allocate(127, 1, out var slice5, true));
Assert.IsTrue(alloc.Allocate(2, 0, out var slice6, true));
Assert.AreEqual(0u, slice6.SegmentIndex);
Assert.AreEqual(2, slice6.Offset);
Assert.IsTrue(alloc.Allocate(1, 1, out var slice7, true));
Assert.AreEqual(1u, slice7.SegmentIndex);
Assert.AreEqual(127, slice7.Offset);
Assert.IsTrue(alloc.Allocate(129, 0, out var slice8, false));
Assert.AreEqual(2u, slice8.SegmentIndex);
Assert.AreEqual(0, slice8.Offset);
}
}
}

View File

@ -0,0 +1,697 @@
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Extensions.Log­ging;
using System.Diagnostics;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class TcpRpc
{
public static int TcpPort = 33444;
(TcpRpcServer, TcpRpcClient) SetupClientServerPair()
{
var server = new TcpRpcServer(IPAddress.Any, TcpPort);
var client = new TcpRpcClient("localhost", TcpPort);
return (server, client);
}
bool ExpectingLogOutput { get; set; }
[TestInitialize]
public void InitConsoleLogging()
{
ExpectingLogOutput = true;
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) =>
{
if (!ExpectingLogOutput && level != LogLevel.Debug)
{
Assert.Fail("Did not expect any logging output, but got this: " + msg);
}
return true;
});
}
int MediumTimeout => Debugger.IsAttached ? Timeout.Infinite : 2000;
[TestMethod]
public void CreateAndDispose()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
}
}
[TestMethod]
public void ConnectAndDispose()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
}
}
[TestMethod]
public void ConnectNoServer()
{
using (var client = new TcpRpcClient("localhost", TcpPort))
{
Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => client.WhenConnected).Wait(10000));
}
}
[TestMethod]
public void ConnectAndBootstrap()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
server.Main = new ProvidedCapabilityMock();
var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumTimeout));
}
}
[TestMethod]
public void ConnectNoBootstrap()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability;
Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => resolving.WhenResolved).Wait(MediumTimeout));
}
}
[TestMethod]
public void CallReturn()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, false))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 0);
result.WriteData(0, 654321);
mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
var outresult = answer.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult.Kind);
Assert.AreEqual(654321, outresult.ReadDataInt(0));
}
}
}
[TestMethod]
public void CallCancelOnServer()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, false))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
mock.Return.SetCanceled();
Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>(() => answer.WhenReturned).Wait(MediumTimeout));
}
}
}
[TestMethod]
public void CallCancelOnClient()
{
ExpectingLogOutput = false;
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
try
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
CancellationToken ctx;
using (var answer = main.Call(0x1234567812345678, 0x3333, args, false))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
ctx = ct;
}
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout));
}
finally
{
ExpectingLogOutput = true;
}
}
}
[TestMethod]
public void CallReturnAfterClientSideCancel()
{
ExpectingLogOutput = false;
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
try
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
CancellationToken ctx;
IPromisedAnswer answer;
using (answer = main.Call(0x1234567812345678, 0x3333, args, false))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
ctx = ct;
}
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout));
var mbr = MessageBuilder.Create();
mbr.InitCapTable();
var result = new DynamicSerializerState(mbr);
result.SetStruct(1, 0);
result.WriteData(0, 654321);
mock.Return.SetResult(result);
// Even after the client cancelled the call, the server must still send
// a response.
Assert.IsTrue(answer.WhenReturned.ContinueWith(t => { }).Wait(MediumTimeout));
}
finally
{
ExpectingLogOutput = true;
}
}
}
class MyTestException: System.Exception
{
public MyTestException(): base("Test exception")
{
}
}
[TestMethod]
public void CallServerSideException()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, false))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
mock.Return.SetException(new MyTestException());
var exTask = Assert.ThrowsExceptionAsync<RpcException>(() => answer.WhenReturned);
Assert.IsTrue(exTask.Wait(MediumTimeout));
Assert.IsTrue(exTask.Result.Message.Contains(new MyTestException().Message));
}
}
}
[TestMethod]
public void PipelineBeforeReturn()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, true))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(
new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
var args2 = DynamicSerializerState.CreateForRpc();
args2.SetStruct(1, 0);
args2.WriteData(0, 654321);
using (var answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2, false))
{
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
var mock2 = new ProvidedCapabilityMock();
var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2);
result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2);
result.LinkToCapability(1, id);
mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
Assert.IsFalse(ct.IsCancellationRequested);
Assert.IsTrue(mock2.WhenCalled.Wait(MediumTimeout));
(var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result;
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
Assert.AreEqual<ushort>(0x4444, methodId2);
Assert.AreEqual(ObjectKind.Struct, inargs2.Kind);
Assert.AreEqual(654321, inargs2.ReadDataInt(0));
var result2 = DynamicSerializerState.CreateForRpc();
result2.SetStruct(1, 0);
result2.WriteData(0, 222222);
mock2.Return.SetResult(result2);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
var outresult2 = answer2.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
Assert.AreEqual(222222, outresult2.ReadDataInt(0));
}
}
}
}
[TestMethod]
public void PipelineAfterReturn()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, true))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
var mock2 = new ProvidedCapabilityMock();
var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2);
result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2);
result.LinkToCapability(1, id);
mock.Return.SetResult(result);
using (var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(
answer.Access(
new MemberAccessPath(
new MemberAccessPath.MemberAccess[] {
new MemberAccessPath.StructMemberAccess(1) }))))
{
var args2 = DynamicSerializerState.CreateForRpc();
args2.SetStruct(1, 0);
args2.WriteData(0, 654321);
using (var answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2, false))
{
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
Assert.IsTrue(mock2.WhenCalled.Wait(MediumTimeout));
(var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result;
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
Assert.AreEqual<ushort>(0x4444, methodId2);
Assert.AreEqual(ObjectKind.Struct, inargs2.Kind);
Assert.AreEqual(654321, inargs2.ReadDataInt(0));
var result2 = DynamicSerializerState.CreateForRpc();
result2.SetStruct(1, 0);
result2.WriteData(0, 222222);
mock2.Return.SetResult(result2);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
var outresult2 = answer2.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
Assert.AreEqual(222222, outresult2.ReadDataInt(0));
}
}
}
}
}
[TestMethod]
public void PipelineMultiple()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args, true))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
var args2 = DynamicSerializerState.CreateForRpc();
args2.SetStruct(1, 0);
args2.WriteData(0, 111111);
var args3 = DynamicSerializerState.CreateForRpc();
args3.SetStruct(1, 0);
args3.WriteData(0, 222222);
using (var answer2 = pipelined.Call(0x1111111111111111, 0x1111, args2, false))
using (var answer3 = pipelined.Call(0x2222222222222222, 0x2222, args3, false))
{
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId);
Assert.AreEqual(ObjectKind.Struct, inargs.Kind);
Assert.AreEqual(123456, inargs.ReadDataInt(0));
var mock2 = new ProvidedCapabilityMultiCallMock();
var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2);
result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2);
result.LinkToCapability(1, id);
mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
Assert.IsFalse(ct.IsCancellationRequested);
var args4 = DynamicSerializerState.CreateForRpc();
args4.SetStruct(1, 0);
args4.WriteData(0, 333333);
var args5 = DynamicSerializerState.CreateForRpc();
args5.SetStruct(1, 0);
args5.WriteData(0, 444444);
using (var answer4 = pipelined.Call(0x3333333333333333, 0x3333, args4, false))
using (var answer5 = pipelined.Call(0x4444444444444444, 0x4444, args5, false))
{
var call2 = mock2.WhenCalled;
var call3 = mock2.WhenCalled;
var call4 = mock2.WhenCalled;
var call5 = mock2.WhenCalled;
Assert.IsTrue(call2.Wait(MediumTimeout));
Assert.IsTrue(call3.Wait(MediumTimeout));
Assert.IsTrue(call4.Wait(MediumTimeout));
Assert.IsTrue(call5.Wait(MediumTimeout));
Assert.AreEqual<ulong>(0x1111111111111111, call2.Result.InterfaceId);
Assert.AreEqual<ulong>(0x2222222222222222, call3.Result.InterfaceId);
Assert.AreEqual<ulong>(0x3333333333333333, call4.Result.InterfaceId);
Assert.AreEqual<ulong>(0x4444444444444444, call5.Result.InterfaceId);
var ret2 = DynamicSerializerState.CreateForRpc();
ret2.SetStruct(1, 0);
ret2.WriteData(0, -1);
call2.Result.Result.SetResult(ret2);
var ret3 = DynamicSerializerState.CreateForRpc();
ret3.SetStruct(1, 0);
ret3.WriteData(0, -2);
call3.Result.Result.SetResult(ret3);
var ret4 = DynamicSerializerState.CreateForRpc();
ret4.SetStruct(1, 0);
ret4.WriteData(0, -3);
call4.Result.Result.SetResult(ret4);
var ret5 = DynamicSerializerState.CreateForRpc();
ret5.SetStruct(1, 0);
ret5.WriteData(0, -4);
call5.Result.Result.SetResult(ret5);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
Assert.IsTrue(answer3.WhenReturned.Wait(MediumTimeout));
Assert.IsTrue(answer4.WhenReturned.Wait(MediumTimeout));
Assert.IsTrue(answer5.WhenReturned.Wait(MediumTimeout));
Assert.AreEqual(-1, answer2.WhenReturned.Result.ReadDataInt(0));
Assert.AreEqual(-2, answer3.WhenReturned.Result.ReadDataInt(0));
Assert.AreEqual(-3, answer4.WhenReturned.Result.ReadDataInt(0));
Assert.AreEqual(-4, answer5.WhenReturned.Result.ReadDataInt(0));
}
}
}
}
}
[TestMethod]
public void PipelineCallAfterDisposal()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
BareProxy pipelined;
using (var answer = main.Call(0x1234567812345678, 0x3333, args, true))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(
answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
}
var args2 = DynamicSerializerState.CreateForRpc();
args2.SetStruct(1, 0);
args2.WriteData(0, 654321);
Assert.ThrowsException<System.ObjectDisposedException>(() => pipelined.Call(0x8765432187654321, 0x4444, args2, false));
}
}
[TestMethod]
public void PipelineCallDuringDisposal()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock();
server.Main = mock;
var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0);
args.WriteData(0, 123456);
IPromisedAnswer answer2;
using (var answer = main.Call(0x1234567812345678, 0x3333, args, true))
{
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
var args2 = DynamicSerializerState.CreateForRpc();
args2.SetStruct(1, 0);
args2.WriteData(0, 654321);
answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2, false);
}
using (answer2)
{
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
var tcs = new TaskCompletionSource<int>();
using (ct.Register(() => tcs.SetResult(0)))
{
Assert.IsTrue(tcs.Task.Wait(MediumTimeout));
}
var mock2 = new ProvidedCapabilityMock();
var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2);
result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2);
result.LinkToCapability(1, id);
mock.Return.SetResult(result);
Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>(
() => answer2.WhenReturned).Wait(MediumTimeout));
}
}
}
}
}

View File

@ -0,0 +1,92 @@
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Rpc;
using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class TcpRpcAdvancedStuff: TestBase
{
[TestMethod]
public void MultiConnect()
{
using (var server = SetupServer())
{
var counters = new Counters();
var tcs = new TaskCompletionSource<int>();
server.Main = new TestInterfaceImpl(counters, tcs);
for (int i = 1; i <= 10; i++)
{
using (var client = SetupClient())
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestInterface>())
{
var request1 = main.Foo(123, true, default);
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
var s = new TestAllTypes();
Common.InitTestMessage(s);
var request2 = main.Baz(s, default);
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", request1.Result);
Assert.AreEqual(2 * i, counters.CallCount);
}
}
// Bootstrap capability must not be disposed
Assert.IsFalse(tcs.Task.IsCompleted);
}
}
}
[TestMethod]
public void TwoClients()
{
using (var server = SetupServer())
{
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var client1 = SetupClient())
using (var client2 = SetupClient())
{
Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout));
Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client1.GetMain<ITestMoreStuff>())
{
Assert.IsTrue(main.Hold(new TestInterfaceImpl(counters)).Wait(MediumNonDbgTimeout));
}
using (var main = client2.GetMain<ITestMoreStuff>())
{
Assert.IsTrue(main.CallHeld().Wait(MediumNonDbgTimeout));
var getHeld = main.GetHeld();
Assert.IsTrue(getHeld.Wait(MediumNonDbgTimeout));
var foo = getHeld.Result.Foo(123, true);
Assert.IsTrue(foo.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", foo.Result);
}
client1.Dispose();
using (var main = client2.GetMain<ITestMoreStuff>())
{
ExpectPromiseThrows(main.CallHeld());
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,622 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnproto_test.Capnp.Test;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class TcpRpcPorted: TestBase
{
[TestMethod]
public void Basic()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestInterfaceImpl(counters);
using (var main = client.GetMain<ITestInterface>())
{
var request1 = main.Foo(123, true, default);
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
var s = new TestAllTypes();
Common.InitTestMessage(s);
var request2 = main.Baz(s, default);
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", request1.Result);
Assert.AreEqual(2, counters.CallCount);
}
}
}
[TestMethod]
public void Pipeline()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestPipelineImpl(counters);
using (var main = client.GetMain<ITestPipeline>())
{
var chainedCallCount = new Counters();
var request = main.GetCap(234, new TestInterfaceImpl(chainedCallCount), default);
using (var outBox = request.OutBox_Cap())
{
var pipelineRequest = outBox.Foo(321, false, default);
var pipelineRequest2 = ((Proxy)outBox).Cast<ITestExtends>(false).Grault(default);
Assert.IsTrue(pipelineRequest.Wait(MediumNonDbgTimeout));
Assert.IsTrue(pipelineRequest2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", pipelineRequest.Result);
Common.CheckTestMessage(pipelineRequest2.Result);
Assert.AreEqual(3, counters.CallCount);
Assert.AreEqual(1, chainedCallCount.CallCount);
}
}
}
}
[TestMethod]
public void Release()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>())
{
var task1 = main.GetHandle(default);
var task2 = main.GetHandle(default);
Assert.IsTrue(task1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(task2.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2, counters.HandleCount);
task1.Result.Dispose();
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, MediumNonDbgTimeout));
task2.Result.Dispose();
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, MediumNonDbgTimeout));
}
}
}
[TestMethod]
public void ReleaseOnCancel()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>())
{
((Proxy)main).WhenResolved.Wait(MediumNonDbgTimeout);
// Since we have a threaded model, there is no way to deterministically provoke the situation
// where Cancel and Finish message cross paths. Instead, we'll do a lot of such requests and
// later on verify that the handle count is 0.
for (int i = 0; i < 1000; i++)
{
var cts = new CancellationTokenSource();
var task = main.GetHandle(cts.Token);
cts.Cancel();
task.ContinueWith(t =>
{
t.Result.Dispose();
cts.Dispose();
});
}
Thread.Sleep(ShortTimeout);
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, MediumNonDbgTimeout));
}
}
}
[TestMethod]
public void TestTailCall()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestTailCallerImpl(counters);
using (var main = client.GetMain<ITestTailCaller>())
{
var calleeCallCount = new Counters();
var callee = new TestTailCalleeImpl(calleeCallCount);
var promise = main.Foo(456, callee, default);
var dependentCall0 = promise.C().GetCallSequence(0, default);
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
Assert.AreEqual(456u, promise.Result.I);
Assert.AreEqual("from TestTailCaller", promise.Result.T);
var dependentCall1 = promise.C().GetCallSequence(0, default);
var dependentCall2 = promise.C().GetCallSequence(0, default);
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1, counters.CallCount);
Assert.AreEqual(1, calleeCallCount.CallCount);
}
}
}
[TestMethod]
public void Cancelation()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>())
{
var destroyed = new TaskCompletionSource<int>();
var impl = new TestInterfaceImpl(counters, destroyed);
var cts = new CancellationTokenSource();
var cancelTask = main.ExpectCancel(impl, cts.Token);
Assert.IsFalse(SpinWait.SpinUntil(() => destroyed.Task.IsCompleted || cancelTask.IsCompleted, ShortTimeout));
cts.Cancel();
Assert.IsTrue(destroyed.Task.Wait(MediumNonDbgTimeout));
Assert.IsFalse(cancelTask.IsCompletedSuccessfully);
}
}
}
[TestMethod]
public void PromiseResolve()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var tcs = new TaskCompletionSource<ITestInterface>();
var eager = tcs.Task.PseudoEager();
var request = main.CallFoo(eager, default);
var request2 = main.CallFooWhenResolved(eager, default);
var gcs = main.GetCallSequence(0, default);
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2u, gcs.Result);
Assert.AreEqual(3, counters.CallCount);
var chainedCallCount = new Counters();
var tiimpl = new TestInterfaceImpl(chainedCallCount);
tcs.SetResult(tiimpl);
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", request.Result);
Assert.AreEqual("bar", request2.Result);
Assert.AreEqual(3, counters.CallCount);
Assert.AreEqual(2, chainedCallCount.CallCount);
}
}
}
[TestMethod]
public void RetainAndRelease()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var holdTask = main.Hold(new TestInterfaceImpl(new Counters(), destructionPromise), default);
Assert.IsTrue(holdTask.Wait(MediumNonDbgTimeout));
var cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, cstask.Result);
Assert.IsFalse(destructionTask.IsCompleted);
var htask = main.CallHeld(default);
Assert.IsTrue(htask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", htask.Result);
var gtask = main.GetHeld(default);
Assert.IsTrue(gtask.Wait(MediumNonDbgTimeout));
// We can get the cap back from it.
using (var cap = gtask.Result)
{
// Wait for balanced state
WaitClientServerIdle(server, client);
// And call it, without any network communications.
long oldSendCount = client.SendCount;
var ftask = cap.Foo(123, true, default);
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", ftask.Result);
Assert.AreEqual(oldSendCount, client.SendCount);
// We can send another copy of the same cap to another method, and it works.
var ctask = main.CallFoo(cap, default);
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ctask.Result);
// Give some time to settle.
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(5u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(6u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(7u, cstask.Result);
// Can't be destroyed, we haven't released it.
Assert.IsFalse(destructionTask.IsCompleted);
}
// In deviation from original test, we have null the held capability on the main interface.
// This is because the main interface is the bootstrap capability and, as such, won't be disposed
// after disconnect.
var holdNullTask = main.Hold(null, default);
Assert.IsTrue(holdNullTask.Wait(MediumNonDbgTimeout));
}
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
}
[TestMethod]
public void Cancel()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
using (var cts = new CancellationTokenSource())
{
var ntask = main.NeverReturn(new TestInterfaceImpl(counters, destructionPromise), cts.Token);
// Allow some time to settle.
var cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2u, cstask.Result);
// The cap shouldn't have been destroyed yet because the call never returned.
Assert.IsFalse(destructionTask.IsCompleted);
// There will be no automatic cancellation just because "ntask" goes of of scope or
// because the Proxy is disposed. Even ntask.Dispose() would not cancel the request.
// In .NET this needs to be done explicitly.
cts.Cancel();
}
// Now the cap should be released.
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
}
[TestMethod]
public void SendTwice()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var cap = new TestInterfaceImpl(new Counters(), destructionPromise);
Task<string> ftask1, ftask2;
using (Skeleton.Claim(cap))
{
var ftask = main.CallFoo(cap, default);
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask.Result);
var ctask = main.GetCallSequence(0, default);
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, ctask.Result);
ftask1 = main.CallFoo(cap, default);
ftask2 = main.CallFoo(cap, default);
}
Assert.IsTrue(ftask1.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask1.Result);
Assert.IsTrue(ftask2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask2.Result);
// Now the cap should be released.
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
}
}
[TestMethod]
public void Embargo()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var cap = new TestCallOrderImpl();
var earlyCall = main.GetCallSequence(0, default);
var echo = main.Echo(cap, default);
using (var pipeline = echo.Eager())
{
var call0 = pipeline.GetCallSequence(0, default);
var call1 = pipeline.GetCallSequence(1, default);
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
impl.EnableEcho();
var call2 = pipeline.GetCallSequence(2, default);
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
using (var resolved = echo.Result)
{
var call3 = pipeline.GetCallSequence(3, default);
var call4 = pipeline.GetCallSequence(4, default);
var call5 = pipeline.GetCallSequence(5, default);
Assert.IsTrue(call0.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
Assert.AreEqual(0u, call0.Result);
Assert.AreEqual(1u, call1.Result);
Assert.AreEqual(2u, call2.Result);
Assert.AreEqual(3u, call3.Result);
Assert.AreEqual(4u, call4.Result);
Assert.AreEqual(5u, call5.Result);
}
}
}
}
}
[TestMethod]
public void EmbargoError()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var cap = new TaskCompletionSource<ITestCallOrder>();
var earlyCall = main.GetCallSequence(0, default);
var echo = main.Echo(cap.Task.PseudoEager(), default);
var pipeline = echo.Eager();
var call0 = pipeline.GetCallSequence(0, default);
var call1 = pipeline.GetCallSequence(1, default);
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
impl.EnableEcho();
var call2 = pipeline.GetCallSequence(2, default);
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
var resolved = echo.Result;
var call3 = pipeline.GetCallSequence(3, default);
var call4 = pipeline.GetCallSequence(4, default);
var call5 = pipeline.GetCallSequence(5, default);
cap.SetException(new InvalidOperationException("I'm annoying"));
ExpectPromiseThrows(call0);
ExpectPromiseThrows(call1);
ExpectPromiseThrows(call2);
ExpectPromiseThrows(call3);
ExpectPromiseThrows(call4);
ExpectPromiseThrows(call5);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
}
[TestMethod]
public void EmbargoNull()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var promise = main.GetNull(default);
var cap = promise.Eager();
var call0 = cap.GetCallSequence(0, default);
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
var call1 = cap.GetCallSequence(1, default);
ExpectPromiseThrows(call0);
ExpectPromiseThrows(call1);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
}
[TestMethod]
public void CallBrokenPromise()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var tcs = new TaskCompletionSource<ITestInterface>();
var req = main.Hold(tcs.Task.PseudoEager(), default);
Assert.IsTrue(req.Wait(MediumNonDbgTimeout));
var req2 = main.CallHeld(default);
Assert.IsFalse(req2.Wait(ShortTimeout));
tcs.SetException(new InvalidOperationException("I'm a promise-breaker!"));
ExpectPromiseThrows(req2);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
}
}
}

View File

@ -0,0 +1,103 @@
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Rpc;
using Capnproto_test.Capnp.Test;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class TcpRpcStress: TestBase
{
ILogger Logger { get; set; }
void Repeat(int count, Action action)
{
for (int i = 0; i < count; i++)
{
Logger.LogTrace("Repetition {0}", i);
action();
}
}
[TestInitialize]
public void InitConsoleLogging()
{
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
Logger = Logging.CreateLogger<TcpRpcStress>();
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
}
[TestMethod]
public void ResolveMain()
{
Repeat(5000, () =>
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
}
}
});
}
[TestMethod]
public void Cancel()
{
var t = new TcpRpcPorted();
Repeat(1000, t.Cancel);
}
[TestMethod]
public void Embargo()
{
var t = new TcpRpcPorted();
Repeat(100, t.Embargo);
var t2 = new TcpRpcInterop();
Repeat(100, t2.EmbargoServer);
}
[TestMethod]
public void EmbargoNull()
{
// Some code paths are really rare during this test, therefore increased repetition count.
var t = new TcpRpcPorted();
Repeat(1000, t.EmbargoNull);
var t2 = new TcpRpcInterop();
Repeat(100, t2.EmbargoNullServer);
}
[TestMethod]
public void RetainAndRelease()
{
var t = new TcpRpcPorted();
Repeat(100, t.RetainAndRelease);
}
[TestMethod]
public void PipelineAfterReturn()
{
var t = new TcpRpc();
Repeat(100, t.PipelineAfterReturn);
}
}
}

View File

@ -0,0 +1,89 @@
using Capnp.Rpc;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests
{
public class TestBase
{
public static int TcpPort = 33444;
public static int MediumNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 5000;
public static int LargeNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 10000;
public static int ShortTimeout => 500;
protected ILogger Logger { get; set; }
protected TcpRpcClient SetupClient() => new TcpRpcClient("localhost", TcpPort);
protected TcpRpcServer SetupServer() => new TcpRpcServer(IPAddress.Any, TcpPort);
protected (TcpRpcServer, TcpRpcClient) SetupClientServerPair()
{
var server = SetupServer();
var client = SetupClient();
return (server, client);
}
[TestInitialize]
public void InitConsoleLogging()
{
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
Logger = Logging.CreateLogger<TcpRpcStress>();
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
}
/// <summary>
/// Somewhat ugly helper method which ensures that both Tcp client and server
/// are waiting for data reception from each other. This is a "balanced" state, meaning
/// that nothing will ever happen in the RcpEngines without some other thread requesting
/// anything.
/// </summary>
protected void WaitClientServerIdle(TcpRpcServer server, TcpRpcClient client)
{
var conn = server.Connections[0];
SpinWait.SpinUntil(() => conn.IsWaitingForData && client.IsWaitingForData &&
conn.RecvCount == client.SendCount &&
conn.SendCount == client.RecvCount,
MediumNonDbgTimeout);
}
protected void ExpectPromiseThrows(Task task)
{
async Task ExpectPromiseThrowsAsync(Task t)
{
try
{
await t;
Assert.Fail("Did not throw");
}
catch (InvalidOperationException)
{
// Happens if the call went to the resolution
// (thus, locally). In this case, the original
// exception is routed here.
}
catch (RpcException)
{
// Happens if the call went to the promise
// (thus, remotely). In this case, the original
// exception had to be serialized, so we receive
// the wrapped version.
}
catch (System.Exception exception)
{
Assert.Fail($"Got wrong kind of exception: {exception}");
}
}
Assert.IsTrue(ExpectPromiseThrowsAsync(task).Wait(MediumNonDbgTimeout));
}
}
}

View File

@ -0,0 +1,745 @@
using Capnp.Rpc;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Capnproto_test.Capnp.Test;
namespace Capnp.Net.Runtime.Tests.GenImpls
{
class Common
{
static byte[] Data(string s) => System.Text.Encoding.UTF8.GetBytes(s);
static bool DataSequenceEqual(IEnumerable<IReadOnlyList<byte>> seq1, IEnumerable<IReadOnlyList<byte>> seq2)
{
return seq1.Zip(seq2, (s1, s2) => s1.SequenceEqual(s2)).All(_ => _);
}
public static void InitTestMessage(TestAllTypes s)
{
s.BoolField = true;
s.Int8Field = -123;
s.Int16Field = -12345;
s.Int32Field = -12345678;
s.Int64Field = -123456789012345;
s.UInt8Field = 234;
s.UInt16Field = 45678;
s.UInt32Field = 3456789012;
s.UInt64Field = 12345678901234567890;
s.Float32Field = 1234.5f;
s.Float64Field = -123e45;
s.TextField = "foo";
s.DataField = Data("bar");
{
s.StructField = new TestAllTypes();
var sub = s.StructField;
sub.BoolField = true;
sub.Int8Field = -12;
sub.Int16Field = 3456;
sub.Int32Field = -78901234;
sub.Int64Field = 56789012345678;
sub.UInt8Field = 90;
sub.UInt16Field = 1234;
sub.UInt32Field = 56789012;
sub.UInt64Field = 345678901234567890;
sub.Float32Field = -1.25e-10f;
sub.Float64Field = 345;
sub.TextField = "baz";
sub.DataField = Data("qux");
{
sub.StructField = new TestAllTypes()
{
TextField = "nested",
StructField = new TestAllTypes()
{
TextField = "really nested"
}
};
}
sub.EnumField = TestEnum.baz;
sub.VoidList = 3;
sub.BoolList = new bool[] { false, true, false, true, true };
sub.Int8List = new sbyte[] { 12, -34, -0x80, 0x7f };
sub.Int16List = new short[] { 1234, -5678, -0x8000, 0x7fff };
sub.Int32List = new int[] { 12345678, -90123456, -0x7fffffff - 1, 0x7fffffff };
sub.Int64List = new long[] { 123456789012345, -678901234567890, -0x7fffffffffffffff - 1, 0x7fffffffffffffff };
sub.UInt8List = new byte[] { 12, 34, 0, 0xff };
sub.UInt16List = new ushort[] { 1234, 5678, 0, 0xffff };
sub.UInt32List = new uint[] { 12345678u, 90123456u, 0u, 0xffffffffu };
sub.UInt64List = new ulong[] { 123456789012345, 678901234567890, 0, 0xffffffffffffffff };
sub.Float32List = new float[] { 0, 1234567, 1e37f, -1e37f, 1e-37f, -1e-37f };
sub.Float64List = new double[] { 0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306 };
sub.TextList = new string[] { "quux", "corge", "grault" };
sub.DataList = new byte[][] { Data("garply"), Data("waldo"), Data("fred") };
sub.StructList = new TestAllTypes[]
{
new TestAllTypes() { TextField = "x structlist 1" },
new TestAllTypes() { TextField = "x structlist 2" },
new TestAllTypes() { TextField = "x structlist 3" }
};
sub.EnumList = new TestEnum[] { TestEnum.qux, TestEnum.bar, TestEnum.grault };
}
s.EnumField = TestEnum.corge;
s.VoidList = 6;
s.BoolList = new bool[] { true, false, false, true };
s.Int8List = new sbyte[] { 111, -111 };
s.Int16List = new short[] { 11111, -11111 };
s.Int32List = new int[] { 111111111, -111111111 };
s.Int64List = new long[] { 1111111111111111111, -1111111111111111111 };
s.UInt8List = new byte[] { 111, 222 };
s.UInt16List = new ushort[] { 33333, 44444 };
s.UInt32List = new uint[] { 3333333333 };
s.UInt64List = new ulong[] { 11111111111111111111 };
s.Float32List = new float[] { 5555.5f, float.PositiveInfinity, float.NegativeInfinity, float.NaN };
s.Float64List = new double[] { 7777.75, double.PositiveInfinity, double.NegativeInfinity, double.NaN };
s.TextList = new string[] { "plugh", "xyzzy", "thud" };
s.DataList = new byte[][] { Data("oops"), Data("exhausted"), Data("rfc3092") };
{
s.StructList = new TestAllTypes[]
{
new TestAllTypes() { TextField = "structlist 1" },
new TestAllTypes() { TextField = "structlist 2" },
new TestAllTypes() { TextField = "structlist 3" }
};
}
s.EnumList = new TestEnum[] { TestEnum.foo, TestEnum.garply };
}
public static void CheckTestMessage(TestAllTypes s)
{
var sub = s.StructField;
Assert.IsTrue(sub.BoolField);
Assert.AreEqual(-12, sub.Int8Field);
Assert.AreEqual(3456, sub.Int16Field);
Assert.AreEqual(-78901234, sub.Int32Field);
Assert.AreEqual(56789012345678, sub.Int64Field);
Assert.AreEqual(90, sub.UInt8Field);
Assert.AreEqual(1234, sub.UInt16Field);
Assert.AreEqual(56789012u, sub.UInt32Field);
Assert.AreEqual(345678901234567890ul, sub.UInt64Field);
Assert.AreEqual(-1.25e-10f, sub.Float32Field);
Assert.AreEqual(345.0, sub.Float64Field);
Assert.AreEqual("baz", sub.TextField);
Assert.IsTrue(Data("qux").SequenceEqual(sub.DataField));
{
var subsub = sub.StructField;
Assert.AreEqual("nested", subsub.TextField);
Assert.AreEqual("really nested", subsub.StructField.TextField);
}
Assert.AreEqual(TestEnum.baz, sub.EnumField);
Assert.AreEqual(3, sub.VoidList);
Assert.IsTrue(sub.BoolList.SequenceEqual(new bool[] { false, true, false, true, true }));
Assert.IsTrue(sub.Int8List.SequenceEqual(new sbyte[] { 12, -34, -0x80, 0x7f }));
Assert.IsTrue(sub.Int16List.SequenceEqual(new short[] { 1234, -5678, -0x8000, 0x7fff }));
Assert.IsTrue(sub.Int32List.SequenceEqual(new int[] { 12345678, -90123456, -0x7fffffff - 1, 0x7fffffff }));
Assert.IsTrue(sub.Int64List.SequenceEqual(new long[] { 123456789012345, -678901234567890, -0x7fffffffffffffff - 1, 0x7fffffffffffffff }));
Assert.IsTrue(sub.UInt8List.SequenceEqual(new byte[] { 12, 34, 0, 0xff }));
Assert.IsTrue(sub.UInt16List.SequenceEqual(new ushort[] { 1234, 5678, 0, 0xffff }));
Assert.IsTrue(sub.UInt32List.SequenceEqual(new uint[] { 12345678, 90123456, 0u, 0xffffffff }));
Assert.IsTrue(sub.UInt64List.SequenceEqual(new ulong[] { 123456789012345, 678901234567890, 0, 0xffffffffffffffff }));
Assert.IsTrue(sub.Float32List.SequenceEqual(new float[] { 0.0f, 1234567.0f, 1e37f, -1e37f, 1e-37f, -1e-37f }));
Assert.IsTrue(sub.Float64List.SequenceEqual(new double[] { 0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306 }));
Assert.IsTrue(sub.TextList.SequenceEqual(new string[] { "quux", "corge", "grault" }));
Assert.IsTrue(DataSequenceEqual(sub.DataList, new byte[][] { Data("garply"), Data("waldo"), Data("fred") }));
{
var list = sub.StructList;
Assert.AreEqual(3, list.Count);
Assert.AreEqual("x structlist 1", list[0].TextField);
Assert.AreEqual("x structlist 2", list[1].TextField);
Assert.AreEqual("x structlist 3", list[2].TextField);
}
Assert.IsTrue(sub.EnumList.SequenceEqual(new TestEnum[] { TestEnum.qux, TestEnum.bar, TestEnum.grault }));
Assert.AreEqual(TestEnum.corge, s.EnumField);
Assert.AreEqual(6, s.VoidList);
Assert.IsTrue(s.BoolList.SequenceEqual(new bool[] { true, false, false, true }));
Assert.IsTrue(s.Int8List.SequenceEqual(new sbyte[] { 111, -111 }));
Assert.IsTrue(s.Int16List.SequenceEqual(new short[] { 11111, -11111 }));
Assert.IsTrue(s.Int32List.SequenceEqual(new int[] { 111111111, -111111111 }));
Assert.IsTrue(s.Int64List.SequenceEqual(new long[] { 1111111111111111111, -1111111111111111111 }));
Assert.IsTrue(s.UInt8List.SequenceEqual(new byte[] { 111, 222 }));
Assert.IsTrue(s.UInt16List.SequenceEqual(new ushort[] { 33333, 44444 }));
Assert.IsTrue(s.UInt32List.SequenceEqual(new uint[] { 3333333333 }));
Assert.IsTrue(s.UInt64List.SequenceEqual(new ulong[] { 11111111111111111111 }));
{
var list = s.Float32List;
Assert.AreEqual(4, list.Count);
Assert.AreEqual(5555.5f, list[0]);
Assert.AreEqual(float.PositiveInfinity, list[1]);
Assert.AreEqual(float.NegativeInfinity, list[2]);
Assert.IsTrue(float.IsNaN(list[3]));
}
{
var list = s.Float64List;
Assert.AreEqual(4, list.Count);
Assert.AreEqual(7777.75, list[0]);
Assert.IsTrue(double.IsPositiveInfinity(list[1]));
Assert.IsTrue(double.IsNegativeInfinity(list[2]));
Assert.IsTrue(double.IsNaN(list[3]));
}
Assert.IsTrue(s.TextList.SequenceEqual(new string[] { "plugh", "xyzzy", "thud" }));
Assert.IsTrue(DataSequenceEqual(s.DataList, new byte[][] { Data("oops"), Data("exhausted"), Data("rfc3092") }));
{
var list = s.StructList;
Assert.AreEqual(3, list.Count);
Assert.AreEqual("structlist 1", list[0].TextField);
Assert.AreEqual("structlist 2", list[1].TextField);
Assert.AreEqual("structlist 3", list[2].TextField);
}
Assert.IsTrue(s.EnumList.SequenceEqual(new TestEnum[] { TestEnum.foo, TestEnum.garply }));
}
public static void CheckTestMessageAllZero(TestAllTypes s)
{
Assert.IsFalse(s.BoolField);
Assert.AreEqual(0, s.Int8Field);
Assert.AreEqual(0, s.Int16Field);
Assert.AreEqual(0, s.Int32Field);
Assert.AreEqual(0, s.Int64Field);
Assert.AreEqual(0, s.UInt8Field);
Assert.AreEqual(0, s.UInt16Field);
Assert.AreEqual(0, s.UInt32Field);
Assert.AreEqual(0, s.UInt64Field);
Assert.AreEqual(0f, s.Float32Field);
Assert.AreEqual(0.0, s.Float64Field);
Assert.AreEqual(string.Empty, s.TextField);
Assert.IsTrue(Data(string.Empty).SequenceEqual(s.DataField));
{
var sub = s.StructField;
Assert.IsFalse(sub.BoolField);
Assert.AreEqual(0, sub.Int8Field);
Assert.AreEqual(0, sub.Int16Field);
Assert.AreEqual(0, sub.Int32Field);
Assert.AreEqual(0, sub.Int64Field);
Assert.AreEqual(0, sub.UInt8Field);
Assert.AreEqual(0, sub.UInt16Field);
Assert.AreEqual(0, sub.UInt32Field);
Assert.AreEqual(0, sub.UInt64Field);
Assert.AreEqual(0f, sub.Float32Field);
Assert.AreEqual(0.0, sub.Float64Field);
Assert.AreEqual(string.Empty, sub.TextField);
Assert.AreEqual(Data(string.Empty), sub.DataField);
{
var subsub = sub.StructField;
Assert.AreEqual(string.Empty, subsub.TextField);
Assert.AreEqual(string.Empty, subsub.StructField.TextField);
}
Assert.AreEqual(0, sub.VoidList);
Assert.AreEqual(0, sub.BoolList.Count);
Assert.AreEqual(0, sub.Int8List.Count);
Assert.AreEqual(0, sub.Int16List.Count);
Assert.AreEqual(0, sub.Int32List.Count);
Assert.AreEqual(0, sub.Int64List.Count);
Assert.AreEqual(0, sub.UInt8List.Count);
Assert.AreEqual(0, sub.UInt16List.Count);
Assert.AreEqual(0, sub.UInt32List.Count);
Assert.AreEqual(0, sub.UInt64List.Count);
Assert.AreEqual(0, sub.Float32List.Count);
Assert.AreEqual(0, sub.Float64List.Count);
Assert.AreEqual(0, sub.TextList.Count);
Assert.AreEqual(0, sub.DataList.Count);
Assert.AreEqual(0, sub.StructList.Count);
}
Assert.AreEqual(0, s.VoidList);
Assert.AreEqual(0, s.BoolList.Count);
Assert.AreEqual(0, s.Int8List.Count);
Assert.AreEqual(0, s.Int16List.Count);
Assert.AreEqual(0, s.Int32List.Count);
Assert.AreEqual(0, s.Int64List.Count);
Assert.AreEqual(0, s.UInt8List.Count);
Assert.AreEqual(0, s.UInt16List.Count);
Assert.AreEqual(0, s.UInt32List.Count);
Assert.AreEqual(0, s.UInt64List.Count);
Assert.AreEqual(0, s.Float32List.Count);
Assert.AreEqual(0, s.Float64List.Count);
Assert.AreEqual(0, s.TextList.Count);
Assert.AreEqual(0, s.DataList.Count);
Assert.AreEqual(0, s.StructList.Count);
}
public static void InitListDefaults(TestLists lists)
{
lists.List0 = new TestLists.Struct0[]
{
new TestLists.Struct0(),
new TestLists.Struct0()
};
lists.List1 = new TestLists.Struct1[]
{
new TestLists.Struct1() { F = true },
new TestLists.Struct1() { F = false },
new TestLists.Struct1() { F = true },
new TestLists.Struct1() { F = true }
};
lists.List8 = new TestLists.Struct8[]
{
new TestLists.Struct8() { F = 123 },
new TestLists.Struct8() { F = 45 }
};
lists.List16 = new TestLists.Struct16[]
{
new TestLists.Struct16() { F = 12345 },
new TestLists.Struct16() { F = 6789 }
};
lists.List32 = new TestLists.Struct32[]
{
new TestLists.Struct32() { F = 123456789 },
new TestLists.Struct32() { F = 234567890 }
};
lists.List64 = new TestLists.Struct64[]
{
new TestLists.Struct64() { F = 1234567890123456 },
new TestLists.Struct64() { F = 2345678901234567}
};
lists.ListP = new TestLists.StructP[]
{
new TestLists.StructP() { F = "foo" },
new TestLists.StructP() { F = "bar" }
};
lists.Int32ListList = new int[][]
{
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 12341234 }
};
lists.TextListList = new string[][]
{
new string[] { "foo", "bar" },
new string[] { "baz" },
new string[] { "qux", "corge" }
};
lists.StructListList = new TestAllTypes[][]
{
new TestAllTypes[]
{
new TestAllTypes() { Int32Field = 123 },
new TestAllTypes() { Int32Field = 456 }
},
new TestAllTypes[]
{
new TestAllTypes() { Int32Field = 789 }
}
};
}
public static void CheckListDefault(TestLists lists)
{
Assert.AreEqual(2, lists.List0.Count);
Assert.AreEqual(4, lists.List1.Count);
Assert.AreEqual(2, lists.List8.Count);
Assert.AreEqual(2, lists.List16.Count);
Assert.AreEqual(2, lists.List32.Count);
Assert.AreEqual(2, lists.List64.Count);
Assert.AreEqual(2, lists.ListP.Count);
Assert.IsTrue(lists.List1[0].F);
Assert.IsFalse(lists.List1[1].F);
Assert.IsTrue(lists.List1[2].F);
Assert.IsTrue(lists.List1[3].F);
Assert.AreEqual(123, lists.List8[0].F);
Assert.AreEqual(45, lists.List8[1].F);
Assert.AreEqual(12345, lists.List16[0].F);
Assert.AreEqual(6789, lists.List16[1].F);
Assert.AreEqual(123456789, lists.List32[0].F);
Assert.AreEqual(234567890, lists.List32[1].F);
Assert.AreEqual(1234567890123456, lists.List64[0].F);
Assert.AreEqual(2345678901234567, lists.List64[1].F);
Assert.AreEqual("foo", lists.ListP[0].F);
Assert.AreEqual("bar", lists.ListP[1].F);
Assert.AreEqual(3, lists.Int32ListList.Count);
Assert.IsTrue(lists.Int32ListList[0].SequenceEqual(new int[] { 1, 2, 3 }));
Assert.IsTrue(lists.Int32ListList[1].SequenceEqual(new int[] { 4, 5 }));
Assert.IsTrue(lists.Int32ListList[2].SequenceEqual(new int[] { 12341234 }));
Assert.AreEqual(3, lists.TextListList.Count);
Assert.IsTrue(lists.TextListList[0].SequenceEqual(new string[] { "foo", "bar" }));
Assert.IsTrue(lists.TextListList[1].SequenceEqual(new string[] { "baz" }));
Assert.IsTrue(lists.TextListList[2].SequenceEqual(new string[] { "qux", "corge" }));
Assert.AreEqual(2, lists.StructListList.Count);
Assert.AreEqual(2, lists.StructListList[0].Count);
Assert.AreEqual(123, lists.StructListList[0][0]);
Assert.AreEqual(456, lists.StructListList[0][1]);
Assert.AreEqual(1, lists.StructListList[1].Count);
Assert.AreEqual(789, lists.StructListList[1][0]);
}
}
class Counters
{
public int CallCount;
public int HandleCount;
}
#region TestInterface
class TestInterfaceImpl : ITestInterface
{
readonly TaskCompletionSource<int> _tcs;
protected readonly Counters _counters;
public TestInterfaceImpl(Counters counters, TaskCompletionSource<int> tcs)
{
_tcs = tcs;
_counters = counters;
}
public TestInterfaceImpl(Counters counters)
{
_counters = counters;
}
public Task Bar(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task Baz(TestAllTypes s, CancellationToken cancellationToken)
{
Interlocked.Increment(ref _counters.CallCount);
Common.CheckTestMessage(s);
return Task.CompletedTask;
}
public void Dispose()
{
_tcs?.SetResult(0);
}
public virtual Task<string> Foo(uint i, bool j, CancellationToken cancellationToken)
{
Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual(123u, i);
Assert.IsTrue(j);
return Task.FromResult("foo");
}
}
#endregion TestInterface
#region TestExtends
class TestExtendsImpl : TestInterfaceImpl, ITestExtends
{
public TestExtendsImpl(Counters counters) : base(counters)
{
}
public override Task<string> Foo(uint i, bool j, CancellationToken cancellationToken)
{
Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual(321u, i);
Assert.IsFalse(j);
return Task.FromResult("bar");
}
public Task Corge(TestAllTypes s, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<TestAllTypes> Grault(CancellationToken cancellationToken)
{
Interlocked.Increment(ref _counters.CallCount);
var result = new TestAllTypes();
Common.InitTestMessage(result);
return Task.FromResult(result);
}
public Task Qux(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
#endregion TestExtends
#region TestPipeline
class TestPipelineImpl : ITestPipeline
{
protected readonly Counters _counters;
public TestPipelineImpl(Counters counters)
{
_counters = counters;
}
public void Dispose()
{
}
public async Task<(string, TestPipeline.AnyBox)> GetAnyCap(uint n, BareProxy inCap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual(234u, n);
var s = await inCap.Cast<ITestInterface>(true).Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return ("bar", new TestPipeline.AnyBox() { Cap = BareProxy.FromImpl(new TestExtendsImpl(_counters)) });
}
public async Task<(string, TestPipeline.Box)> GetCap(uint n, ITestInterface inCap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual(234u, n);
var s = await inCap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return ("bar", new TestPipeline.Box() { Cap = new TestExtendsImpl(_counters) });
}
public Task TestPointers(ITestInterface cap, AnyPointer obj, IReadOnlyList<ITestInterface> list, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
}
#endregion TestPipeline
#region TestCallOrder
class TestCallOrderImpl : ITestCallOrder
{
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
public uint Count { get; set; }
public void Dispose()
{
}
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_)
{
return Task.FromResult(Count++);
}
}
#endregion TestCallOrder
#region TestTailCaller
class TestTailCaller : ITestTailCaller
{
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
return callee.Foo(i, "from TestTailCaller", cancellationToken_);
}
}
#endregion TestTailCaller
#region TestTailCaller
class TestTailCallerImpl : ITestTailCaller
{
readonly Counters _counters;
public TestTailCallerImpl(Counters counters)
{
_counters = counters;
}
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
using (callee)
{
return callee.Foo(i, "from TestTailCaller", cancellationToken_);
}
}
}
#endregion TestTailCaller
#region TestTailCallee
class TestTailCalleeImpl : ITestTailCallee
{
readonly Counters _counters;
public TestTailCalleeImpl(Counters counters)
{
_counters = counters;
}
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, string t, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
var result = new TestTailCallee.TailResult()
{
I = (uint)i,
T = t,
C = new TestCallOrderImpl()
};
return Task.FromResult(result);
}
}
#endregion TestTailCallee
#region TestMoreStuff
class TestMoreStuffImpl : ITestMoreStuff
{
readonly TaskCompletionSource<int> _echoAllowed = new TaskCompletionSource<int>();
readonly Counters _counters;
public TestMoreStuffImpl(Counters counters)
{
_counters = counters;
}
public ITestInterface ClientToHold { get; set; }
public void EnableEcho()
{
_echoAllowed.SetResult(0);
}
public async Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
using (cap)
{
string s = await cap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
}
return "bar";
}
public async Task<string> CallFooWhenResolved(ITestInterface cap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
await ((Proxy)cap).WhenResolved;
string s = await cap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return "bar";
}
public async Task<string> CallHeld(CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
string s = await ClientToHold.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return "bar";
}
public void Dispose()
{
ClientToHold?.Dispose();
}
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
return Task.FromResult(cap);
}
public Task ExpectCancel(ITestInterface cap, CancellationToken cancellationToken_)
{
return NeverReturn(cap, cancellationToken_);
}
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_)
{
return Task.FromResult((uint)(Interlocked.Increment(ref _counters.CallCount) - 1));
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_)
{
return Task.FromResult(new string(new char[100000000]));
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_)
{
return Task.FromResult((ITestHandle)new TestHandleImpl(_counters));
}
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
return Task.FromResult(ClientToHold);
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)
{
return Task.FromResult(default(ITestMoreStuff));
}
public Task Hold(ITestInterface cap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
ClientToHold?.Dispose();
ClientToHold = cap;
return Task.CompletedTask;
}
public Task<(string, string)> MethodWithDefaults(string a, uint b, string c, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string a, ITestInterface b, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public async Task<ITestInterface> NeverReturn(ITestInterface cap, CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
try
{
var tcs = new TaskCompletionSource<int>();
using (cancellationToken_.Register(() => tcs.SetResult(0)))
{
await tcs.Task;
throw new TaskCanceledException();
}
}
finally
{
cap.Dispose();
}
}
}
#endregion TestMoreStuff
#region TestHandle
class TestHandleImpl : ITestHandle, IDisposable
{
readonly Counters _counters;
public TestHandleImpl(Counters counters)
{
_counters = counters;
Interlocked.Increment(ref _counters.HandleCount);
}
public void Dispose()
{
Interlocked.Decrement(ref _counters.HandleCount);
}
}
#endregion TestHandle
}

View File

@ -0,0 +1,102 @@
using Capnp.Rpc;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests.ManualImpls
{
// [Skeleton(typeof(TestInterfaceSkeleton))]
// [Proxy(typeof(TestInterfaceProxy))]
// interface ITestInterface: IDisposable
// {
// Task<string> Foo(uint i, bool j);
// Task Bar();
// Task<int> Baz(int s);
// }
// [Skeleton(typeof(TestExtendsSkeleton))]
// [Proxy(typeof(TestExtendsProxy))]
// interface ITestExtends: ITestInterface, IDisposable
// {
// void Qux();
// Task Corge(int x);
// Task<int> Grault();
// }
// interface ITestExtends2: ITestExtends, IDisposable
// {
// }
// struct Box
// {
// public ITestExtends Cap { get; set; }
// }
// struct AnyBox
// {
// public object Cap { get; set; }
// }
// [Skeleton(typeof(TestPipelineSkeleton))]
// [Proxy(typeof(TestPipelineProxy))]
// interface ITestPipeline: IDisposable
// {
// Task<(string, Box)> GetCap(uint n, ITestInterface inCap);
// Task TestPointers(ITestExtends cap, DeserializerState obj, IReadOnlyList<ITestExtends> list);
// Task<(string, AnyBox)> GetAnyCap(uint n, object inCap);
// }
// [Skeleton(typeof(TestCallOrderSkeleton))]
// [Proxy(typeof(TestCallOrderProxy))]
// interface ITestCallOrder : IDisposable
// {
// Task<uint> GetCallSequence(uint expected);
// }
// struct TailResult
// {
// public uint I { get; set; }
// public string T { get; set; }
// public ITestCallOrder C { get; set; }
//}
// [Skeleton(typeof(TestTailCalleeSkeleton))]
// [Proxy(typeof(TestTailCalleeProxy))]
// interface ITestTailCallee: IDisposable
// {
// Task<TailResult> Foo(int i, string t);
// }
// [Skeleton(typeof(TestTailCallerSkeleton))]
// [Proxy(typeof(TestTailCallerProxy))]
// interface ITestTailCaller: IDisposable
// {
// Task<TailResult> Foo(int i, ITestTailCallee c);
// }
// [Skeleton(typeof(TestHandleSkeleton))]
// [Proxy(typeof(TestHandleProxy))]
// interface ITestHandle: IDisposable { }
// [Skeleton(typeof(TestMoreStuffSkeleton))]
// [Proxy(typeof(TestMoreStuffProxy))]
// interface ITestMoreStuff: ITestCallOrder
// {
// Task<string> CallFoo(ITestInterface cap);
// Task<string> CallFooWhenResolved(ITestInterface cap);
// Task<ITestInterface> NeverReturn(ITestInterface cap, CancellationToken ct);
// Task Hold(ITestInterface cap);
// Task<string> CallHeld();
// Task<ITestInterface> GetHeld();
// Task<ITestCallOrder> Echo(ITestCallOrder cap);
// Task ExpectCancel(ITestInterface cap, CancellationToken ct);
// Task<(string, string)> MethodWithDefaults(string a, uint b, string c);
// void MethodWithNullDefault(string a, ITestInterface b);
// Task<ITestHandle> GetHandle();
// Task<ITestMoreStuff> GetNull();
// Task<string> GetEnormousString();
// }
}

View File

@ -0,0 +1,228 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
public class WirePointerTests
{
[TestMethod]
public void Struct()
{
var wp = default(WirePointer);
wp.BeginStruct(17, 71);
wp.Offset = -321;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.Struct, wp.Kind);
Assert.AreEqual<ushort>(17, wp.StructDataCount);
Assert.AreEqual<ushort>(71, wp.StructPtrCount);
Assert.AreEqual(-321, wp.Offset);
}
[TestMethod]
public void StructAsListTag()
{
var wp = default(WirePointer);
wp.BeginStruct(17, 71);
wp.ListOfStructsElementCount = 555;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.Struct, wp.Kind);
Assert.AreEqual<ushort>(17, wp.StructDataCount);
Assert.AreEqual<ushort>(71, wp.StructPtrCount);
Assert.AreEqual(555, wp.ListOfStructsElementCount);
}
[TestMethod]
public void ListOfEmpty()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfEmpty, 112);
wp.Offset = 517;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfEmpty, wp.ListKind);
Assert.AreEqual(112, wp.ListElementCount);
Assert.AreEqual(517, wp.Offset);
}
[TestMethod]
public void ListOfBits()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfBits, 888);
wp.Offset = -919;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfBits, wp.ListKind);
Assert.AreEqual(888, wp.ListElementCount);
Assert.AreEqual(-919, wp.Offset);
}
[TestMethod]
public void ListOfBytes()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfBytes, 1023);
wp.Offset = 1027;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfBytes, wp.ListKind);
Assert.AreEqual(1023, wp.ListElementCount);
Assert.AreEqual(1027, wp.Offset);
}
[TestMethod]
public void ListOfShorts()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfShorts, 12345);
wp.Offset = -12345;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfShorts, wp.ListKind);
Assert.AreEqual(12345, wp.ListElementCount);
Assert.AreEqual(-12345, wp.Offset);
}
[TestMethod]
public void ListOfInts()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfInts, 89400);
wp.Offset = 111000;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfInts, wp.ListKind);
Assert.AreEqual(89400, wp.ListElementCount);
Assert.AreEqual(111000, wp.Offset);
}
[TestMethod]
public void ListOfLongs()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfLongs, 34500);
wp.Offset = 8100999;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfLongs, wp.ListKind);
Assert.AreEqual(34500, wp.ListElementCount);
Assert.AreEqual(8100999, wp.Offset);
}
[TestMethod]
public void ListOfPointers()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfPointers, 12999777);
wp.Offset = -11222000;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfPointers, wp.ListKind);
Assert.AreEqual(12999777, wp.ListElementCount);
Assert.AreEqual(-11222000, wp.Offset);
}
[TestMethod]
public void ListOfStructs()
{
var wp = default(WirePointer);
wp.BeginList(ListKind.ListOfStructs, 77000);
wp.Offset = -99888;
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.List, wp.Kind);
Assert.AreEqual(ListKind.ListOfStructs, wp.ListKind);
Assert.AreEqual(77000, wp.ListElementCount);
Assert.AreEqual(-99888, wp.Offset);
}
[TestMethod]
public void Far()
{
var wp = default(WirePointer);
wp.SetFarPointer(29, 777, false);
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.Far, wp.Kind);
Assert.IsFalse(wp.IsDoubleFar);
Assert.AreEqual(29u, wp.TargetSegmentIndex);
Assert.AreEqual(777, wp.LandingPadOffset);
}
[TestMethod]
public void DoubleFar()
{
var wp = default(WirePointer);
wp.SetFarPointer(92, 891, true);
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.Far, wp.Kind);
Assert.IsTrue(wp.IsDoubleFar);
Assert.AreEqual(92u, wp.TargetSegmentIndex);
Assert.AreEqual(891, wp.LandingPadOffset);
}
[TestMethod]
public void Capability()
{
var wp = default(WirePointer);
wp.SetCapability(123456);
ulong v = wp;
wp = v;
Assert.AreEqual(PointerKind.Other, wp.Kind);
Assert.AreEqual(0u, wp.OtherPointerKind);
Assert.AreEqual(123456u, wp.CapabilityIndex);
}
[TestMethod]
public void OffsetOutOfBounds()
{
var wp = default(WirePointer);
wp.BeginStruct(12345, 54321);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => wp.Offset = 1 << 30);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => wp.Offset = int.MinValue);
}
[TestMethod]
public void ElementCountOutOfBounds()
{
var wp = default(WirePointer);
Assert.ThrowsException<ArgumentOutOfRangeException>(
() => wp.BeginList(ListKind.ListOfBytes, 1 << 29));
wp.BeginList(ListKind.ListOfInts, 1 << 29 - 1);
Assert.ThrowsException<ArgumentOutOfRangeException>(
() => wp.BeginList(ListKind.ListOfBytes, -1));
}
[TestMethod]
public void FarPointerOffsetOutOfBounds()
{
var wp = default(WirePointer);
Assert.ThrowsException<ArgumentOutOfRangeException>(
() => wp.SetFarPointer(1, 1 << 29, false));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
namespace Capnp
{
/// <summary>
/// Generic <see cref="ICapnpSerializable"/> implementation, based on a wrapper around <see cref="DeserializerState"/>.
/// </summary>
public class AnyPointer : ICapnpSerializable
{
/// <summary>
/// The <see cref="DeserializerState"/> will be set by the Deserialize method.
/// </summary>
public DeserializerState State { get; private set; }
/// <summary>
/// Sets the State property.
/// </summary>
/// <param name="state">deserializer state</param>
public void Deserialize(DeserializerState state)
{
State = state;
}
/// <summary>
/// Performs a deep copy from State to given state.
/// </summary>
/// <param name="state">serializer state</param>
public void Serialize(SerializerState state)
{
Reserializing.DeepCopy(State, state);
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Capnp</RootNamespace>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Capnp
{
/// <summary>
/// Provides functionality to construct domain objects from <see cref="DeserializerState"/>.
/// </summary>
public static class CapnpSerializable
{
interface IConstructibleFromDeserializerState<out T>
{
T Create(DeserializerState state);
}
class FromStruct<T>: IConstructibleFromDeserializerState<T>
where T : ICapnpSerializable, new()
{
public T Create(DeserializerState state)
{
var result = new T();
if (state.Kind != ObjectKind.Nil)
{
result.Deserialize(state);
}
return result;
}
}
class FromList<T>: IConstructibleFromDeserializerState<IReadOnlyList<T>>
where T: class
{
readonly Func<DeserializerState, T> _elementSerializer;
public FromList()
{
_elementSerializer = (Func<DeserializerState, T>)GetSerializer(typeof(T));
}
public IReadOnlyList<T> Create(DeserializerState state)
{
return state.RequireList().Cast(_elementSerializer);
}
}
class FromCapability<T>: IConstructibleFromDeserializerState<T>
where T: class
{
public T Create(DeserializerState state)
{
return state.RequireCap<T>();
}
}
static readonly ConditionalWeakTable<Type, Func<DeserializerState, object>> _typeMap =
new ConditionalWeakTable<Type, Func<DeserializerState, object>>();
static CapnpSerializable()
{
_typeMap.Add(typeof(string), d => d.RequireList().CastText());
_typeMap.Add(typeof(IReadOnlyList<bool>), d => d.RequireList().CastBool());
_typeMap.Add(typeof(IReadOnlyList<sbyte>), d => d.RequireList().CastSByte());
_typeMap.Add(typeof(IReadOnlyList<byte>), d => d.RequireList().CastByte());
_typeMap.Add(typeof(IReadOnlyList<short>), d => d.RequireList().CastShort());
_typeMap.Add(typeof(IReadOnlyList<ushort>), d => d.RequireList().CastUShort());
_typeMap.Add(typeof(IReadOnlyList<int>), d => d.RequireList().CastInt());
_typeMap.Add(typeof(IReadOnlyList<uint>), d => d.RequireList().CastUInt());
_typeMap.Add(typeof(IReadOnlyList<long>), d => d.RequireList().CastLong());
_typeMap.Add(typeof(IReadOnlyList<ulong>), d => d.RequireList().CastULong());
_typeMap.Add(typeof(IReadOnlyList<float>), d => d.RequireList().CastFloat());
_typeMap.Add(typeof(IReadOnlyList<double>), d => d.RequireList().CastDouble());
}
static Func<DeserializerState, object> CreateSerializer(Type type)
{
if (typeof(ICapnpSerializable).IsAssignableFrom(type))
{
try
{
return ((IConstructibleFromDeserializerState<object>)
Activator.CreateInstance(typeof(FromStruct<>).MakeGenericType(type))).Create;
}
catch (Exception ex)
{
throw new ArgumentException(
$"Cannot create serializer, probably because serializer {type.Name} does not expose a public parameterless constructor",
ex);
}
}
else if (type.IsGenericType && typeof(IReadOnlyList<>) == type.GetGenericTypeDefinition())
{
try
{
var elementType = type.GetGenericArguments()[0];
return ((IConstructibleFromDeserializerState<object>)
Activator.CreateInstance(typeof(FromList<>).MakeGenericType(elementType))).Create;
}
catch (TargetInvocationException ex)
{
throw ex.InnerException;
}
catch (Exception ex)
{
throw new ArgumentException(
$"Cannot create list serializer, probably because the element type is not a reference type",
ex);
}
}
else
{
try
{
Rpc.CapabilityReflection.ValidateCapabilityInterface(type);
}
catch (Exception ex)
{
throw new ArgumentException(
$"Don't know how to construct a serializer from {type.Name}. Tried to interpret it as capability interface, but it didn't work.",
ex);
}
try
{
return ((IConstructibleFromDeserializerState<object>)
Activator.CreateInstance(typeof(FromCapability<>).MakeGenericType(type))).Create;
}
catch (Exception ex)
{
throw new ArgumentException(
$"Cannot create serializer, probably because serializer {type.Name} a not a viable capability interface",
ex);
}
}
}
static Func<DeserializerState, object> GetSerializer(Type type)
{
return _typeMap.GetValue(type, CreateSerializer);
}
/// <summary>
/// Constructs a domain object from a given deserializer state.
/// </summary>
/// <typeparam name="T">Type of domain object to construct. Must be one of the following:
/// <list type="bullet">
/// <item><description>Type implementing <see cref="ICapnpSerializable"/>. The type must must have a public parameterless constructor.</description></item>
/// <item><description>A capability interface (<seealso cref="Rpc.InvalidCapabilityInterfaceException"/> for further explanation)</description></item>
/// <item><description><see cref="String"/></description></item>
/// <item><description><see cref="IReadOnlyList{Boolean}"/></description></item>
/// <item><description><see cref="IReadOnlyList{SByte}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Byte}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Int16}"/></description></item>
/// <item><description><see cref="IReadOnlyList{UInt16}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Int32}"/></description></item>
/// <item><description><see cref="IReadOnlyList{UInt32}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Int64}"/></description></item>
/// <item><description><see cref="IReadOnlyList{UInt64}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Single}"/></description></item>
/// <item><description><see cref="IReadOnlyList{Double}"/></description></item>
/// <item><description><see cref="IReadOnlyList{T}"/> whereby T is one of the things listed here.</description></item>
/// </list>
/// </typeparam>
/// <param name="state"></param>
/// <returns></returns>
public static T Create<T>(DeserializerState state)
where T: class
{
return (T)GetSerializer(typeof(T))(state);
}
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace Capnp
{
/// <summary>
/// This exception gets thrown when a Cap'n Proto object could not be deserialized correctly.
/// </summary>
public class DeserializationException : Exception
{
public DeserializationException(string message) : base(message)
{
}
public DeserializationException(string message, Exception innerException):
base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,686 @@
using System;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// Implements the heart of deserialization. This stateful helper struct exposes all functionality to traverse 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.
/// </summary>
public struct DeserializerState: IStructDeserializer
{
/// <summary>
/// A wire message is essentially a collection of memory blocks.
/// </summary>
public IReadOnlyList<Memory<ulong>> Segments { get; }
/// <summary>
/// Index of the segment (into the Segments property) which this state currently refers to.
/// </summary>
public uint CurrentSegmentIndex { get; private set; }
/// <summary>
/// Word offset within the current segment which this state currently refers to.
/// </summary>
public int Offset { get; set; }
/// <summary>
/// Context-dependent meaning: Usually the number of bytes traversed until this state was reached, to prevent amplification attacks.
/// However, if this state is of Kind == ObjectKind.Value (an artificial category which will never occur on the wire but is used to
/// internally represent lists of primitives as lists of structs), it contains the primitive's value.
/// </summary>
public uint BytesTraversedOrData { get; set; }
/// <summary>
/// If this state currently represents a list, the number of list elements.
/// </summary>
public int ListElementCount { get; private set; }
/// <summary>
/// If this state currently represents a struct, the struct's data section word count.
/// </summary>
public ushort StructDataCount { get; set; }
/// <summary>
/// If this state currently represents a struct, the struct's pointer section word count.
/// </summary>
public ushort StructPtrCount { get; set; }
/// <summary>
/// The kind of object this state currently represents.
/// </summary>
public ObjectKind Kind { get; set; }
/// <summary>
/// The capabilities imported from the capability table. Only valid in RPC context.
/// </summary>
public IReadOnlyList<Rpc.ConsumedCapability> Caps { get; set; }
/// <summary>
/// Current segment (essentially Segments[CurrentSegmentIndex]
/// </summary>
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
DeserializerState(IReadOnlyList<Memory<ulong>> segments)
{
Segments = segments;
CurrentSegmentIndex = 0;
Offset = 0;
BytesTraversedOrData = 0;
ListElementCount = 0;
StructDataCount = 0;
StructPtrCount = 1;
Kind = ObjectKind.Struct;
Caps = null;
}
/// <summary>
/// Constructs a state representing a message root object.
/// </summary>
/// <param name="frame">the message</param>
/// <returns></returns>
public static DeserializerState CreateRoot(WireFrame frame)
{
var state = new DeserializerState(frame.Segments);
state.DecodePointer(0);
return state;
}
/// <summary>
/// Implicitly converts a serializer state into a deserializer state.
/// The conversion is cheap, since it does not involve copying any payload.
/// </summary>
/// <param name="state">The serializer state to be converted</param>
public static implicit operator DeserializerState(SerializerState state)
{
if (state == null)
throw new ArgumentNullException(nameof(state));
switch (state.Kind)
{
case ObjectKind.ListOfBits:
case ObjectKind.ListOfBytes:
case ObjectKind.ListOfEmpty:
case ObjectKind.ListOfInts:
case ObjectKind.ListOfLongs:
case ObjectKind.ListOfPointers:
case ObjectKind.ListOfShorts:
case ObjectKind.ListOfStructs:
case ObjectKind.Nil:
case ObjectKind.Struct:
return new DeserializerState(state.Allocator.Segments)
{
CurrentSegmentIndex = state.SegmentIndex,
Offset = state.Offset,
ListElementCount = state.ListElementCount,
StructDataCount = state.StructDataCount,
StructPtrCount = state.StructPtrCount,
Kind = state.Kind,
Caps = state.Caps
};
case ObjectKind.Capability:
return new DeserializerState(state.Allocator.Segments)
{
Kind = ObjectKind.Capability,
Caps = state.Caps,
BytesTraversedOrData = state.CapabilityIndex
};
default:
throw new ArgumentException("Unexpected type of object, cannot convert that into DeserializerState", nameof(state));
}
}
/// <summary>
/// Constructs a state representing the given value. This kind of state is artificial and beyond the Cap'n Proto specification.
/// We need it to internally represent list of primitive values as lists of structs.
/// </summary>
public static DeserializerState MakeValueState(uint value)
{
return new DeserializerState()
{
BytesTraversedOrData = value,
Kind = ObjectKind.Value
};
}
/// <summary>
/// Increments the number of bytes traversed and checks the results against the traversal limit.
/// </summary>
/// <param name="additionalBytesTraversed">Amount to increase the traversed bytes</param>
public void IncrementBytesTraversed(uint additionalBytesTraversed)
{
BytesTraversedOrData = checked(BytesTraversedOrData + additionalBytesTraversed);
if (BytesTraversedOrData > SecurityOptions.TraversalLimit)
throw new DeserializationException("Traversal limit was reached");
}
/// <summary>
/// Memory span which represents this struct's data section (given this state actually represents a struct)
/// </summary>
public ReadOnlySpan<ulong> StructDataSection => CurrentSegment.Slice(Offset, StructDataCount);
ReadOnlySpan<ulong> StructPtrSection => CurrentSegment.Slice(Offset + StructDataCount, StructPtrCount);
ReadOnlySpan<ulong> GetRawBits() => CurrentSegment.Slice(Offset, (ListElementCount + 63) / 64);
ReadOnlySpan<ulong> GetRawBytes() => CurrentSegment.Slice(Offset, (ListElementCount + 7) / 8);
ReadOnlySpan<ulong> GetRawShorts() => CurrentSegment.Slice(Offset, (ListElementCount + 3) / 4);
ReadOnlySpan<ulong> GetRawInts() => CurrentSegment.Slice(Offset, (ListElementCount + 1) / 2);
ReadOnlySpan<ulong> GetRawLongs() => CurrentSegment.Slice(Offset, ListElementCount);
/// <summary>
/// If this state represents a list of primitive values, returns the raw list data.
/// </summary>
public ReadOnlySpan<ulong> RawData
{
get
{
switch (Kind)
{
case ObjectKind.ListOfBits:
return GetRawBits();
case ObjectKind.ListOfBytes:
return GetRawBytes();
case ObjectKind.ListOfShorts:
return GetRawShorts();
case ObjectKind.ListOfInts:
return GetRawInts();
case ObjectKind.ListOfLongs:
return GetRawLongs();
default:
return default;
}
}
}
void Validate()
{
try
{
switch (Kind)
{
case ObjectKind.Struct:
CurrentSegment.Slice(Offset, StructDataCount + StructPtrCount);
break;
case ObjectKind.ListOfBits:
GetRawBits();
break;
case ObjectKind.ListOfBytes:
GetRawBytes();
break;
case ObjectKind.ListOfInts:
GetRawInts();
break;
case ObjectKind.ListOfLongs:
case ObjectKind.ListOfPointers:
GetRawLongs();
break;
case ObjectKind.ListOfStructs:
CurrentSegment.Slice(Offset, checked(ListElementCount * (StructDataCount + StructPtrCount)));
break;
}
}
catch (Exception problem)
{
throw new DeserializationException("Invalid wire pointer", problem);
}
}
/// <summary>
/// Interprets a pointer within the current segment and mutates this state to represent the pointer's target.
/// </summary>
/// <param name="offset">word offset relative to this.Offset within current segment</param>
/// <exception cref="IndexOutOfRangeException">offset negative or out of range</exception>
/// <exception cref="DeserializationException">invalid pointer data or traversal limit exceeded</exception>
internal void DecodePointer(int offset)
{
if (offset < 0)
throw new IndexOutOfRangeException(nameof(offset));
WirePointer pointer = CurrentSegment[Offset + offset];
int derefCount = 0;
do
{
if (pointer.IsNull)
{
this = default;
return;
}
switch (pointer.Kind)
{
case PointerKind.Struct:
Offset = checked(pointer.Offset + Offset + offset + 1);
IncrementBytesTraversed(checked(8u * pointer.StructSize));
StructDataCount = pointer.StructDataCount;
StructPtrCount = pointer.StructPtrCount;
Kind = ObjectKind.Struct;
Validate();
return;
case PointerKind.List:
Offset = checked(pointer.Offset + Offset + offset + 1);
ListElementCount = pointer.ListElementCount;
StructDataCount = 0;
StructPtrCount = 0;
switch (pointer.ListKind)
{
case ListKind.ListOfEmpty: // e.g. List(void)
// the “traversal limit” should count a list of zero-sized elements as if each element were one word instead.
IncrementBytesTraversed(checked(8u * (uint)ListElementCount));
Kind = ObjectKind.ListOfEmpty;
break;
case ListKind.ListOfBits:
IncrementBytesTraversed(checked((uint)ListElementCount + 7) / 8);
Kind = ObjectKind.ListOfBits;
break;
case ListKind.ListOfBytes:
IncrementBytesTraversed((uint)ListElementCount);
Kind = ObjectKind.ListOfBytes;
break;
case ListKind.ListOfShorts:
IncrementBytesTraversed(checked(2u * (uint)ListElementCount));
Kind = ObjectKind.ListOfShorts;
break;
case ListKind.ListOfInts:
IncrementBytesTraversed(checked(4u * (uint)ListElementCount));
Kind = ObjectKind.ListOfInts;
break;
case ListKind.ListOfLongs:
IncrementBytesTraversed(checked(8u * (uint)ListElementCount));
Kind = ObjectKind.ListOfLongs;
break;
case ListKind.ListOfPointers:
IncrementBytesTraversed(checked(8u * (uint)ListElementCount));
Kind = ObjectKind.ListOfPointers;
break;
case ListKind.ListOfStructs:
{
WirePointer tag = CurrentSegment[Offset];
if (tag.Kind != PointerKind.Struct)
throw new DeserializationException("Unexpected: List of composites with non-struct type tag");
IncrementBytesTraversed(checked(8u * (uint)pointer.ListElementCount + 8u));
ListElementCount = tag.ListOfStructsElementCount;
StructDataCount = tag.StructDataCount;
StructPtrCount = tag.StructPtrCount;
Kind = ObjectKind.ListOfStructs;
}
break;
default:
throw new InvalidProgramException();
}
Validate();
return;
case PointerKind.Far:
if (pointer.IsDoubleFar)
{
CurrentSegmentIndex = pointer.TargetSegmentIndex;
Offset = 0;
WirePointer pointer1 = CurrentSegment[pointer.LandingPadOffset];
if (pointer1.Kind != PointerKind.Far || pointer1.IsDoubleFar)
throw new DeserializationException("Error decoding double-far pointer: convention broken");
WirePointer pointer2 = CurrentSegment[pointer.LandingPadOffset + 1];
if (pointer2.Kind == PointerKind.Far)
throw new DeserializationException("Error decoding double-far pointer: not followed by intra-segment pointer");
CurrentSegmentIndex = pointer1.TargetSegmentIndex;
Offset = 0;
pointer = pointer2;
offset = -1;
}
else
{
CurrentSegmentIndex = pointer.TargetSegmentIndex;
Offset = 0;
offset = pointer.LandingPadOffset;
pointer = CurrentSegment[pointer.LandingPadOffset];
}
continue;
case PointerKind.Other:
this = default;
Kind = ObjectKind.Capability;
BytesTraversedOrData = pointer.CapabilityIndex;
return;
default:
throw new InvalidProgramException();
}
} while (++derefCount < SecurityOptions.RecursionLimit);
throw new DeserializationException("Recursion limit reached while decoding a pointer");
}
/// <summary>
/// Interprets a pointer within the current segment as capability pointer and returns the according low-level capability object from
/// the capability table. Does not mutate this state.
/// </summary>
/// <param name="offset">Offset relative to this.Offset within current segment</param>
/// <returns>the low-level capability object</returns>
/// <exception cref="IndexOutOfRangeException">offset negative or out of range</exception>
/// <exception cref="InvalidOperationException">capability table not set</exception>
/// <exception cref="Rpc.RpcException">not a capability pointer or invalid capability index</exception>
internal Rpc.ConsumedCapability DecodeCapPointer(int offset)
{
if (offset < 0)
{
throw new IndexOutOfRangeException(nameof(offset));
}
if (Caps == null)
{
throw new InvalidOperationException("Capbility table not set");
}
WirePointer pointer = CurrentSegment[Offset + offset];
if (pointer.IsNull)
{
// Despite this behavior is not officially specified,
// the official C++ implementation seems to send null pointers for null caps.
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];
}
/// <summary>
/// Reads a slice of up to 64 bits from this struct's data section, starting from the specified bit offset.
/// The slice must be aligned within a 64 bit word boundary.
/// </summary>
/// <param name="bitOffset">Start bit offset relative to the data section, little endian</param>
/// <param name="bitCount">numbers of bits to read</param>
/// <returns>the data</returns>
/// <exception cref="ArgumentOutOfRangeException">non-aligned access</exception>
/// <exception cref="IndexOutOfRangeException">bitOffset exceeds the data section</exception>
/// <exception cref="DeserializationException">this state does not represent a struct</exception>
public ulong StructReadData(ulong bitOffset, int bitCount)
{
switch (Kind)
{
case ObjectKind.Nil:
return 0;
case ObjectKind.Struct:
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 + bitCount > 64)
throw new ArgumentOutOfRangeException(nameof(bitCount));
ulong word = data[index];
if (bitCount == 64)
{
return word;
}
else
{
ulong mask = (1ul << bitCount) - 1;
return (word >> relBitOffset) & mask;
}
case ObjectKind.Value:
if (bitOffset >= 32) return 0;
if (bitCount >= 32) return BytesTraversedOrData >> (int)bitOffset;
return (BytesTraversedOrData >> (int)bitOffset) & ((1u << bitCount) - 1);
default:
throw new DeserializationException("This is not a struct");
}
}
/// <summary>
/// Decodes a pointer from this struct's pointer section and returns the state representing the pointer target.
/// It is valid to specify an index beyond the pointer section, in which case a default state (representing the "null object")
/// will be returned. This is to preserve upward compatibility with schema evolution.
/// </summary>
/// <param name="index">Index within the pointer section</param>
/// <returns>the target state</returns>
/// <exception cref="DeserializationException">this state does not represent a struct,
/// invalid pointer, or traversal limit exceeded</exception>
public DeserializerState StructReadPointer(int index)
{
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
throw new DeserializationException("This is not a struct");
if (index >= StructPtrCount)
return default;
DeserializerState state = this;
state.DecodePointer(index + StructDataCount);
return state;
}
internal Rpc.ConsumedCapability StructReadRawCap(int index)
{
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
throw new InvalidOperationException("Allowed on structs only");
if (index >= StructPtrCount)
return null;
return DecodeCapPointer(index + StructDataCount);
}
/// <summary>
/// Given this state represents a list (of anything), returns a ListDeserializer to further decode the list content.
/// </summary>
/// <exception cref="DeserializationException">state does not represent a list</exception>
public ListDeserializer RequireList()
{
switch (Kind)
{
case ObjectKind.ListOfBits:
return new ListOfBitsDeserializer(ref this, false);
case ObjectKind.ListOfBytes:
return new ListOfPrimitivesDeserializer<byte>(ref this, ListKind.ListOfBytes);
case ObjectKind.ListOfEmpty:
return new ListOfEmptyDeserializer(ref this);
case ObjectKind.ListOfInts:
return new ListOfPrimitivesDeserializer<int>(ref this, ListKind.ListOfInts);
case ObjectKind.ListOfLongs:
return new ListOfPrimitivesDeserializer<long>(ref this, ListKind.ListOfLongs);
case ObjectKind.ListOfPointers:
return new ListOfPointersDeserializer(ref this);
case ObjectKind.ListOfShorts:
return new ListOfPrimitivesDeserializer<short>(ref this, ListKind.ListOfShorts);
case ObjectKind.ListOfStructs:
return new ListOfStructsDeserializer(ref this);
case ObjectKind.Nil:
return new EmptyListDeserializer();
default:
throw new DeserializationException("Cannot deserialize this object as list");
}
}
/// <summary>
/// Given this state represents a list of pointers, returns a ListOfCapsDeserializer for decoding it as list of capabilities.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <exception cref="DeserializationException">state does not represent a list of pointers</exception>
public ListOfCapsDeserializer<T> RequireCapList<T>() where T: class
{
switch (Kind)
{
case ObjectKind.ListOfPointers:
return new ListOfCapsDeserializer<T>(ref this);
default:
throw new DeserializationException("Cannot deserialize this object as capability list");
}
}
/// <summary>
/// Convenience method. Given this state represents a struct, decodes text field from its pointer table.
/// </summary>
/// <param name="index">index within this struct's pointer table</param>
/// <param name="defaultText">default text to return of pointer is null</param>
/// <returns>the decoded text, or defaultText (which might be null)</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-list-of-bytes pointer, traversal limit exceeded</exception>
public string ReadText(int index, string defaultText = null)
{
return StructReadPointer(index).RequireList().CastText() ?? defaultText;
}
/// <summary>
/// Convenience method. Given this state represents a struct, decodes a list deserializer field from its pointer table.
/// </summary>
/// <param name="index">index within this struct's pointer table</param>
/// <returns>the list deserializer instance</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-list pointer, traversal limit exceeded</exception>
public ListDeserializer ReadList(int index)
{
return StructReadPointer(index).RequireList();
}
/// <summary>
/// Convenience method. Given this state represents a struct, decodes a capability list field from its pointer table.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <param name="index">index within this struct's pointer table</param>
/// <returns>the capability list deserializer instance</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-list-of-pointers pointer, traversal limit exceeded</exception>
public ListOfCapsDeserializer<T> ReadCapList<T>(int index) where T : class
{
return StructReadPointer(index).RequireCapList<T>();
}
/// <summary>
/// Convenience method. Given this state represents a struct, decodes a list of structs field from its pointer table.
/// </summary>
/// <typeparam name="T">Struct target representation type</typeparam>
/// <param name="index">index within this struct's pointer table</param>
/// <param name="cons">constructs a target representation type instance from the underlying deserializer state</param>
/// <returns>the decoded list of structs</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-list-of-{structs,pointers} pointer, traversal limit exceeded</exception>
public IReadOnlyList<T> ReadListOfStructs<T>(int index, Func<DeserializerState, T> cons)
{
return ReadList(index).Cast(cons);
}
/// <summary>
/// Convenience method. Given this state represents a struct, decodes a struct field from its pointer table.
/// </summary>
/// <typeparam name="T">Struct target representation type</typeparam>
/// <param name="index">index within this struct's pointer table</param>
/// <param name="cons">constructs a target representation type instance from the underlying deserializer state</param>
/// <returns>the decoded struct</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-struct pointer, traversal limit exceeded</exception>
public T ReadStruct<T>(int index, Func<DeserializerState, T> cons)
{
return cons(StructReadPointer(index));
}
/// <summary>
/// Given this state represents a capability, returns its index into the capability table.
/// </summary>
public uint CapabilityIndex => Kind == ObjectKind.Capability ? BytesTraversedOrData : ~0u;
/// <summary>
/// Given this state represents a struct, decodes a capability field from its pointer table.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <param name="index">index within this struct's pointer table</param>
/// <returns>capability instance or null if pointer was null</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-capability pointer, traversal limit exceeded</exception>
public T ReadCap<T>(int index,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) where T: class
{
var cap = StructReadRawCap(index);
return Rpc.CapabilityReflection.CreateProxy<T>(cap, memberName, sourceFilePath, sourceLineNumber) as T;
}
/// <summary>
/// Given this state represents a struct, decodes a capability field from its pointer table and
/// returns it as bare (generic) proxy.
/// </summary>
/// <param name="index">index within this struct's pointer table</param>
/// <returns>capability instance or null if pointer was null</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-capability pointer, traversal limit exceeded</exception>
public Rpc.BareProxy ReadCap(int index)
{
var cap = StructReadRawCap(index);
return new Rpc.BareProxy(cap);
}
/// <summary>
/// Given this state represents a capability, wraps it into a proxy instance for the desired interface.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <returns>capability instance or null if pointer was null</returns>
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a capability</exception>
public T RequireCap<T>() where T: class
{
if (Kind == ObjectKind.Nil)
return null;
if (Kind != ObjectKind.Capability)
throw new DeserializationException("Expected a capability");
return Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T;
}
}
}

View File

@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// This SerializerState specialization provides functionality to build arbitrary Cap'n Proto objects without requiring the schema code generator.
/// </summary>
public class DynamicSerializerState : SerializerState
{
/// <summary>
/// Constructs an unbound instance.
/// </summary>
public DynamicSerializerState()
{
}
/// <summary>
/// Constructs an instance and binds it to the given <see cref="MessageBuilder"/>.
/// </summary>
/// <param name="messageBuilder">message builder</param>
public DynamicSerializerState(MessageBuilder messageBuilder):
base(messageBuilder)
{
}
/// <summary>
/// Constructs an instance, binds it to a dedicated message builder, and initializes the capability table for usage in RPC context.
/// </summary>
public static DynamicSerializerState CreateForRpc()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
return new DynamicSerializerState(mb);
}
/// <summary>
/// Converts any <see cref="DeserializerState"/> to a DynamicSerializerState instance, which involves deep copying the object graph.
/// </summary>
/// <param name="state">The deserializer state to convert</param>
public static explicit operator DynamicSerializerState(DeserializerState state)
{
var mb = MessageBuilder.Create();
if (state.Caps != null)
mb.InitCapTable();
var sstate = mb.CreateObject<DynamicSerializerState>();
Reserializing.DeepCopy(state, sstate);
return sstate;
}
/// <summary>
/// Links a sub-item (struct field or list element) of this state to another state. Usually, this operation is not necessary, since objects are constructed top-down.
/// However, there might be some advanced scenarios where you want to reference the same object twice (also interesting for designing amplification attacks).
/// The Cap'n Proto serialization intrinsically supports this, since messages are object graphs, not trees.
/// </summary>
/// <param name="slot">If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.</param>
/// <param name="target">state to be linked</param>
/// <param name="allowCopy">Whether to deep copy the target state if it belongs to a different message builder than this state.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="slot"/> out of range</exception>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item></list>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// <item><description>This state and <paramref name="target"/> belong to different message builder, and<paramref name="allowCopy"/> is false</description></item></list>
/// </exception>
public new void Link(int slot, SerializerState target, bool allowCopy = true) => base.Link(slot, target, allowCopy);
/// <summary>
/// Links a sub-item (struct field or list element) of this state to a capability.
/// </summary>
/// <param name="slot">If this state describes a struct: Index into this struct's pointer table.
/// If this state describes a list of pointers: List element index.</param>
/// <param name="capabilityIndex">capability index inside the capability table</param>
/// <exception cref="InvalidOperationException"><list type="bullet">
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item></list>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception>
public new void LinkToCapability(int slot, uint capabilityIndex) => base.LinkToCapability(slot, capabilityIndex);
/// <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>
public new void SetStruct(ushort dataCount, ushort ptrCount) => base.SetStruct(dataCount, ptrCount);
/// <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>
public new void SetListOfValues(byte bitsPerElement, int totalCount) => base.SetListOfValues(bitsPerElement, totalCount);
/// <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>
public new void SetListOfPointers(int totalCount) => base.SetListOfPointers(totalCount);
/// <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>
public new void SetListOfStructs(int totalCount, ushort dataCount, ushort ptrCount) => base.SetListOfStructs(totalCount, dataCount, ptrCount);
/// <summary>
/// Constructs the underlying object from the given representation.
/// </summary>
/// <param name="obj">Object representation. Must be one of the following:
/// <list type="bullet">
/// <item><description>An instance implementing <see cref="ICapnpSerializable"/></description></item>
/// <item><description>null</description></item>
/// <item><description>A <see cref="String"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Byte}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{SByte}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{UInt16}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Int16}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{UInt32}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Int64}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{UInt64}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Single}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Double}"/></description></item>
/// <item><description>A <see cref="IReadOnlyList{Boolean}/></description></item>
/// <item><description>A <see cref="IReadOnlyList{String}"/></description></item>
/// <item><description>Another <see cref="DeserializerState"/></description></item>
/// <item><description>Another <see cref="SerializerState"/></description></item>
/// <item><description>Low-level capability object (<see cref="Rpc.ConsumedCapability"/>)</description></item>
/// <item><description>Proxy object (<see cref="Rpc.Proxy"/>)</description></item>
/// <item><description>Skeleton object (<see cref="Rpc.Skeleton"/>)</description></item>
/// <item><description>Capability interface implementation</description></item>
/// <item><description>A <see cref="IReadOnlyList{Object}"/> whereby each list item is one of the things listed here.</description></item>
/// </list>
/// </param>
public void SetObject(object obj)
{
switch (obj)
{
case ICapnpSerializable serializable:
serializable.Serialize(this);
break;
case string s:
WriteText(s);
break;
case IReadOnlyList<byte> bytes:
Rewrap<ListOfPrimitivesSerializer<byte>>().Init(bytes);
break;
case IReadOnlyList<sbyte> sbytes:
Rewrap<ListOfPrimitivesSerializer<sbyte>>().Init(sbytes);
break;
case IReadOnlyList<ushort> ushorts:
Rewrap<ListOfPrimitivesSerializer<ushort>>().Init(ushorts);
break;
case IReadOnlyList<short> shorts:
Rewrap<ListOfPrimitivesSerializer<short>>().Init(shorts);
break;
case IReadOnlyList<uint> uints:
Rewrap<ListOfPrimitivesSerializer<uint>>().Init(uints);
break;
case IReadOnlyList<int> ints:
Rewrap<ListOfPrimitivesSerializer<int>>().Init(ints);
break;
case IReadOnlyList<ulong> ulongs:
Rewrap<ListOfPrimitivesSerializer<ulong>>().Init(ulongs);
break;
case IReadOnlyList<long> longs:
Rewrap<ListOfPrimitivesSerializer<long>>().Init(longs);
break;
case IReadOnlyList<float> floats:
Rewrap<ListOfPrimitivesSerializer<float>>().Init(floats);
break;
case IReadOnlyList<double> doubles:
Rewrap<ListOfPrimitivesSerializer<double>>().Init(doubles);
break;
case IReadOnlyList<bool> bools:
Rewrap<ListOfBitsSerializer>().Init(bools);
break;
case IReadOnlyList<string> strings:
Rewrap<ListOfTextSerializer>().Init(strings);
break;
case IReadOnlyList<object> objects:
Rewrap<ListOfPointersSerializer<DynamicSerializerState>>().Init(objects, (s, o) => s.SetObject(o));
break;
case DeserializerState ds:
Reserializing.DeepCopy(ds, this);
break;
case SerializerState s:
Reserializing.DeepCopy(s, this);
break;
case null:
break;
default:
SetCapability(ProvideCapability(obj));
break;
}
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Capnp
{
/// <summary>
/// Implements an empty <see cref="IReadOnlyList{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public class EmptyList<T> : IReadOnlyList<T>
{
/// <summary>
/// Always throws an <see cref="ArgumentOutOfRangeException"/>.
/// </summary>
/// <param name="index">Ignored</param>
public T this[int index] => throw new ArgumentOutOfRangeException(nameof(index));
/// <summary>
/// Always 0.
/// </summary>
public int Count => 0;
/// <summary>
/// Returns an empty enumerator.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
return Enumerable.Empty<T>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for empty lists.
/// </summary>
public class EmptyListDeserializer : ListDeserializer
{
/// <summary>
/// Always ListKind.ListOfEmpty (despite the fact the empty list != List(Void)
/// </summary>
public override ListKind Kind => ListKind.ListOfEmpty;
/// <summary>
/// Returns am empty <see cref="IReadOnlyList{T}"/>.
/// </summary>
/// <typeparam name="T">Element ype</typeparam>
/// <param name="cons">Ignored</param>
public override IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons) => new EmptyList<T>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Boolean}"/>.
/// </summary>
public override IReadOnlyList<bool> CastBool() => new EmptyList<bool>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Byte}"/>.
/// </summary>
public override IReadOnlyList<byte> CastByte() => new EmptyList<byte>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Double}"/>.
/// </summary>
public override IReadOnlyList<double> CastDouble() => new EmptyList<double>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Single}"./>
/// </summary>
public override IReadOnlyList<float> CastFloat() => new EmptyList<float>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Int32}"/>.
/// </summary>
public override IReadOnlyList<int> CastInt() => new EmptyList<int>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{ListDeserializer}"/>.
/// </summary>
public override IReadOnlyList<ListDeserializer> CastList() => new EmptyList<ListDeserializer>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Int64}"/>.
/// </summary>
public override IReadOnlyList<long> CastLong() => new EmptyList<long>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{SByte}"/>.
/// </summary>
public override IReadOnlyList<sbyte> CastSByte() => new EmptyList<sbyte>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{Int16}"/>.
/// </summary>
public override IReadOnlyList<short> CastShort() => new EmptyList<short>();
/// <summary>
/// Returns an empty string.
/// </summary>
public override string CastText() => string.Empty;
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{UInt32}"/>.
/// </summary>
public override IReadOnlyList<uint> CastUInt() => new EmptyList<uint>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{UInt64}"/>.
/// </summary>
public override IReadOnlyList<ulong> CastULong() => new EmptyList<ulong>();
/// <summary>
/// Returns an empty <see cref="IReadOnlyList{UInt16}"/>.
/// </summary>
public override IReadOnlyList<ushort> CastUShort() => new EmptyList<ushort>();
}
}

View File

@ -0,0 +1,206 @@
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp
{
/// <summary>
/// The FramePump handles sending and receiving Cap'n Proto messages over a stream. It exposes a Send method for writing frames to the stream, and an
/// event handler for processing received frames. It does not fork any new thread by itself, but instead exposes a synchronous blocking Run method that
/// implements the receive loop. Invoke this method in the thread context of your choice.
/// </summary>
public class FramePump: IDisposable
{
ILogger Logger { get; } = Logging.CreateLogger<FramePump>();
int _disposing;
readonly Stream _stream;
readonly BinaryWriter _writer;
readonly object _writeLock = new object();
/// <summary>
/// Constructs a new instance for given stream.
/// </summary>
/// <param name="stream">The stream for message I/O.
/// If you intend to receive messages, the stream must support reading (CanRead).
/// If you intend to send messages, the stream must support writing (CanWrite).
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
public FramePump(Stream stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
if (stream.CanWrite)
_writer = new BinaryWriter(_stream);
}
/// <summary>
/// Disposes this instance and the underlying stream. This will also cause the Run method to return.
/// </summary>
public void Dispose()
{
if (0 == Interlocked.Exchange(ref _disposing, 1))
{
_writer?.Dispose();
_stream.Dispose();
}
}
/// <summary>
/// Event handler for frame reception.
/// </summary>
public event Action<WireFrame> FrameReceived;
/// <summary>
/// Sends a message over the stream.
/// </summary>
/// <param name="frame">Message to be sent</param>
/// <exception cref="InvalidOperationException">The underlying stream does not support writing.</exception>
/// <exception cref="ArgumentException">The message does not provide at least one segment, or one of its segments is empty.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
/// <exception cref="ObjectDisposedException">This instance or stream is diposed.</exception>
public void Send(WireFrame frame)
{
if (_writer == null)
throw new InvalidOperationException("Stream is not writable");
if (frame.Segments == null)
throw new ArgumentException("Do not pass default(WireFrame)");
if (frame.Segments.Count == 0)
throw new ArgumentException("Expected at least one segment");
foreach (var segment in frame.Segments)
{
if (segment.Length == 0)
throw new ArgumentException("Segment must not have zero length");
}
lock (_writeLock)
{
_writer.Write(frame.Segments.Count - 1);
foreach (var segment in frame.Segments)
{
_writer.Write(segment.Length);
}
if ((frame.Segments.Count & 1) == 0)
{
// Padding
_writer.Write(0);
}
foreach (var segment in frame.Segments)
{
var bytes = MemoryMarshal.Cast<ulong, byte>(segment.Span);
_writer.Write(bytes);
}
}
}
long _waitingForData;
/// <summary>
/// Whether the pump is currently waiting for data to receive.
/// </summary>
public bool IsWaitingForData
{
get => Interlocked.Read(ref _waitingForData) != 0;
private set => Interlocked.Exchange(ref _waitingForData, value ? 1 : 0);
}
/// <summary>
/// Synchronously runs the frame reception loop. Will only return after calling Dispose() or upon error condition.
/// The method does not propagate EndOfStreamException or ObjectDisposedException to the caller, since these conditions are considered
/// to be part of normal operation. It does pass exceptions which arise due to I/O errors or invalid data.
/// </summary>
/// <exception cref="ArgumentException">The underlying stream does not support reading or is already closed.</exception>
/// <exception cref="OverflowException">Received invalid data.</exception>
/// <exception cref="OutOfMemoryException">Received a message with too many or too big segments, probably dues to invalid data.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public void Run()
{
try
{
using (var reader = new BinaryReader(_stream, Encoding.Default))
{
while (true)
{
IsWaitingForData = true;
uint scountm = reader.ReadUInt32();
int scount = checked((int)(scountm + 1));
var buffers = new Memory<ulong>[scount];
for (uint i = 0; i < scount; i++)
{
int size = checked((int)reader.ReadUInt32());
// This implementation will never send empty segments.
// Other implementations should not. An empty segment may also
// indicate and end-of-stream (stream closed) condition.
if (size == 0)
{
Logger.LogInformation("Received zero-sized segment, stopping interaction");
return;
}
buffers[i] = new Memory<ulong>(new ulong[size]);
}
if ((scount & 1) == 0)
{
// Padding
reader.ReadUInt32();
}
for (uint i = 0; i < scount; i++)
{
var buffer = MemoryMarshal.Cast<ulong, byte>(buffers[i].Span);
int got = reader.Read(buffer);
if (got != buffer.Length)
{
Logger.LogWarning("Received incomplete frame");
throw new EndOfStreamException("Expected more bytes according to framing header");
}
}
IsWaitingForData = false;
FrameReceived?.Invoke(new WireFrame(new ArraySegment<Memory<ulong>>(buffers, 0, scount)));
}
}
}
catch (EndOfStreamException)
{
}
catch (ObjectDisposedException)
{
}
catch (Exception exception)
{
// When disposing, all kinds of errors might happen here,
// not worth logging.
if (_disposing == 0)
{
Logger.LogWarning(exception.Message);
}
}
finally
{
IsWaitingForData = false;
}
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Capnp
{
/// <summary>
/// Supports the deserialization of Cap'n Proto messages from a stream (see https://capnproto.org/encoding.html#serialization-over-a-stream).
/// Packing and compression cannot be handled yet.
/// </summary>
public static class Framing
{
/// <summary>
/// Deserializes a message from given stream.
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <returns>The deserialized message</returns>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <exception cref="ArgumentException">The stream does not support reading, is null, or is already closed.</exception>
/// <exception cref="EndOfStreamException">The end of the stream is reached.</exception>
/// <exception cref="ObjectDisposedException">The stream is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
/// <exception cref="OverflowException">Encountered invalid framing data.</exception>
/// <exception cref="OutOfMemoryException">Too many or too large segments, probably due to invalid framing data.</exception>
public static WireFrame ReadSegments(Stream stream)
{
using (var reader = new BinaryReader(stream, Encoding.Default, true))
{
uint scountm = reader.ReadUInt32();
uint scount = checked(scountm + 1);
var buffers = new Memory<ulong>[scount];
for (uint i = 0; i < scount; i++)
{
uint size = reader.ReadUInt32();
buffers[i] = new Memory<ulong>(new ulong[size]);
}
if ((scount & 1) == 0)
{
// Padding
reader.ReadUInt32();
}
for (uint i = 0; i < scount; i++)
{
var buffer = MemoryMarshal.Cast<ulong, byte>(buffers[i].Span);
if (reader.Read(buffer) != buffer.Length)
{
throw new EndOfStreamException("Expected more bytes according to framing header");
}
}
return new WireFrame(buffers);
}
}
}
}

View File

@ -0,0 +1,21 @@
namespace Capnp
{
/// <summary>
/// This interface is intended to be implemented by schema-generated domain classes which support deserialization from
/// a <see cref="DeserializerState"/> and serialization to a <see cref="SerializerState"/>.
/// </summary>
public interface ICapnpSerializable
{
/// <summary>
/// Serializes the implementation's current state to a serializer state.
/// </summary>
/// <param name="state">Target serializer state</param>
void Serialize(SerializerState state);
/// <summary>
/// Deserializes the implementation's state from a deserializer state.
/// </summary>
/// <param name="state">Source deserializer state</param>
void Deserialize(DeserializerState state);
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// Implements a segment allocation policy for Cap'n Proto building messages.
/// </summary>
public interface ISegmentAllocator
{
/// <summary>
/// Currently allocated segments.
/// </summary>
IReadOnlyList<Memory<ulong>> Segments { get; }
/// <summary>
/// Attempts to allocate a memory block. The first allocation attempt is made inside the segment specified by <paramref name="preferredSegment"/>.
/// If that segment does not provide enough space or does not exist, further actions depend on the <paramref name="forcePreferredSegment"/> flag.
/// If that flag is true, allocation will fail (return false). Otherwise, the allocation shall scan existing segments for the requested amount of space,
/// and create a new segment if none provides enough space.
/// </summary>
/// <param name="nwords">Number of words to allocate</param>
/// <param name="preferredSegment">Index of preferred segment wherein the block should be allocated</param>
/// <param name="slice">Position of allocated memory block (undefined in case of failure)</param>
/// <param name="forcePreferredSegment">Whether the segment specified by <paramref name="preferredSegment"/> is mandatory</param>
/// <returns>Whether allocation was successful</returns>
bool Allocate(uint nwords, uint preferredSegment, out SegmentSlice slice, bool forcePreferredSegment);
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace Capnp
{
/// <summary>
/// An implementations of this interface represents a struct which is being deserialized from a Cap'n Proto object.
/// </summary>
public interface IStructDeserializer
{
/// <summary>
/// Reads a slice of up to 64 bits from this struct's data section, starting from the specified bit offset.
/// The slice must be aligned within a 64 bit word boundary.
/// </summary>
/// <param name="bitOffset">Start bit offset relative to the data section, little endian</param>
/// <param name="bitCount">numbers of bits to read</param>
/// <returns>the data</returns>
/// <exception cref="ArgumentOutOfRangeException">non-aligned access</exception>
/// <exception cref="IndexOutOfRangeException">bitOffset exceeds the data section</exception>
/// <exception cref="DeserializationException">this state does not represent a struct</exception>
ulong StructReadData(ulong bitOffset, int bitCount);
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace Capnp
{
/// <summary>
/// An implementations of this interface represents a struct which is being serialized as Cap'n Proto object.
/// </summary>
public interface IStructSerializer: IStructDeserializer
{
/// <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="data">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>
void StructWriteData(ulong bitOffset, int bitCount, ulong data);
/// <summary>
/// The struct's data section as memory span.
/// </summary>
Span<ulong> StructDataSection { get; }
}
}

View File

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// Base class for interpreting a <see cref="DeserializerState"/> as List(T).
/// </summary>
public abstract class ListDeserializer
{
static class GenericCasts<T>
{
public static Func<ListDeserializer, T> CastFunc;
}
static ListDeserializer()
{
GenericCasts<IReadOnlyList<bool>>.CastFunc = _ => _.CastBool();
GenericCasts<IReadOnlyList<sbyte>>.CastFunc = _ => _.CastSByte();
GenericCasts<IReadOnlyList<byte>>.CastFunc = _ => _.CastByte();
GenericCasts<IReadOnlyList<short>>.CastFunc = _ => _.CastShort();
GenericCasts<IReadOnlyList<ushort>>.CastFunc = _ => _.CastUShort();
GenericCasts<IReadOnlyList<int>>.CastFunc = _ => _.CastInt();
GenericCasts<IReadOnlyList<uint>>.CastFunc = _ => _.CastUInt();
GenericCasts<IReadOnlyList<long>>.CastFunc = _ => _.CastLong();
GenericCasts<IReadOnlyList<ulong>>.CastFunc = _ => _.CastULong();
GenericCasts<IReadOnlyList<float>>.CastFunc = _ => _.CastFloat();
GenericCasts<IReadOnlyList<double>>.CastFunc = _ => _.CastDouble();
GenericCasts<string>.CastFunc = _ => _.CastText();
}
/// <summary>
/// Underlying deserializer state
/// </summary>
protected readonly DeserializerState State;
internal ListDeserializer(ref DeserializerState state)
{
State = state;
}
internal ListDeserializer()
{
}
T Cast<T>()
{
var func = GenericCasts<T>.CastFunc;
if (func == null)
throw new NotSupportedException("Requested cast is not supported");
return func(this);
}
/// <summary>
/// This list's element count
/// </summary>
public int Count => State.ListElementCount;
/// <summary>
/// The list's element category
/// </summary>
public abstract ListKind Kind { get; }
/// <summary>
/// Represents this list by applying a selector function to each element's deserializer state.
/// This operator is only supported by certain specializations.
/// </summary>
/// <typeparam name="T">Target element type</typeparam>
/// <param name="cons">Selector function</param>
/// <returns>The desired representation</returns>
public abstract IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons);
/// <summary>
/// Represents this list as a list of lists.
/// </summary>
/// <returns>The list of lists representation, each element being a <see cref="ListDeserializer"/> on its own.</returns>
/// <exception cref="NotSupportedException">If this kind of list cannot be represented as list of lists (because it is a list of non-pointers)</exception>
public virtual IReadOnlyList<ListDeserializer> CastList()
{
throw new NotSupportedException("This kind of list does not contain nested lists");
}
/// <summary>
/// Represents this list as a list of capabilities.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <returns>Capability list representation</returns>
/// <exception cref="NotSupportedException">If this kind of list cannot be represented as list of capabilities (because it is a list of non-pointers)</exception>
/// <exception cref="Rpc.InvalidCapabilityInterfaceException">If <typeparamref name="T"/> does not qualify as capability interface.</exception>
public virtual IReadOnlyList<ListOfCapsDeserializer<T>> CastCapList<T>() where T: class
{
throw new NotSupportedException("This kind of list does not contain nested lists");
}
object CastND(int n, Func<ListDeserializer, object> func)
{
if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n));
for (int i = 1; i < n; i++)
{
var copy = func; // This copy assignment is intentional. Try to optimize it away and be amazed!
func = ld => ld.CastList().LazyListSelect(copy);
}
return func(this);
}
/// <summary>
/// Represents this list as n-dimensional list of T, with T being a primitive type.
/// </summary>
/// <typeparam name="T">Element type, must be primitive</typeparam>
/// <param name="n">Number of dimensions</param>
/// <returns>The desired representation as <![CDATA[IReadOnlyList<...IReadOnlyList<T>>]]></returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is less than or equal to 0</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public object CastND<T>(int n) => CastND(n, ld => ld.Cast<IReadOnlyList<T>>());
/// <summary>
/// Represents this list as n-dimensional list of T, with T being any type.
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <param name="n">Number of dimensions</param>
/// <param name="cons">Selector function which constructs an instance of <typeparamref name="T"/> from a <see cref="DeserializerState"/></param>
/// <returns>The desired representation as <![CDATA[IReadOnlyList<...IReadOnlyList<T>>]]></returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is less than or equals 0.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public object CastND<T>(int n, Func<DeserializerState, T> cons) => CastND(n, ld => ld.Cast(cons));
/// <summary>
/// Represents this list as n-dimensional list of enums.
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="n">Number of dimensions</param>
/// <param name="cons">Cast function which converts ushort value to enum value</param>
/// <returns>The desired representation as <![CDATA[IReadOnlyList<...IReadOnlyList<T>>]]></returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is less than or equals 0.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public object CastEnumsND<T>(int n, Func<ushort, T> cons) => CastND(n, ld => ld.CastEnums(cons));
/// <summary>
/// Represents this list as n-dimensional List(...List(Void))
/// </summary>
/// <param name="n">Number of dimensions</param>
/// <returns>The desired representation as <![CDATA[IReadOnlyList<...IReadOnlyList<T>>]]></returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is less than or equals 0</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public object CastVoidND(int n) => CastND(n, (ListDeserializer ld) => ld.Count);
/// <summary>
/// Represents this list as "matrix" (jagged array) with primitive element type.
/// </summary>
/// <typeparam name="T">Element type, must be primitive</typeparam>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<T>> Cast2D<T>()
{
return CastList().LazyListSelect(ld => ld.Cast<IReadOnlyList<T>>());
}
/// <summary>
/// Represents this list as List(Data).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<byte>> CastData() => Cast2D<byte>();
/// <summary>
/// Represents this list as "matrix" (jagged array) with complex element type.
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <param name="cons">Selector function which constructs an instance of <typeparamref name="T"/> from a <see cref="DeserializerState"/></param>
/// <returns>The desired representation</returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<T>> Cast2D<T>(Func<DeserializerState, T> cons)
{
return CastList().LazyListSelect(ld => ld.Cast(cons));
}
/// <summary>
/// Represents this list as "matrix" (jagged array) of enum-typed elements.
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="cons">Cast function which converts ushort value to enum value</param>
/// <returns>The desired representation</returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<T>> CastEnums2D<T>(Func<ushort, T> cons)
{
return CastList().LazyListSelect(ld => ld.CastEnums(cons));
}
/// <summary>
/// Represents this list as 3-dimensional jagged array with primitive element type.
/// </summary>
/// <typeparam name="T">Element type, must be primitive</typeparam>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<IReadOnlyList<T>>> Cast3D<T>()
{
return CastList().LazyListSelect(ld => ld.Cast2D<T>());
}
/// <summary>
/// Represents this list as 3-dimensional jagged array with complex element type.
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <param name="cons">Selector function which constructs an instance of <typeparamref name="T"/> from a <see cref="DeserializerState"/></param>
/// <returns>The desired representation</returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<IReadOnlyList<T>>> Cast3D<T>(Func<DeserializerState, T> cons)
{
return CastList().LazyListSelect(ld => ld.Cast2D(cons));
}
/// <summary>
/// Represents this list as 3-dimensional jagged array of enum-typed elements.
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="cons">Cast function which converts ushort value to enum value</param>
/// <returns>The desired representation</returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<IReadOnlyList<T>>> CastEnums3D<T>(Func<ushort, T> cons)
{
return CastList().LazyListSelect(ld => ld.CastEnums2D(cons));
}
/// <summary>
/// Represents this list as list of enum-typed elements.
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="cons">Cast function which converts ushort value to enum value</param>
/// <returns>The desired representation</returns>
/// <exception cref="ArgumentNullException"><paramref name="cons"/> is null.</exception>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<T> CastEnums<T>(Func<ushort, T> cons)
{
return CastUShort().LazyListSelect(cons);
}
/// <summary>
/// Represents this list as List(Void), which boils down to returning the number of elements.
/// </summary>
/// <returns>The List(Void) representation which is nothing but the list's element count.</returns>
public int CastVoid() => Count;
/// <summary>
/// Represents this list as List(List(Void)), which boils down to returning a list of element counts.
/// </summary>
/// <returns>A list of integers whereby each number equals the sublist's element count.</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<int> CastVoid2D() => CastList().LazyListSelect(ld => ld.Count);
/// <summary>
/// Represents this list as List(List(List(Void))).
/// </summary>
/// <returns>The List(List(List(Void))) representation which is in turn a 2-dimensional jagged array of element counts.</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<int>> CastVoid3D() => CastList().LazyListSelect(ld => ld.CastVoid2D());
/// <summary>
/// Represents this list as List(Text). For representing it as Text, use <seealso cref="CastText"/>.
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<string> CastText2() => CastList().LazyListSelect(ld => ld.CastText());
/// <summary>
/// Represents this list as Text. For representing it as List(Text), use <seealso cref="CastText2"/>.
/// </summary>
/// <remarks>
/// Did you notice that the naming pattern is broken here? For every other CastX method, X depicts the element type.
/// CastX actually means "represent this list as list of X". Logically, the semantics of CastText should be the semantics
/// implemented by CastText2. And this method's name should be "CastChar". This wouldn't be accurate either, since a string
/// is semantically more than the list of its characters. Trying to figure out a consistent naming pattern, we'd probably
/// end up in less concise method names (do you have a good suggestion?). Considering this and the fact that you probably
/// won't use these methods directly (because the code generator will produce nice wrappers for you) it seems acceptable to
/// live with the asymmetric and somewhat ugly naming.
/// </remarks>
/// <returns>The decoded text</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual string CastText()
{
throw new NotSupportedException("This kind of list does not represent text");
}
/// <summary>
/// Represents this list as List(Bool).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<bool> CastBool()
{
return Cast(sd => sd.ReadDataBool(0));
}
/// <summary>
/// Represents this list as List(Int8).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<sbyte> CastSByte()
{
return Cast(sd => sd.ReadDataSByte(0));
}
/// <summary>
/// Represents this list as List(UInt8).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<byte> CastByte()
{
return Cast(sd => sd.ReadDataByte(0));
}
/// <summary>
/// Represents this list as List(Int16).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<short> CastShort()
{
return Cast(sd => sd.ReadDataShort(0));
}
/// <summary>
/// Represents this list as List(UInt16).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<ushort> CastUShort()
{
return Cast(sd => sd.ReadDataUShort(0));
}
/// <summary>
/// Represents this list as List(Int32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<int> CastInt()
{
return Cast(sd => sd.ReadDataInt(0));
}
/// <summary>
/// Represents this list as List(UInt32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<uint> CastUInt()
{
return Cast(sd => sd.ReadDataUInt(0));
}
public virtual IReadOnlyList<long> CastLong()
/// <summary>
/// Represents this list as List(Int64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
{
return Cast(sd => sd.ReadDataLong(0));
}
/// <summary>
/// Represents this list as List(UInt64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<ulong> CastULong()
{
return Cast(sd => sd.ReadDataULong(0));
}
/// <summary>
/// Represents this list as List(Float32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<float> CastFloat()
{
return Cast(sd => sd.ReadDataFloat(0));
}
/// <summary>
/// Represents this list as List(Float64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual IReadOnlyList<double> CastDouble()
{
return Cast(sd => sd.ReadDataDouble(0));
}
}
}

View File

@ -0,0 +1,48 @@
namespace Capnp
{
/// <summary>
/// Enumerates the list element categories which are defined by Cap'n Proto.
/// </summary>
public enum ListKind: byte
{
/// <summary>
/// List(Void)
/// </summary>
ListOfEmpty = 0,
/// <summary>
/// List(Bool)
/// </summary>
ListOfBits = 1,
/// <summary>
/// List(Int8) or List(UInt8)
/// </summary>
ListOfBytes = 2,
/// <summary>
/// List(Int16), List(UInt16), or List(Enum)
/// </summary>
ListOfShorts = 3,
/// <summary>
/// List(Int32), List(UInt32), or List(Float32)
/// </summary>
ListOfInts = 4,
/// <summary>
/// List(Int64), List(UInt64), or List(Float64)
/// </summary>
ListOfLongs = 5,
/// <summary>
/// A list of pointers
/// </summary>
ListOfPointers = 6,
/// <summary>
/// A list of fixed-size composites (i.e. structs)
/// </summary>
ListOfStructs = 7
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for a List(Bool).
/// </summary>
public class ListOfBitsDeserializer: ListDeserializer, IReadOnlyList<bool>
{
readonly bool _defaultValue;
internal ListOfBitsDeserializer(ref DeserializerState context, bool defaultValue) :
base(ref context)
{
_defaultValue = defaultValue;
}
/// <summary>
/// Always ListKind.ListOfBits
/// </summary>
public override ListKind Kind => ListKind.ListOfBits;
/// <summary>
/// Gets the element at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element value</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public bool this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
int wi = index / 64;
int bi = index % 64;
return ((State.CurrentSegment[State.Offset + wi] >> bi) & 1) !=
(_defaultValue ? 1u : 0);
}
}
IEnumerable<bool> Enumerate()
{
for (int i = 0; i < Count; i++)
yield return this[i];
}
/// <summary>
/// Implements <see cref="IEnumerable{bool}"/>
/// </summary>
/// <returns></returns>
public IEnumerator<bool> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Return this
/// </summary>
public override IReadOnlyList<bool> CastBool() => this;
/// <summary>
/// Always throws <see cref="NotSupportedException"/> since it is not intended to represent a list of bits differently.
/// </summary>
public override IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons)
{
throw new NotSupportedException("Cannot cast a list of bits to anything else");
}
}
}

View File

@ -0,0 +1,98 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for a List(Bool).
/// </summary>
public class ListOfBitsSerializer: SerializerState, IReadOnlyList<bool>
{
/// <summary>
/// Gets or sets the element at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element value</returns>
/// <exception cref="InvalidOperationException">List was not initialized, or attempting to overwrite a non-null element.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public bool this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
int wi = index / 64;
int bi = index % 64;
return ((RawData[wi] >> bi) & 1) != 0;
}
set
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
int wi = index / 64;
int bi = index % 64;
if (value)
RawData[wi] |= (1ul << bi);
else
RawData[wi] &= ~(1ul << bi);
}
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
SetListOfValues(1, count);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
public void Init(IReadOnlyList<bool> items)
{
if (items == null)
{
return;
}
Init(items.Count);
for (int i = 0; i < items.Count; i++)
{
this[i] = items[i];
}
}
/// <summary>
/// Implements <see cref="IEnumerable{Boolean}"/>
/// </summary>
public IEnumerator<bool> GetEnumerator() => (IEnumerator<bool>)this.ToArray().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.ToArray().GetEnumerator();
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for a list of capabilities.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
public class ListOfCapsDeserializer<T> : ListDeserializer, IReadOnlyList<T>
where T: class
{
internal ListOfCapsDeserializer(ref DeserializerState state) : base(ref state)
{
Rpc.CapabilityReflection.ValidateCapabilityInterface(typeof(T));
}
/// <summary>
/// Returns the capability at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>The capability at given index (in terms of its proxy instance)</returns>
public T this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
return Rpc.CapabilityReflection.CreateProxy<T>(State.DecodeCapPointer(index)) as T;
}
}
/// <summary>
/// Always ListKind.ListOfPointers
/// </summary>
public override ListKind Kind => ListKind.ListOfPointers;
/// <summary>
/// Always throws <see cref="NotSupportedException"/>, since it is not intended to convert a capability list to anything else.
/// </summary>
public override IReadOnlyList<T1> Cast<T1>(Func<DeserializerState, T1> cons)
{
throw new NotSupportedException("Cannot cast a list of capabilities to anything else");
}
IEnumerable<T> Enumerate()
{
int count = Count;
for (int i = 0; i < count; i++)
{
yield return this[i];
}
}
/// <summary>
/// Implements <see cref="IEnumerable{T}"./>
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for a list of capabilities.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
public class ListOfCapsSerializer<T> :
SerializerState,
IReadOnlyList<T>
where T : class
{
/// <summary>
/// Constructs an instance.
/// </summary>
/// <exception cref="Rpc.InvalidCapabilityInterfaceException"><typeparamref name="T"/> does not quality as capability interface.
/// The implementation might attempt to throw this exception earlier in the static constructor (during type load). Currently it doesn't
/// because there is a significant risk of messing something up and ending with a hard-to-debug <see cref="TypeLoadException"/>.</exception>
public ListOfCapsSerializer()
{
Rpc.CapabilityReflection.ValidateCapabilityInterface(typeof(T));
}
/// <summary>
/// Gets or sets the capability at given element index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Proxy object of capability at given element index</returns>
/// <exception cref="InvalidOperationException">List was not initialized, or attempting to overwrite an already set element.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public T this[int index]
{
get => Rpc.CapabilityReflection.CreateProxy<T>(DecodeCapPointer(index)) as T;
set
{
if (!IsAllocated)
throw new InvalidOperationException("Call Init() first");
if (index < 0 || index >= RawData.Length)
throw new IndexOutOfRangeException("index out of range");
uint id = ProvideCapability(value);
WirePointer ptr = default;
ptr.SetCapability(id);
RawData[index] = id;
}
}
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
SetListOfPointers(count);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <param name="caps">List content. Can be null in which case the list is simply not initialized.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
public void Init(IReadOnlyList<T> caps)
{
if (caps == null)
{
return;
}
Init(caps.Count);
for (int i = 0; i < caps.Count; i++)
{
this[i] = caps[i];
}
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
IEnumerable<T> Enumerate()
{
int count = Count;
for (int i = 0; i < count; i++)
yield return this[i];
}
/// <summary>
/// Implements <see cref="IEnumerable{T}"./>
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for List(Void).
/// </summary>
public class ListOfEmptyDeserializer : ListDeserializer, IReadOnlyList<DeserializerState>
{
internal ListOfEmptyDeserializer(ref DeserializerState state) :
base(ref state)
{
}
/// <summary>
/// Returns a DeserializerState representing an element at given index.
/// This is always the null object, since Void cannot carry any data.
/// </summary>
/// <param name="index">Element index</param>
/// <returns><code>default(DeserializerState)</code></returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public DeserializerState this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
return default;
}
}
/// <summary>
/// Always ListKind.ListOfEmpty
/// </summary>
public override ListKind Kind => ListKind.ListOfEmpty;
/// <summary>
/// Applies a selector function to each element.
/// Trivia: Since each element is the null object, the selector function always gets fed with a null object.
/// </summary>
/// <typeparam name="T">Element target type</typeparam>
/// <param name="cons">Selector function</param>
/// <returns>The desired representation</returns>
public override IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons)
{
return this.LazyListSelect(cons);
}
/// <summary>
/// Implements <see cref="IEnumerable{DeserializerState}"/>.
/// </summary>
public IEnumerator<DeserializerState> GetEnumerator()
{
return Enumerable.Repeat(default(DeserializerState), Count).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,33 @@
using System;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for List(Void).
/// </summary>
public class ListOfEmptySerializer:
SerializerState
{
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
SetListOfValues(0, count);
}
}
}

View File

@ -0,0 +1,91 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for List(T) when T is unknown (generic), List(Data), List(Text), and List(List(...)).
/// </summary>
public class ListOfPointersDeserializer: ListDeserializer, IReadOnlyList<DeserializerState>
{
internal ListOfPointersDeserializer(ref DeserializerState state) :
base(ref state)
{
}
/// <summary>
/// Always <code>ListKind.ListOfPointers</code>
/// </summary>
public override ListKind Kind => ListKind.ListOfPointers;
/// <summary>
/// Gets the DeserializerState representing the element at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>DeserializerState representing the element at given index</returns>
public DeserializerState this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
var state = State;
state.DecodePointer(index);
return state;
}
}
IEnumerable<DeserializerState> Enumerate()
{
for (int i = 0; i < Count; i++)
yield return this[i];
}
/// <summary>
/// Implements <see cref="IEnumerable{DeserializerState}"/>.
/// </summary>
public IEnumerator<DeserializerState> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Applies a selector function to each element.
/// </summary>
/// <typeparam name="T">Element target type</typeparam>
/// <param name="cons">Selector function</param>
/// <returns>The desired representation</returns>
public override IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons)
{
return this.LazyListSelect(cons);
}
/// <summary>
/// Interprets this instance as List(List(...)).
/// </summary>
/// <returns>The desired representation. Since it is evaluated lazily, type conflicts will not happen before accessing the resulting list's elements.</returns>
public override IReadOnlyList<ListDeserializer> CastList()
{
return this.LazyListSelect(d => d.RequireList());
}
/// <summary>
/// Interprets this instance as a list of capabilities.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <returns>The desired representation. Since it is evaluated lazily, type conflicts will not happen before accessing the resulting list's elements.</returns>
public override IReadOnlyList<ListOfCapsDeserializer<T>> CastCapList<T>()
{
return this.LazyListSelect(d => d.RequireCapList<T>());
}
}
}

View File

@ -0,0 +1,118 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for List(T) when T is unknown (generic), List(Data), List(Text), and List(List(...)).
/// </summary>
/// <typeparam name="TS">SerializerState which represents the element type</typeparam>
public class ListOfPointersSerializer<TS>:
SerializerState,
IReadOnlyList<TS>
where TS: SerializerState, new()
{
/// <summary>
/// Gets or sets the element at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Serializer state representing the desired element</returns>
/// <exception cref="InvalidOperationException">List was not initialized, or attempting to overwrite a non-null element.</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public TS this[int index]
{
get
{
if (!IsAllocated)
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
return BuildPointer<TS>(index);
}
set
{
if (!IsAllocated)
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
Link(index, value, true);
}
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
IEnumerable<TS> Enumerate()
{
int count = Count;
for (int i = 0; i < count; i++)
{
yield return TryGetPointer<TS>(i);
}
}
/// <summary>
/// Implements <see cref="IEnumerable{TS}"/>.
/// </summary>
public IEnumerator<TS> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
SetListOfPointers(count);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
/// <param name="init">Serialization action to transfer a particular item into the serializer state.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
public void Init<T>(IReadOnlyList<T> items, Action<TS, T> init)
{
if (items == null)
{
return;
}
Init(items.Count);
for (int i = 0; i < items.Count; i++)
{
init(this[i], items[i]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,234 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for List(Int*), List(UInt*), List(Float*), and List(Enum).
/// </summary>
/// <typeparam name="T">List element type</typeparam>
public class ListOfPrimitivesDeserializer<T>: ListDeserializer, IReadOnlyList<T>
where T: struct
{
class ListOfULongAsStructView<U> : IReadOnlyList<U>
{
readonly ListOfPrimitivesDeserializer<ulong> _lpd;
readonly Func<DeserializerState, U> _sel;
public ListOfULongAsStructView(ListOfPrimitivesDeserializer<ulong> lpd, Func<DeserializerState, U> sel)
{
_lpd = lpd;
_sel = sel;
}
public U this[int index]
{
get
{
var state = _lpd.State;
if (index < 0 || index >= _lpd.Count)
throw new ArgumentOutOfRangeException(nameof(index));
state.Offset += index;
state.Kind = ObjectKind.Struct;
state.StructDataCount = 1;
state.StructPtrCount = 0;
return _sel(state);
}
}
public int Count => _lpd.Count;
IEnumerable<U> Enumerate()
{
var state = _lpd.State;
state.Kind = ObjectKind.Struct;
state.StructDataCount = 1;
state.StructPtrCount = 0;
for (int i = 0; i < Count; i++)
{
yield return _sel(state);
++state.Offset;
}
}
public IEnumerator<U> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
readonly ListKind _kind;
internal ListOfPrimitivesDeserializer(ref DeserializerState state, ListKind kind) :
base(ref state)
{
_kind = kind;
var binCoder = PrimitiveCoder.Get<T>();
}
/// <summary>
/// One of ListOfBytes, ListOfShorts, ListOfInts, ListOfLongs.
/// </summary>
public override ListKind Kind => _kind;
ReadOnlySpan<T> Data => MemoryMarshal.Cast<ulong, T>(State.CurrentSegment.Slice(State.Offset)).Slice(0, Count);
/// <summary>
/// Returns the element at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element value</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public T this[int index] => Data[index];
ListOfPrimitivesDeserializer<U> PrimitiveCast<U>() where U: struct
{
if (Marshal.SizeOf<U>() != Marshal.SizeOf<T>())
throw new NotSupportedException("Source and target types have different sizes, cannot cast");
var stateCopy = State;
return new ListOfPrimitivesDeserializer<U>(ref stateCopy, Kind);
}
/// <summary>
/// Always throws <see cref="NotSupportedException"/> because this specialization can never represent a List(bool).
/// </summary>
public override IReadOnlyList<bool> CastBool() => throw new NotSupportedException("Cannot cast to list of bits");
/// <summary>
/// Attempts to interpret this instance as List(UInt8).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 1 byte.</exception>
public override IReadOnlyList<byte> CastByte() => PrimitiveCast<byte>();
/// <summary>
/// Attempts to interpret this instance as List(Int8).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 1 byte.</exception>
public override IReadOnlyList<sbyte> CastSByte() => PrimitiveCast<sbyte>();
/// <summary>
/// Attempts to interpret this instance as List(UInt16).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 2 bytes.</exception>
public override IReadOnlyList<ushort> CastUShort() => PrimitiveCast<ushort>();
/// <summary>
/// Attempts to interpret this instance as List(Int16).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 2 bytes.</exception>
public override IReadOnlyList<short> CastShort() => PrimitiveCast<short>();
/// <summary>
/// Attempts to interpret this instance as List(UInt32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 4 bytes.</exception>
public override IReadOnlyList<uint> CastUInt() => PrimitiveCast<uint>();
/// <summary>
/// Attempts to interpret this instance as List(Int32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 4 bytes.</exception>
public override IReadOnlyList<int> CastInt() => PrimitiveCast<int>();
/// <summary>
/// Attempts to interpret this instance as List(UInt64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 8 bytes.</exception>
public override IReadOnlyList<ulong> CastULong() => PrimitiveCast<ulong>();
/// <summary>
/// Attempts to interpret this instance as List(Int64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 8 bytes.</exception>
public override IReadOnlyList<long> CastLong() => PrimitiveCast<long>();
/// <summary>
/// Attempts to interpret this instance as List(Float32).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 4 bytes.</exception>
public override IReadOnlyList<float> CastFloat() => PrimitiveCast<float>();
/// <summary>
/// Attempts to interpret this instance as List(Float64).
/// </summary>
/// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">Element size is different from 8 bytes.</exception>
public override IReadOnlyList<double> CastDouble() => PrimitiveCast<double>();
/// <summary>
/// Attempts to interpret this instance as List(U) whereby U is a struct, applying a selector function to each element.
/// </summary>
/// <param name="cons">Selector function</param>
/// <returns>The desired representation</returns>
public override IReadOnlyList<U> Cast<U>(Func<DeserializerState, U> cons)
{
switch (Marshal.SizeOf<T>())
{
case 1: return PrimitiveCast<byte>().LazyListSelect(x => cons(DeserializerState.MakeValueState(x)));
case 2: return PrimitiveCast<ushort>().LazyListSelect(x => cons(DeserializerState.MakeValueState(x)));
case 4: return PrimitiveCast<uint>().LazyListSelect(x => cons(DeserializerState.MakeValueState(x)));
case 8: return new ListOfULongAsStructView<U>(PrimitiveCast<ulong>(), cons);
default:
throw new InvalidProgramException("This program path should not be reachable");
}
}
/// <summary>
/// Attempts to interpret this instance as Text and returns the string representation.
/// </summary>
/// <returns>The decoded string</returns>
/// <exception cref="NotSupportedException">Element size is different from 1 byte.</exception>
public override string CastText()
{
var utf8Bytes = PrimitiveCast<byte>().Data;
if (utf8Bytes.Length == 0) return string.Empty;
var utf8GytesNoZterm = utf8Bytes.Slice(0, utf8Bytes.Length - 1);
return Encoding.UTF8.GetString(utf8GytesNoZterm);
}
IEnumerable<T> Enumerate()
{
for (int i = 0; i < Count; i++)
yield return this[i];
}
/// <summary>
/// Implements <see cref="IEnumerable{T}"/>.
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for List(Int*), List(UInt*), List(Float*), and List(Enum).
/// </summary>
/// <typeparam name="T">List element type, must be primitive. Static constructor will throw if the type does not work.</typeparam>
public class ListOfPrimitivesSerializer<T> :
SerializerState,
IReadOnlyList<T>
where T : struct
{
static readonly int ElementSize;
static ListOfPrimitivesSerializer()
{
if (typeof(T).IsEnum)
{
ElementSize = Marshal.SizeOf(Enum.GetUnderlyingType(typeof(T)));
}
else
{
ElementSize = Marshal.SizeOf<T>();
}
}
Span<T> Data => MemoryMarshal.Cast<ulong, T>(RawData);
/// <summary>
/// Gets or sets the value at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element value</returns>
public T this[int index]
{
get => Data[index];
set => Data[index] = value;
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
SetListOfValues((byte)(8 * ElementSize), count);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
public void Init(IReadOnlyList<T> items)
{
if (items == null)
{
return;
}
Init(items.Count);
for (int i = 0; i < items.Count; i++)
{
this[i] = items[i];
}
}
/// <summary>
/// Implements <see cref="IEnumerable{T}"/>.
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator() => (IEnumerator<T>)Data.ToArray().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Data.ToArray().GetEnumerator();
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Capnp
{
/// <summary>
/// ListDeserializer specialization for List(T) when T is a known struct (i.e. a list of fixed-width composites).
/// </summary>
public class ListOfStructsDeserializer: ListDeserializer, IReadOnlyList<DeserializerState>
{
internal ListOfStructsDeserializer(ref DeserializerState context):
base(ref context)
{
}
/// <summary>
/// Always returns <code>ListKind.ListOfStructs</code>.
/// </summary>
public override ListKind Kind => ListKind.ListOfStructs;
/// <summary>
/// Returns the deserializer state at given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>Element deserializer state</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
/// <exception cref="DeserializationException">Traversal limit reached</exception>
public DeserializerState this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
int stride = State.StructDataCount + State.StructPtrCount;
var state = State;
// the “traversal limit” should count a list of zero-sized elements as if each element were one word instead.
state.IncrementBytesTraversed(checked(8u * (uint)(stride == 0 ? 1 : stride)));
state.Offset = checked(state.Offset + 1 + index * stride);
state.Kind = ObjectKind.Struct;
return state;
}
}
/// <summary>
/// Converts this list to a different representation by applying an element selector function.
/// </summary>
/// <typeparam name="T">Target type after applying the selector function</typeparam>
/// <param name="cons">The selector function</param>
/// <returns>The new list representation</returns>
public override IReadOnlyList<T> Cast<T>(Func<DeserializerState, T> cons)
{
return this.LazyListSelect(cons);
}
IEnumerable<DeserializerState> Enumerate()
{
for (int i = 0; i < Count; i++)
{
yield return this[i];
}
}
/// <summary>
/// Implements <see cref="IEnumerable{DeserializerState}"./>
/// </summary>
public IEnumerator<DeserializerState> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,95 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for List(T) when T is a known struct (i.e. a list of fixed-width composites).
/// </summary>
/// <typeparam name="TS">SerializerState which represents the struct type</typeparam>
public class ListOfStructsSerializer<TS> :
SerializerState,
IReadOnlyList<TS>
where TS : SerializerState, new()
{
/// <summary>
/// Returns the struct serializer a given index.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>The struct serializer</returns>
public TS this[int index]
{
get
{
if (!IsAllocated)
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
return ListBuildStruct<TS>(index);
}
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
/// <summary>
/// Implementation of <see cref="IEnumerable{TS}"/>/>
/// </summary>
/// <returns></returns>
public IEnumerator<TS> GetEnumerator()
{
if (Count == 0) return Enumerable.Empty<TS>().GetEnumerator();
return ListEnumerateStructs<TS>().GetEnumerator();
}
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
var sample = new TS();
SetListOfStructs(count, sample.StructDataCount, sample.StructPtrCount);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
/// <param name="init">Serialization action to transfer a particular item into the serializer state.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items.</exception>
public void Init<T>(IReadOnlyList<T> items, Action<TS, T> init)
{
if (items == null)
{
return;
}
Init(items.Count);
for (int i = 0; i < items.Count; i++)
{
init(this[i], items[i]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Capnp
{
/// <summary>
/// SerializerState specialization for List(Text)
/// </summary>
public class ListOfTextSerializer :
SerializerState,
IReadOnlyList<string>
{
/// <summary>
/// Gets or sets the text at given index. Once an element is set, it cannot be overwritten.
/// </summary>
/// <param name="index">Element index</param>
/// <exception cref="InvalidOperationException">List is not initialized</exception>
/// <exception cref="IndexOutOfRangeException"><paramref name="index"/> is out of range.</exception>
/// <exception cref="ArgumentOutOfRangeException">UTF-8 encoding exceeds 2^29-2 bytes</exception>
public string this[int index]
{
get
{
if (!IsAllocated)
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
return ReadText(index);
}
set
{
if (!IsAllocated)
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException();
WriteText(index, value);
}
}
/// <summary>
/// This list's element count.
/// </summary>
public int Count => ListElementCount;
IEnumerable<string> Enumerate()
{
int count = Count;
for (int i = 0; i < count; i++)
{
yield return TryGetPointer<SerializerState>(i)?.ListReadAsText();
}
}
/// <summary>
/// Implementation of <see cref="IEnumerable{String}"/>/>
/// </summary>
public IEnumerator<string> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
/// <summary>
/// Initializes this list with a specific size. The list can be initialized only once.
/// </summary>
/// <param name="count">List element count</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is negative or greater than 2^29-1</exception>
public void Init(int count)
{
if (IsAllocated)
throw new InvalidOperationException("Already initialized");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
SetListOfPointers(count);
}
/// <summary>
/// Initializes the list with given content.
/// </summary>
/// <param name="items">List content. Can be null in which case the list is simply not initialized.</param>
/// <exception cref="InvalidOperationException">The list was already initialized</exception>
/// <exception cref="ArgumentOutOfRangeException">More than 2^29-1 items, or the UTF-8 encoding of an individual string requires more than 2^29-2 bytes.</exception>
public void Init(IReadOnlyList<string> items)
{
if (items == null)
{
return;
}
Init(items.Count);
for (int i = 0; i < items.Count; i++)
{
this[i] = items[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using Microsoft.Extensions.Logging;
namespace Capnp
{
/// <summary>
/// Runtime logging features rely on <see cref="Microsoft.Extensions.Logging"/>
/// </summary>
public static class Logging
{
/// <summary>
/// Gets or sets the logger factory which will be used by this assembly.
/// </summary>
public static ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory();
/// <summary>
/// Creates a new ILogger instance, using the LoggerFactory of this class.
/// </summary>
/// <typeparam name="T">The type using the logger</typeparam>
/// <returns>The logger instance</returns>
public static ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();
}
}

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace Capnp
{
/// <summary>
/// Entry point for building Cap'n Proto messages.
/// </summary>
public class MessageBuilder
{
readonly ISegmentAllocator _allocator;
readonly DynamicSerializerState _rootPtrBuilder;
List<Rpc.ConsumedCapability> _capTable;
MessageBuilder(ISegmentAllocator allocator)
{
_allocator = allocator;
_rootPtrBuilder = new DynamicSerializerState(this);
_rootPtrBuilder.SetStruct(0, 1);
_rootPtrBuilder.Allocate();
}
/// <summary>
/// Constructs an instance using a custom segment allocator and reserves space for the root pointer.
/// </summary>
/// <typeparam name="T">Segment allocator implementation type</typeparam>
public static MessageBuilder Create<T>() where T: ISegmentAllocator, new()
{
return new MessageBuilder(new T());
}
/// <summary>
/// Constructs an instance using the default segment allocator and reserves space for the root pointer.
/// </summary>
/// <param name="defaultSegmentSize">Default segment size, <see cref="SegmentAllocator"/></param>
public static MessageBuilder Create(int defaultSegmentSize = 128)
{
return new MessageBuilder(new SegmentAllocator(defaultSegmentSize));
}
/// <summary>
/// Creates a new object inside the message.
/// </summary>
/// <typeparam name="TS">Serializer state specialization</typeparam>
/// <returns>Serializer state instance representing the new object</returns>
public TS CreateObject<TS>() where TS: SerializerState, new()
{
var ts = new TS();
ts.Bind(this);
return ts;
}
/// <summary>
/// Gets or sets the root object. The root object must be set exactly once per message.
/// Setting it manually is only required (and allowed) when it was created with <see cref="CreateObject{TS}"/>.
/// </summary>
public SerializerState Root
{
get => _rootPtrBuilder.TryGetPointer(0);
set => _rootPtrBuilder.Link(0, value);
}
/// <summary>
/// Creates an object and sets it as root object.
/// </summary>
/// <typeparam name="TS">Serializer state specialization</typeparam>
/// <returns>Serializer state instance representing the new object</returns>
public TS BuildRoot<TS>() where TS: SerializerState, new()
{
if (Root != null)
throw new InvalidOperationException("Root already set");
var root = CreateObject<TS>();
Root = root;
return root;
}
/// <summary>
/// Returns the wire representation of the built message.
/// </summary>
public WireFrame Frame => new WireFrame(_allocator.Segments);
/// <summary>
/// Initializes the capability table for using the message builder in RPC context.
/// </summary>
public void InitCapTable()
{
if (_capTable != null)
throw new InvalidOperationException("Capability table was already initialized");
_capTable = new List<Rpc.ConsumedCapability>();
}
/// <summary>
/// Returns this message builder's segment allocator.
/// </summary>
public ISegmentAllocator Allocator => _allocator;
internal List<Rpc.ConsumedCapability> Caps => _capTable;
}
}

View File

@ -0,0 +1,73 @@
using System;
namespace Capnp
{
/// <summary>
/// The different kinds of Cap'n Proto objects.
/// Despite this is a [Flags] enum, it does not make sense to mutually combine literals.
/// </summary>
[Flags]
public enum ObjectKind: byte
{
/// <summary>
/// The null object, obtained by decoding a null pointer.
/// </summary>
Nil = 0,
/// <summary>
/// A struct
/// </summary>
Struct = 1,
/// <summary>
/// A capability
/// </summary>
Capability = 2,
/// <summary>
/// A List(void)
/// </summary>
ListOfEmpty = 8,
/// <summary>
/// A list of bits
/// </summary>
ListOfBits = 9,
/// <summary>
/// A list of octets
/// </summary>
ListOfBytes = 10,
/// <summary>
/// A list of 16 bit words
/// </summary>
ListOfShorts = 11,
/// <summary>
/// A list of 32 bit words
/// </summary>
ListOfInts = 12,
/// <summary>
/// A list of 64 bits words
/// </summary>
ListOfLongs = 13,
/// <summary>
/// A list of pointers
/// </summary>
ListOfPointers = 14,
/// <summary>
/// A list of fixed-width composites
/// </summary>
ListOfStructs = 15,
/// <summary>
/// A value. This kind of object does not exist on the wire and is not specified by Capnp.
/// It is an internal helper to represent lists of primitive values as lists of structs.
/// </summary>
Value = 16
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Capnp
{
class PrimitiveCoder
{
class Coder<T>
{
public static Func<T, T, T> Fn { get; set; }
}
static PrimitiveCoder()
{
Coder<bool>.Fn = (x, y) => x != y;
Coder<sbyte>.Fn = (x, y) => (sbyte)(x ^ y);
Coder<byte>.Fn = (x, y) => (byte)(x ^ y);
Coder<short>.Fn = (x, y) => (short)(x ^ y);
Coder<ushort>.Fn = (x, y) => (ushort)(x ^ y);
Coder<int>.Fn = (x, y) => x ^ y;
Coder<uint>.Fn = (x, y) => x ^ y;
Coder<long>.Fn = (x, y) => x ^ y;
Coder<ulong>.Fn = (x, y) => x ^ y;
Coder<float>.Fn = (x, y) =>
{
int xi = BitConverter.SingleToInt32Bits(x);
int yi = BitConverter.SingleToInt32Bits(y);
int zi = xi ^ yi;
return BitConverter.Int32BitsToSingle(zi);
};
Coder<double>.Fn = (x, y) =>
{
long xi = BitConverter.DoubleToInt64Bits(x);
long yi = BitConverter.DoubleToInt64Bits(y);
long zi = xi ^ yi;
return BitConverter.Int64BitsToDouble(zi);
};
}
public static Func<T, T, T> Get<T>()
{
return Coder<T>.Fn ??
throw new NotSupportedException("Generic type argument is not a supported primitive type, no coder defined");
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Capnp
{
/// <summary>
/// Provides extension methods for <see cref="IReadOnlyList{T}"/>
/// </summary>
public static class ReadOnlyListExtensions
{
class ReadOnlyListSelectOperator<From, To> : IReadOnlyList<To>
{
readonly IReadOnlyList<From> _source;
readonly Func<From, To> _selector;
public ReadOnlyListSelectOperator(IReadOnlyList<From> source, Func<From, To> selector)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
_selector = selector ?? throw new ArgumentNullException(nameof(selector));
}
public To this[int index] => _selector(_source[index]);
public int Count => _source.Count;
public IEnumerator<To> GetEnumerator()
{
return _source.Select(_selector).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
/// <summary>
/// LINQ-like "Select" operator for <see cref="IReadOnlyList{T}", with the addition that the resulting elements are accessible by index.
/// The operator implements lazy semantics, which means that the selector function results are not cached./>
/// </summary>
/// <typeparam name="From">Source element type</typeparam>
/// <typeparam name="To">Target element type</typeparam>
/// <param name="source">Source list</param>
/// <param name="selector">Selector function</param>
/// <returns>A read-only list in which each element corresponds to the source element after applying the selector function</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="selector"/> is null.</exception>
public static IReadOnlyList<To> LazyListSelect<From, To>(
this IReadOnlyList<From> source, Func<From, To> selector)
{
return new ReadOnlyListSelectOperator<From, To>(source, selector);
}
/// <summary>
/// Applies a selector function to each list element and stores the result in a new list.
/// As opposed to <see cref="LazyListSelect{From, To}(IReadOnlyList{From}, Func{From, To})"/> the source is evaluated immediately
/// and the result is cached.
/// </summary>
/// <typeparam name="From">Source element type</typeparam>
/// <typeparam name="To">Target element type</typeparam>
/// <param name="source">Source list</param>
/// <param name="selector">Selector function</param>
/// <returns>A read-only list in which each element corresponds to the source element after applying the selector function</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="selector"/> is null.</exception>
public static IReadOnlyList<To> ToReadOnlyList<From, To>(
this IReadOnlyList<From> source, Func<From, To> selector)
{
return source.Select(selector).ToList().AsReadOnly();
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Capnp
{
/// <summary>
/// Provides deep-copy functionality to re-serialize an existing deserializer state into another serializer state.
/// </summary>
public static class Reserializing
{
/// <summary>
/// Performs a deep copy of an existing deserializer state into another serializer state.
/// This implementation does not analyze the source object graph and therefore cannot detect multiple references to the same object.
/// Such cases will result in object duplication.
/// </summary>
/// <param name="from">source state</param>
/// <param name="to">target state</param>
/// <exception cref="ArgumentNullException"><paramref name="to"/> is null.</exception>
/// <exception cref="InvalidOperationException">Target state was already set to a different object type than the source state.</exception>
/// <exception cref="DeserializationException">Security violation due to amplification attack or stack overflow DoS attack,
/// or illegal pointer detected during deserialization.</exception>
public static void DeepCopy(DeserializerState from, SerializerState to)
{
if (to == null)
throw new ArgumentNullException(nameof(to));
var ds = to.Rewrap<DynamicSerializerState>();
IReadOnlyList<DeserializerState> items;
switch (from.Kind)
{
case ObjectKind.Struct:
ds.SetStruct(from.StructDataCount, from.StructPtrCount);
ds.Allocate();
from.StructDataSection.CopyTo(ds.StructDataSection);
for (int i = 0; i < from.StructPtrCount; i++)
{
DeepCopy(from.StructReadPointer(i), ds.BuildPointer(i));
}
break;
case ObjectKind.ListOfBits:
ds.SetListOfValues(1, from.ListElementCount);
from.RawData.CopyTo(ds.RawData);
break;
case ObjectKind.ListOfBytes:
ds.SetListOfValues(8, from.ListElementCount);
from.RawData.CopyTo(ds.RawData);
break;
case ObjectKind.ListOfEmpty:
ds.SetListOfValues(0, from.ListElementCount);
break;
case ObjectKind.ListOfInts:
ds.SetListOfValues(32, from.ListElementCount);
from.RawData.CopyTo(ds.RawData);
break;
case ObjectKind.ListOfLongs:
ds.SetListOfValues(64, from.ListElementCount);
from.RawData.CopyTo(ds.RawData);
break;
case ObjectKind.ListOfShorts:
ds.SetListOfValues(16, from.ListElementCount);
from.RawData.CopyTo(ds.RawData);
break;
case ObjectKind.ListOfPointers:
ds.SetListOfPointers(from.ListElementCount);
items = (IReadOnlyList<DeserializerState>)from.RequireList();
for (int i = 0; i < from.ListElementCount; i++)
{
DeepCopy(items[i], ds.BuildPointer(i));
}
break;
case ObjectKind.ListOfStructs:
ds.SetListOfStructs(from.ListElementCount, from.StructDataCount, from.StructPtrCount);
items = (IReadOnlyList<DeserializerState>)from.RequireList();
for (int i = 0; i < from.ListElementCount; i++)
{
DeepCopy(items[i], ds.ListBuildStruct(i));
}
break;
case ObjectKind.Capability:
ds.SetCapability(from.CapabilityIndex);
break;
}
to.InheritFrom(ds);
}
}
}

View File

@ -0,0 +1,28 @@
namespace Capnp.Rpc
{
/// <summary>
/// Helper struct to support tail calls
/// </summary>
public struct AnswerOrCounterquestion
{
readonly object _obj;
AnswerOrCounterquestion(object obj)
{
_obj = obj;
}
public static implicit operator AnswerOrCounterquestion (SerializerState answer)
{
return new AnswerOrCounterquestion(answer);
}
public static implicit operator AnswerOrCounterquestion (PendingQuestion counterquestion)
{
return new AnswerOrCounterquestion(counterquestion);
}
public SerializerState Answer => _obj as SerializerState;
public PendingQuestion Counterquestion => _obj as PendingQuestion;
}
}

View File

@ -0,0 +1,53 @@
namespace Capnp.Rpc
{
/// <summary>
/// Generic Proxy implementation which exposes the (usually protected) Call method.
/// </summary>
public class BareProxy: Proxy
{
/// <summary>
/// Wraps a capability implementation in a Proxy.
/// </summary>
/// <param name="impl">Capability implementation</param>
/// <returns>Proxy</returns>
/// <exception cref="ArgumentNullException"><paramref name="impl"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException">No <see cref="SkeletonAttribute"/> found on implemented interface(s).</exception>
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
public static BareProxy FromImpl(object impl)
{
return new BareProxy(LocalCapability.Create(CapabilityReflection.CreateSkeleton(impl)));
}
/// <summary>
/// Constructs an unbound instance.
/// </summary>
public BareProxy()
{
}
/// <summary>
/// Constructs an instance and binds it to the given low-level capability.
/// </summary>
/// <param name="cap">low-level capability</param>
public BareProxy(ConsumedCapability cap): base(cap)
{
}
/// <summary>
/// Requests a method call.
/// </summary>
/// <param name="interfaceId">Target interface ID</param>
/// <param name="methodId">Target method ID</param>
/// <param name="args">Method arguments</param>
/// <param name="tailCall">Whether it is a tail call</param>
/// <returns>Answer promise</returns>
public IPromisedAnswer Call(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool tailCall)
{
return base.Call(interfaceId, methodId, args, tailCall);
}
}
}

View File

@ -0,0 +1,285 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Capnp.Rpc
{
/// <summary>
/// Provides functionality to construct Proxy and Skeleton instances from capability interfaces and objects implementing capability interfaces.
/// A capability interface is any .NET interface which is annotated with <see cref="ProxyAttribute"/> and <see cref="SkeletonAttribute"/>.
/// There are some intricacies to consider that you usually don't need to care about, since all that stuff will be generated.
/// </summary>
public static class CapabilityReflection
{
interface IBrokenFactory
{
System.Exception Exception { get; }
}
abstract class ProxyFactory
{
public abstract Proxy NewProxy();
}
class ProxyFactory<T>: ProxyFactory where T: Proxy, new()
{
public override Proxy NewProxy() => new T();
}
class BrokenProxyFactory: ProxyFactory, IBrokenFactory
{
readonly System.Exception _exception;
public BrokenProxyFactory(System.Exception exception)
{
_exception = exception;
}
public System.Exception Exception => _exception;
public override Proxy NewProxy()
{
throw _exception;
}
}
abstract class SkeletonFactory
{
public abstract Skeleton NewSkeleton();
}
class SkeletonFactory<T>: SkeletonFactory where T: Skeleton, new()
{
public override Skeleton NewSkeleton() => new T();
}
class BrokenSkeletonFactory: SkeletonFactory, IBrokenFactory
{
System.Exception _exception;
public BrokenSkeletonFactory(System.Exception exception)
{
_exception = exception;
}
public System.Exception Exception => _exception;
public override Skeleton NewSkeleton()
{
throw _exception;
}
}
class PolySkeletonFactory: SkeletonFactory
{
readonly SkeletonFactory[] _monoFactories;
public PolySkeletonFactory(SkeletonFactory[] monoFactories)
{
_monoFactories = monoFactories;
}
public override Skeleton NewSkeleton()
{
var poly = new PolySkeleton();
foreach (var fac in _monoFactories)
{
poly.AddInterface(fac.NewSkeleton());
}
return poly;
}
}
static ConditionalWeakTable<Type, ProxyFactory> _proxyMap =
new ConditionalWeakTable<Type, ProxyFactory>();
static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap =
new ConditionalWeakTable<Type, SkeletonFactory>();
static CapabilityReflection()
{
_proxyMap.Add(typeof(BareProxy), new ProxyFactory<BareProxy>());
}
static SkeletonFactory CreateMonoSkeletonFactory(SkeletonAttribute attr, Type[] genericArguments)
{
var skeletonClass = attr.SkeletonClass;
if (genericArguments.Length > 0)
skeletonClass = skeletonClass.MakeGenericType(genericArguments);
return (SkeletonFactory)Activator.CreateInstance(
typeof(SkeletonFactory<>)
.MakeGenericType(skeletonClass));
}
static SkeletonFactory GetSkeletonFactory(Type type)
{
return _skeletonMap.GetValue(type, _ =>
{
try
{
var attrs = (from iface in _.GetInterfaces()
from attr in iface.GetCustomAttributes(typeof(SkeletonAttribute), true)
select (SkeletonAttribute)attr).ToArray();
if (attrs.Length == 0)
throw new InvalidCapabilityInterfaceException("No 'Skeleton' attribute defined, don't know how to create the skeleton");
Type[] genericArguments = type.GetGenericArguments();
if (attrs.Length == 1)
{
return CreateMonoSkeletonFactory(attrs[0], genericArguments);
}
else
{
var monoFactories = attrs.Select(a => CreateMonoSkeletonFactory(a, genericArguments)).ToArray();
return new PolySkeletonFactory(monoFactories);
}
}
catch (System.Exception exception)
{
return new BrokenSkeletonFactory(exception);
}
});
}
/// <summary>
/// Creates a Skeleton for a given interface implementation.
/// </summary>
/// <param name="obj">Interface implementation. Must implement at least one interface which is annotated with a <see cref="SkeletonAttribute"/>.</param>
/// <returns>The Skeleton</returns>
/// <exception cref="ArgumentNullException"><paramref name="obj"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException">No <see cref="SkeletonAttribute"/> found on implemented interface(s).</exception>
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
public static Skeleton CreateSkeleton(object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
var factory = GetSkeletonFactory(obj.GetType());
var skeleton = factory.NewSkeleton();
skeleton.Bind(obj);
return skeleton;
}
static ProxyFactory GetProxyFactory(Type type)
{
return _proxyMap.GetValue(type, _ =>
{
try
{
var attrs = type
.GetCustomAttributes(typeof(ProxyAttribute), true)
.Cast<ProxyAttribute>()
.ToArray();
if (attrs.Length == 0)
throw new InvalidCapabilityInterfaceException("No 'Proxy' attribute defined, don't know how to create the proxy");
if (attrs.Length == 1)
{
Type proxyClass = attrs[0].ProxyClass;
Type[] genericArguments = type.GetGenericArguments();
if (genericArguments.Length > 0)
proxyClass = proxyClass.MakeGenericType(proxyClass);
return (ProxyFactory)Activator.CreateInstance(
typeof(ProxyFactory<>)
.MakeGenericType(proxyClass));
}
else
{
throw new InvalidCapabilityInterfaceException("Multiple 'Proxy' attributes defined, don't know which one to take");
}
}
catch (System.Exception exception)
{
return new BrokenProxyFactory(exception);
}
});
}
/// <summary>
/// Validates that a given type qualifies as cpapbility interface, throws <see cref="InvalidCapabilityInterfaceException"/> on failure.
/// </summary>
/// <param name="interfaceType">type to check</param>
/// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException">Given typ did not qualify as capability interface.
/// Message and probably InnterException give more details.</exception>
public static void ValidateCapabilityInterface(Type interfaceType)
{
if (interfaceType == null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
var proxyFactory = GetProxyFactory(interfaceType);
if (proxyFactory is IBrokenFactory brokenFactory)
{
throw new InvalidCapabilityInterfaceException(
"Given type did not qualify as capability interface, see inner exception.",
brokenFactory.Exception);
}
}
/// <summary>
/// Checkes whether a given type qualifies as cpapbility interface./> on failure.
/// </summary>
/// <param name="interfaceType">type to check</param>
/// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns>
/// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception>
public static bool IsValidCapabilityInterface(Type interfaceType)
{
if (interfaceType == null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
var proxyFactory = GetProxyFactory(interfaceType);
return !(proxyFactory is IBrokenFactory);
}
/// <summary>
/// Constructs a Proxy for given capability interface and wraps it around given low-level capability.
/// </summary>
/// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam>
/// <param name="cap">low-level capability</param>
/// <returns>The Proxy instance which implements <typeparamref name="TInterface"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not qualify as capability interface.</exception>
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public static Proxy CreateProxy<TInterface>(ConsumedCapability cap,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
var factory = GetProxyFactory(typeof(TInterface));
var proxy = factory.NewProxy();
proxy.Bind(cap);
#if DebugFinalizers
proxy.CreatorMemberName = memberName;
proxy.CreatorFilePath = sourceFilePath;
proxy.CreatorLineNumber = sourceLineNumber;
if (cap != null)
{
cap.CreatorFilePath = proxy.CreatorFilePath;
cap.CreatorLineNumber = proxy.CreatorLineNumber;
cap.CreatorMemberName = proxy.CreatorMemberName;
}
#endif
return proxy;
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Base class for a low-level capability at consumer side. It is created by the <see cref="RpcEngine"/>. An application does not directly interact with it
/// (which is intentionally impossible, since the invocation method is internal), but instead uses a <see cref="Proxy"/>-derived wrapper.
/// </summary>
public abstract class ConsumedCapability
{
internal abstract IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool tailCall);
/// <summary>
/// Request the RPC engine to release this capability from its import table,
/// which usually also means to remove it from the remote peer's export table.
/// </summary>
protected abstract void ReleaseRemotely();
internal abstract void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
internal abstract void Freeze(out IRpcEndpoint boundEndpoint);
internal abstract void Unfreeze();
internal abstract void AddRef();
internal abstract void Release();
#if DebugFinalizers
public string CreatorMemberName { get; set; }
public string CreatorFilePath { get; set; }
public int CreatorLineNumber { get; set; }
#endif
}
}

View File

@ -0,0 +1,18 @@
namespace Capnp.Rpc
{
/// <summary>
/// A uni-directional endpoint, used in conjunction with the <see cref="RpcEngine"/>.
/// </summary>
public interface IEndpoint
{
/// <summary>
/// Transmit the given Cap'n Proto message over this endpoint.
/// </summary>
void Forward(WireFrame frame);
/// <summary>
/// Close this endpoint.
/// </summary>
void Dismiss();
}
}

View File

@ -0,0 +1,13 @@
namespace Capnp.Rpc
{
/// <summary>
/// A mono skeleton (as opposed to <see cref="PolySkeleton"/>) is a skeleton which implements one particular RPC interface.
/// </summary>
public interface IMonoSkeleton: IProvidedCapability
{
/// <summary>
/// Interface ID of this skeleton.
/// </summary>
ulong InterfaceId { get; }
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// A promised answer due to RPC.
/// </summary>
/// <remarks>
/// Disposing the instance before the answer is available results in a best effort attempt to cancel
/// the ongoing call.
/// </remarks>
public interface IPromisedAnswer: IDisposable
{
/// <summary>
/// Task which will complete when the RPC returns, delivering its result struct.
/// </summary>
Task<DeserializerState> WhenReturned { get; }
/// <summary>
/// Creates a low-level capability for promise pipelining.
/// </summary>
/// <param name="access">Path to the desired capability inside the result struct.</param>
/// <returns>Pipelined low-level capability</returns>
ConsumedCapability Access(MemberAccessPath access);
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Low-level interface of a capability at provider side.
/// </summary>
public interface IProvidedCapability
{
/// <summary>
/// Calls an interface method of this capability.
/// </summary>
/// <param name="interfaceId">ID of interface to call</param>
/// <param name="methodId">ID of method to call</param>
/// <param name="args">Method arguments ("params struct")</param>
/// <param name="cancellationToken">Cancellation token, indicating when the call should cancelled.</param>
/// <returns>A Task which will resolve to the call result ("result struct")</returns>
Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId,
DeserializerState args, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,15 @@
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// A promised capability.
/// </summary>
public interface IResolvingCapability
{
/// <summary>
/// Will eventually give the resolved capability.
/// </summary>
Task<Proxy> WhenResolved { get; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
internal interface IRpcEndpoint
{
PendingQuestion BeginQuestion(ConsumedCapability target, SerializerState inParams);
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
uint AllocateExport(Skeleton providedCapability, out bool first);
void RequestPostAction(Action postAction);
void Finish(uint questionId);
void ReleaseImport(uint importId);
void RemoveImport(uint importId);
void Resolve(uint preliminaryId, Skeleton preliminaryCap, Func<ConsumedCapability> resolvedCapGetter);
Task RequestSenderLoopback(Action<MessageTarget.WRITER> writer);
void DeleteQuestion(PendingQuestion question);
}
}

View File

@ -0,0 +1,148 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Provides support for promise pipelining.
/// </summary>
public static class Impatient
{
static readonly ConditionalWeakTable<Task, IPromisedAnswer> _taskTable = new ConditionalWeakTable<Task, IPromisedAnswer>();
static readonly ThreadLocal<IRpcEndpoint> _askingEndpoint = new ThreadLocal<IRpcEndpoint>();
/// <summary>
/// Attaches a continuation to the given promise and registers the resulting task for pipelining.
/// </summary>
/// <typeparam name="T">Task result type</typeparam>
/// <param name="promise">The promise</param>
/// <param name="then">The continuation</param>
/// <returns>Task representing the future answer</returns>
/// <exception cref="ArgumentNullException"><paramref name="promise"/> or <paramref name="then"/> is null.</exception>
/// <exception cref="ArgumentException">The pomise was already registered.</exception>
public static Task<T> MakePipelineAware<T>(IPromisedAnswer promise, Func<DeserializerState, T> then)
{
async Task<T> AwaitAnswer()
{
return then(await promise.WhenReturned);
}
var rtask = AwaitAnswer();
_taskTable.Add(rtask, promise);
return rtask;
}
/// <summary>
/// Looks up the underlying promise which was previously registered for the given Task using MakePipelineAware.
/// </summary>
/// <param name="task"></param>
/// <returns>The underlying promise</returns>
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
/// <exception cref="ArgumentException">The task was not registered using MakePipelineAware.</exception>
public static IPromisedAnswer GetAnswer(Task task)
{
if (!_taskTable.TryGetValue(task, out var answer))
{
throw new ArgumentException("Unknown task");
}
return answer;
}
internal static IPromisedAnswer TryGetAnswer(Task task)
{
_taskTable.TryGetValue(task, out var answer);
return answer;
}
static async Task<Proxy> AwaitProxy<T>(Task<T> task) where T: class
{
var item = await task;
switch (item)
{
case Proxy proxy:
return proxy;
case null:
return null;
}
var skel = Skeleton.GetOrCreateSkeleton(item, false);
var localCap = LocalCapability.Create(skel);
return CapabilityReflection.CreateProxy<T>(localCap);
}
/// <summary>
/// Returns a local "lazy" proxy for a given Task.
/// This is not real promise pipelining and will probably be removed.
/// </summary>
/// <typeparam name="TInterface">Capability interface type</typeparam>
/// <param name="task">The task</param>
/// <returns>A proxy for the given task.</returns>
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
/// quality as capability interface.</exception>
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
where TInterface : class
{
var lazyCap = new LazyCapability(AwaitProxy(task));
return CapabilityReflection.CreateProxy<TInterface>(lazyCap, memberName, sourceFilePath, sourceLineNumber) as TInterface;
}
internal static IRpcEndpoint AskingEndpoint
{
get => _askingEndpoint.Value;
set { _askingEndpoint.Value = value; }
}
public static async Task<AnswerOrCounterquestion> MaybeTailCall<T>(Task<T> task, Func<T, SerializerState> func)
{
if (TryGetAnswer(task) is PendingQuestion pendingQuestion &&
pendingQuestion.RpcEndpoint == AskingEndpoint)
{
pendingQuestion.IsTailCall = true;
return pendingQuestion;
}
else
{
return func(await task);
}
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2>(Task<(T1, T2)> task, Func<T1, T2, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2> t) => func(t.Item1, t.Item2));
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2, T3>(Task<(T1, T2, T3)> task, Func<T1, T2, T3, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2, T3> t) => func(t.Item1, t.Item2, t.Item3));
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2, T3, T4>(Task<(T1, T2, T3, T4)> task, Func<T1, T2, T3, T4, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2, T3, T4> t) => func(t.Item1, t.Item2, t.Item3, t.Item4));
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2, T3, T4, T5>(Task<(T1, T2, T3, T4, T5)> task, Func<T1, T2, T3, T4, T5, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2, T3, T4, T5> t) => func(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5));
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2, T3, T4, T5, T6>(Task<(T1, T2, T3, T4, T5, T6)> task, Func<T1, T2, T3, T4, T5, T6, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2, T3, T4, T5, T6> t) => func(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6));
}
public static Task<AnswerOrCounterquestion> MaybeTailCall<T1, T2, T3, T4, T5, T6, T7>(Task<(T1, T2, T3, T4, T5, T6, T7)> task, Func<T1, T2, T3, T4, T5, T6, T7, SerializerState> func)
{
return MaybeTailCall(task, (ValueTuple<T1, T2, T3, T4, T5, T6, T7> t) => func(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7));
}
}
}

View File

@ -0,0 +1,55 @@
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Low-level capability which as imported from a remote peer.
/// </summary>
class ImportedCapability : RemoteCapability
{
readonly uint _remoteId;
public ImportedCapability(IRpcEndpoint ep, uint remoteId): base(ep)
{
_remoteId = remoteId;
}
protected override void ReleaseRemotely()
{
_ep.ReleaseImport(_remoteId);
_ep.RemoveImport(_remoteId);
}
protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)
{
var call = base.SetupMessage(args, interfaceId, methodId);
call.Target.which = MessageTarget.WHICH.ImportedCap;
call.Target.ImportedCap = _remoteId;
return call;
}
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
boundEndpoint = _ep;
}
internal override void Unfreeze()
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{
if (endpoint == _ep)
{
capDesc.which = CapDescriptor.WHICH.ReceiverHosted;
capDesc.ReceiverHosted = _remoteId;
}
else
{
capDesc.which = CapDescriptor.WHICH.SenderHosted;
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _);
}
}
}
}

View File

@ -0,0 +1,18 @@
namespace Capnp.Rpc
{
/// <summary>
/// Will be thrown if a type did not qualify as capability interface.
/// In order to qualify the type must be properly annotated with a <see cref="ProxyAttribute"/> and <see cref="SkeletonAttribute"/>.
/// See descriptions of these attributes for further details.
/// </summary>
public class InvalidCapabilityInterfaceException : System.Exception
{
public InvalidCapabilityInterfaceException(string message) : base(message)
{
}
public InvalidCapabilityInterfaceException(string message, System.Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,110 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class LazyCapability : RefCountingCapability, IResolvingCapability
{
public static LazyCapability CreateBrokenCap(string message)
{
var cap = new LazyCapability(Task.FromException<Proxy>(new RpcException(message)));
cap.AddRef(); // Instance shall be persistent
return cap;
}
public static LazyCapability CreateCanceledCap(CancellationToken token)
{
var cap = new LazyCapability(Task.FromCanceled<Proxy>(token));
cap.AddRef(); // Instance shall be persistent
return cap;
}
public static LazyCapability Null { get; } = CreateBrokenCap("Null capability");
public LazyCapability(Task<Proxy> capabilityTask)
{
WhenResolved = capabilityTask;
}
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
if (WhenResolved.IsCompleted)
{
try
{
WhenResolved.Result.Freeze(out boundEndpoint);
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
}
else
{
boundEndpoint = null;
}
}
internal override void Unfreeze()
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (WhenResolved.IsCompletedSuccessfully)
{
WhenResolved.Result.Export(endpoint, writer);
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
}
}
async void DisposeProxyWhenResolved()
{
try
{
var cap = await WhenResolved;
if (cap != null) cap.Dispose();
}
catch
{
}
}
protected override void ReleaseRemotely()
{
DisposeProxyWhenResolved();
}
public Task<Proxy> WhenResolved { get; }
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId,
DynamicSerializerState args, bool pipeline,
CancellationToken cancellationToken)
{
var cap = await WhenResolved;
cancellationToken.ThrowIfCancellationRequested();
if (cap == null)
throw new RpcException("Broken capability");
var call = cap.Call(interfaceId, methodId, args, pipeline);
var whenReturned = call.WhenReturned;
using (var registration = cancellationToken.Register(call.Dispose))
{
return await whenReturned;
}
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
var cts = new CancellationTokenSource();
return new LocalAnswer(cts, CallImpl(interfaceId, methodId, args, pipeline, cts.Token));
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class LocalAnswer : IPromisedAnswer
{
readonly CancellationTokenSource _cts;
public LocalAnswer(CancellationTokenSource cts, Task<DeserializerState> call)
{
_cts = cts ?? throw new ArgumentNullException(nameof(cts));
WhenReturned = call ?? throw new ArgumentNullException(nameof(call));
CleanupAfterReturn();
}
async void CleanupAfterReturn()
{
try
{
await WhenReturned;
}
catch
{
}
finally
{
_cts.Dispose();
}
}
public Task<DeserializerState> WhenReturned { get; }
public ConsumedCapability Access(MemberAccessPath access)
{
return new LocalAnswerCapability(WhenReturned, access);
}
public void Dispose()
{
try
{
_cts.Cancel();
}
catch (ObjectDisposedException)
{
}
}
}
}

View File

@ -0,0 +1,91 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class LocalAnswerCapability : RefCountingCapability, IResolvingCapability
{
readonly Task<DeserializerState> _answer;
readonly MemberAccessPath _access;
public LocalAnswerCapability(Task<DeserializerState> answer, MemberAccessPath access)
{
_answer = answer;
_access = access;
}
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
boundEndpoint = null;
}
internal override void Unfreeze()
{
}
async Task<Proxy> AwaitResolved()
{
var state = await _answer;
return new Proxy(_access.Eval(state));
}
public Task<Proxy> WhenResolved => AwaitResolved();
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (_answer.IsCompleted)
{
DeserializerState result;
try
{
result = _answer.Result;
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
using (var proxy = new Proxy(_access.Eval(result)))
{
proxy.Export(endpoint, writer);
}
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
}
}
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId,
DynamicSerializerState args, bool pipeline,
CancellationToken cancellationToken)
{
var cap = await AwaitResolved();
cancellationToken.ThrowIfCancellationRequested();
if (cap == null)
throw new RpcException("Broken capability");
var call = cap.Call(interfaceId, methodId, args, pipeline);
var whenReturned = call.WhenReturned;
using (var registration = cancellationToken.Register(() => call.Dispose()))
{
return await whenReturned;
}
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
var cts = new CancellationTokenSource();
return new LocalAnswer(cts, CallImpl(interfaceId, methodId, args, pipeline, cts.Token));
}
protected override void ReleaseRemotely()
{
this.DisposeWhenResolved();
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class LocalCapability : ConsumedCapability
{
static readonly ConditionalWeakTable<Skeleton, LocalCapability> _localCaps =
new ConditionalWeakTable<Skeleton, LocalCapability>();
public static ConsumedCapability Create(Skeleton skeleton)
{
if (skeleton is Vine vine)
return vine.Proxy.ConsumedCap;
else
return _localCaps.GetValue(skeleton, _ => new LocalCapability(_));
}
static async Task<DeserializerState> AwaitAnswer(Task<AnswerOrCounterquestion> call)
{
var aorcq = await call;
return aorcq.Answer ?? await aorcq.Counterquestion.WhenReturned;
}
public Skeleton ProvidedCap { get; }
LocalCapability(Skeleton providedCap)
{
ProvidedCap = providedCap ?? throw new ArgumentNullException(nameof(providedCap));
}
internal override void AddRef()
{
ProvidedCap.Claim();
}
internal override void Release()
{
ProvidedCap.Relinquish();
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
var cts = new CancellationTokenSource();
var call = ProvidedCap.Invoke(interfaceId, methodId, args, cts.Token);
return new LocalAnswer(cts, AwaitAnswer(call));
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{
capDesc.which = CapDescriptor.WHICH.SenderHosted;
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
}
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
boundEndpoint = null;
}
internal override void Unfreeze()
{
}
protected override void ReleaseRemotely()
{
}
}
}

View File

@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Capnp.Rpc
{
/// <summary>
/// A path from an outer Cap'n Proto struct to an inner (probably deeply nested) struct member.
/// </summary>
public class MemberAccessPath
{
public static readonly MemberAccessPath BootstrapAccess = new MemberAccessPath(new List<MemberAccess>());
public static MemberAccessPath Deserialize(PromisedAnswer.READER promisedAnswer)
{
var ops = new MemberAccess[promisedAnswer.Transform.Count];
int i = 0;
foreach (var op in promisedAnswer.Transform)
{
ops[i++] = MemberAccess.Deserialize(op);
}
return new MemberAccessPath(ops);
}
/// <summary>
/// Constructs a path from <see cref="MemberAccess"/> qualifiers.
/// </summary>
/// <param name="path">List of member access elements</param>
public MemberAccessPath(IReadOnlyList<MemberAccess> path)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
}
/// <summary>
/// Constructs a path from Cap'n Proto struct member offsets.
/// </summary>
/// <param name="offsets">Member offsets</param>
public MemberAccessPath(params uint[] offsets)
{
if (offsets == null)
throw new ArgumentNullException(nameof(offsets));
Path = offsets.Select(i => new StructMemberAccess(checked((ushort)i))).ToArray();
}
/// <summary>
/// Base class of an individual member access.
/// </summary>
/// <remarks>
/// This might appear a bit of overengineering, since the only specialization is the <see cref="StructMemberAccess"/>.
/// But there might be further specializations in the future, the most obvious one being an "ArrayElementAccess".
/// Now we already have a suitable design pattern, mainly to show the abstract concept behind a member access path.
/// </remarks>
public abstract class MemberAccess
{
public static MemberAccess Deserialize(PromisedAnswer.Op.READER op)
{
switch (op.which)
{
case PromisedAnswer.Op.WHICH.GetPointerField:
return new StructMemberAccess(op.GetPointerField);
default:
throw new NotSupportedException();
}
}
/// <summary>
/// Serializes this instance to a <see cref="PromisedAnswer.Op"/>.
/// </summary>
/// <param name="op">Serialization target</param>
public abstract void Serialize(PromisedAnswer.Op.WRITER op);
/// <summary>
/// Evaluates the member access on a given struct instance.
/// </summary>
/// <param name="state">Input struct instance</param>
/// <returns>Member value or object</returns>
public abstract DeserializerState Eval(DeserializerState state);
}
/// <summary>
/// The one and only member access which is currently supported: Member of a struct.
/// </summary>
public class StructMemberAccess: MemberAccess
{
/// <summary>
/// Constructs an instance for given struct member offset.
/// </summary>
/// <param name="offset">The Cap'n Proto struct member offset</param>
public StructMemberAccess(ushort offset)
{
Offset = offset;
}
/// <summary>
/// The Cap'n Proto struct member offset
/// </summary>
public ushort Offset { get; }
/// <summary>
/// Serializes this instance to a <see cref="PromisedAnswer.Op"/>.
/// </summary>
/// <param name="op">Serialization target</param>
public override void Serialize(PromisedAnswer.Op.WRITER op)
{
op.which = PromisedAnswer.Op.WHICH.GetPointerField;
op.GetPointerField = Offset;
}
/// <summary>
/// Evaluates the member access on a given struct instance.
/// </summary>
/// <param name="state">Input struct instance</param>
/// <returns>Member value or object</returns>
public override DeserializerState Eval(DeserializerState state)
{
if (state.Kind == ObjectKind.Nil)
{
return default(DeserializerState);
}
if (state.Kind != ObjectKind.Struct)
{
throw new ArgumentException("Expected a struct");
}
return state.StructReadPointer(Offset);
}
}
/// <summary>
/// The access path is a composition of individual member accesses.
/// </summary>
public IReadOnlyList<MemberAccess> Path { get; }
/// <summary>
/// Serializes this path th a <see cref="PromisedAnswer"/>.
/// </summary>
/// <param name="promisedAnswer">The serialization target</param>
public void Serialize(PromisedAnswer.WRITER promisedAnswer)
{
promisedAnswer.Transform.Init(Path.Count);
for (int i = 0; i < Path.Count; i++)
{
Path[i].Serialize(promisedAnswer.Transform[i]);
}
}
/// <summary>
/// Evaluates the path on a given object.
/// </summary>
/// <param name="rpcState">The object (usually "params struct") on which to evaluate this path.</param>
/// <returns>Resulting low-level capability</returns>
/// <exception cref="DeserializationException">Evaluation of this path did not give a capability</exception>
public ConsumedCapability Eval(DeserializerState rpcState)
{
var cur = rpcState;
foreach (var op in Path)
{
cur = op.Eval(cur);
}
switch (cur.Kind)
{
case ObjectKind.Nil:
return null;
case ObjectKind.Capability:
return rpcState.Caps[(int)cur.CapabilityIndex];
default:
throw new DeserializationException("Access path did not result in a capability");
}
}
}
}

View File

@ -0,0 +1,310 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class PendingAnswer: IDisposable
{
readonly object _reentrancyBlocker = new object();
readonly CancellationTokenSource _cts;
readonly TaskCompletionSource<int> _whenCanceled;
Task<AnswerOrCounterquestion> _callTask;
Task _initialTask;
Task _chainedTask;
bool _disposed;
public PendingAnswer(Task<AnswerOrCounterquestion> callTask, CancellationTokenSource cts)
{
_cts = cts;
_callTask = callTask ?? throw new ArgumentNullException(nameof(callTask));
_whenCanceled = new TaskCompletionSource<int>();
}
public void Cancel()
{
_cts?.Cancel();
_whenCanceled.SetResult(0);
}
async Task InitialAwaitWhenReady()
{
var which = await Task.WhenAny(_callTask, _whenCanceled.Task);
if (which != _callTask)
{
throw new TaskCanceledException();
}
}
//public Task<SerializerState> WhenReady => ChainedAwaitWhenReady();
//public void Pipeline(PromisedAnswer.READER rd, Action<Proxy> action, Action<System.Exception> error)
//{
// lock (_reentrancyBlocker)
// {
// if (_chainedTask == null)
// {
// _chainedTask = InitialAwaitWhenReady();
// }
// _chainedTask = _chainedTask.ContinueWith(t =>
// {
// bool rethrow = true;
// try
// {
// t.Wait();
// rethrow = false;
// EvaluateProxyAndCallContinuation(rd, action);
// }
// catch (AggregateException aggregateException)
// {
// var innerException = aggregateException.InnerException;
// error(innerException);
// if (rethrow) throw innerException;
// }
// },
// TaskContinuationOptions.ExecuteSynchronously);
// }
//}
async Task AwaitChainedTask(Task chainedTask, Func<Task<AnswerOrCounterquestion>, Task> func)
{
try
{
await chainedTask;
}
catch (System.Exception exception)
{
await func(Task.FromException<AnswerOrCounterquestion>(exception));
throw;
}
await func(_callTask);
}
static async Task AwaitSeq(Task task1, Task task2)
{
await task1;
await task2;
}
public void Chain(bool strictSync, Func<Task<AnswerOrCounterquestion>, Task> func)
{
lock (_reentrancyBlocker)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(PendingAnswer));
}
if (_initialTask == null)
{
_initialTask = InitialAwaitWhenReady();
}
Task followUpTask;
if (strictSync)
{
followUpTask = AwaitChainedTask(_chainedTask ?? _initialTask, func);
}
else
{
followUpTask = AwaitChainedTask(_initialTask, func);
}
if (_chainedTask != null)
{
_chainedTask = AwaitSeq(_chainedTask, followUpTask);
}
else
{
_chainedTask = followUpTask;
}
}
}
public void Chain(bool strictSync, PromisedAnswer.READER rd, Func<Task<Proxy>, Task> func)
{
Chain(strictSync, async t =>
{
async Task<Proxy> EvaluateProxy()
{
var aorcq = await t;
if (aorcq.Answer != null)
{
DeserializerState cur = aorcq.Answer;
foreach (var op in rd.Transform)
{
switch (op.which)
{
case PromisedAnswer.Op.WHICH.GetPointerField:
try
{
cur = cur.StructReadPointer(op.GetPointerField);
}
catch (System.Exception)
{
throw new ArgumentOutOfRangeException("Illegal pointer field in transformation operation");
}
break;
case PromisedAnswer.Op.WHICH.Noop:
break;
default:
throw new ArgumentOutOfRangeException("Unknown transformation operation");
}
}
Proxy proxy;
switch (cur.Kind)
{
case ObjectKind.Capability:
try
{
var cap = aorcq.Answer.Caps[(int)cur.CapabilityIndex];
proxy = new Proxy(cap ?? LazyCapability.Null);
}
catch (ArgumentOutOfRangeException)
{
throw new ArgumentOutOfRangeException("Bad capability table in internal answer - internal error?");
}
return proxy;
default:
throw new ArgumentOutOfRangeException("Transformation did not result in a capability");
}
}
else
{
var path = MemberAccessPath.Deserialize(rd);
var cap = new RemoteAnswerCapability(aorcq.Counterquestion, path);
return new Proxy(cap);
}
}
await func(EvaluateProxy());
});
}
//Task<SerializerState> ChainedAwaitWhenReady()
//{
// async Task<SerializerState> AwaitChainedTask(Task chainedTask)
// {
// await chainedTask;
// return _callTask.Result;
// }
// Task<SerializerState> resultTask;
// lock (_reentrancyBlocker)
// {
// if (_chainedTask == null)
// {
// _chainedTask = InitialAwaitWhenReady();
// }
// resultTask = AwaitChainedTask(_chainedTask);
// _chainedTask = resultTask;
// }
// return resultTask;
//}
public CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None;
//void EvaluateProxyAndCallContinuation(PromisedAnswer.READER rd, Action<Proxy> action)
//{
// var result = _callTask.Result;
// DeserializerState cur = result;
// foreach (var op in rd.Transform)
// {
// switch (op.which)
// {
// case PromisedAnswer.Op.WHICH.GetPointerField:
// try
// {
// cur = cur.StructReadPointer(op.GetPointerField);
// }
// catch (System.Exception)
// {
// throw new ArgumentOutOfRangeException("Illegal pointer field in transformation operation");
// }
// break;
// case PromisedAnswer.Op.WHICH.Noop:
// break;
// default:
// throw new ArgumentOutOfRangeException("Unknown transformation operation");
// }
// }
// Proxy proxy;
// switch (cur.Kind)
// {
// case ObjectKind.Capability:
// try
// {
// var cap = result.MsgBuilder.Caps[(int)cur.CapabilityIndex];
// proxy = new Proxy(cap ?? LazyCapability.Null);
// }
// catch (ArgumentOutOfRangeException)
// {
// throw new ArgumentOutOfRangeException("Bad capability table in internal answer - internal error?");
// }
// action(proxy);
// break;
// default:
// throw new ArgumentOutOfRangeException("Transformation did not result in a capability");
// }
//}
public async void Dispose()
{
if (_cts != null)
{
Task chainedTask;
lock (_reentrancyBlocker)
{
if (_disposed)
{
return;
}
chainedTask = _chainedTask;
_disposed = true;
}
if (chainedTask != null)
{
try
{
await chainedTask;
}
catch
{
}
finally
{
_cts.Dispose();
}
}
}
}
}
}

View File

@ -0,0 +1,311 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// A promised answer due to RPC.
/// </summary>
/// <remarks>
/// Disposing the instance before the answer is available results in a best effort attempt to cancel
/// the ongoing call.
/// </remarks>
public sealed class PendingQuestion: IPromisedAnswer
{
[Flags]
public enum State
{
None = 0,
TailCall = 1,
Sent = 2,
Returned = 4,
FinishRequested = 8,
Disposed = 16,
Finalized = 32
}
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
readonly uint _questionId;
ConsumedCapability _target;
SerializerState _inParams;
int _inhibitFinishCounter;
internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability target, SerializerState inParams)
{
RpcEndpoint = ep ?? throw new ArgumentNullException(nameof(ep));
_questionId = id;
_target = target;
_inParams = inParams;
StateFlags = inParams == null ? State.Sent : State.None;
if (inParams != null)
{
foreach (var cap in inParams.Caps)
{
cap?.AddRef();
}
}
if (target != null)
{
target.AddRef();
}
}
internal IRpcEndpoint RpcEndpoint { get; }
internal object ReentrancyBlocker { get; } = new object();
internal uint QuestionId => _questionId;
internal State StateFlags { get; private set; }
public Task<DeserializerState> WhenReturned => _tcs.Task;
internal bool IsTailCall
{
get => StateFlags.HasFlag(State.TailCall);
set
{
if (value)
StateFlags |= State.TailCall;
else
StateFlags &= ~State.TailCall;
}
}
internal bool IsReturned => StateFlags.HasFlag(State.Returned);
internal void DisallowFinish()
{
++_inhibitFinishCounter;
}
internal void AllowFinish()
{
--_inhibitFinishCounter;
AutoFinish();
}
const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error.";
internal void OnReturn(DeserializerState results)
{
lock (ReentrancyBlocker)
{
SetReturned();
}
if (StateFlags.HasFlag(State.TailCall))
{
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
}
else
{
_tcs.TrySetResult(results);
}
}
internal void OnTailCallReturn()
{
lock (ReentrancyBlocker)
{
SetReturned();
}
if (!StateFlags.HasFlag(State.TailCall))
{
_tcs.TrySetException(new RpcException("Peer sent the results of this questions somewhere else. This was not expected and is a protocol error."));
}
else
{
_tcs.TrySetResult(default);
}
}
internal void OnException(Exception.READER exception)
{
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetException(new RpcException(exception.Reason));
}
internal void OnException(System.Exception exception)
{
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetException(exception);
}
internal void OnCanceled()
{
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetCanceled();
}
void DeleteMyQuestion()
{
RpcEndpoint.DeleteQuestion(this);
}
internal void RequestFinish()
{
RpcEndpoint.Finish(_questionId);
}
void AutoFinish()
{
if (StateFlags.HasFlag(State.FinishRequested))
{
return;
}
if ((_inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned) && !StateFlags.HasFlag(State.TailCall))
|| StateFlags.HasFlag(State.Disposed))
{
StateFlags |= State.FinishRequested;
RequestFinish();
}
}
void SetReturned()
{
if (StateFlags.HasFlag(State.Returned))
{
throw new InvalidOperationException("Return state already set");
}
StateFlags |= State.Returned;
AutoFinish();
DeleteMyQuestion();
}
public ConsumedCapability Access(MemberAccessPath access)
{
lock (ReentrancyBlocker)
{
if ( StateFlags.HasFlag(State.Returned) &&
!StateFlags.HasFlag(State.TailCall))
{
try
{
return access.Eval(WhenReturned.Result);
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
}
else
{
return new RemoteAnswerCapability(this, access);
}
}
}
static void ReleaseCaps(ConsumedCapability target, SerializerState inParams)
{
if (inParams != null)
{
foreach (var cap in inParams.Caps)
{
cap?.Release();
}
}
if (target != null)
{
target.Release();
}
}
internal void Send()
{
SerializerState inParams;
ConsumedCapability target;
lock (ReentrancyBlocker)
{
Debug.Assert(!StateFlags.HasFlag(State.Sent));
inParams = _inParams;
_inParams = null;
target = _target;
_target = null;
StateFlags |= State.Sent;
}
var msg = (Message.WRITER)inParams.MsgBuilder.Root;
Debug.Assert(msg.Call.Target.which != MessageTarget.WHICH.undefined);
var call = msg.Call;
call.QuestionId = QuestionId;
call.SendResultsTo.which = IsTailCall ?
Call.sendResultsTo.WHICH.Yourself :
Call.sendResultsTo.WHICH.Caller;
try
{
RpcEndpoint.SendQuestion(inParams, call.Params);
}
catch (System.Exception exception)
{
OnException(exception);
}
ReleaseCaps(target, inParams);
}
#region IDisposable Support
void Dispose(bool disposing)
{
SerializerState inParams;
ConsumedCapability target;
lock (ReentrancyBlocker)
{
inParams = _inParams;
_inParams = null;
target = _target;
_target = null;
if (disposing)
{
if (!StateFlags.HasFlag(State.Disposed))
{
StateFlags |= State.Disposed;
AutoFinish();
}
}
else
{
StateFlags |= State.Finalized;
}
}
ReleaseCaps(target, inParams);
}
~PendingQuestion()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Combines multiple skeletons to represent objects which implement multiple interfaces.
/// </summary>
public class PolySkeleton: Skeleton
{
readonly Dictionary<ulong, Skeleton> _ifmap = new Dictionary<ulong, Skeleton>();
/// <summary>
/// Adds a skeleton to this instance.
/// </summary>
/// <param name="interfaceId">Interface ID</param>
/// <param name="skeleton">Skeleton to add</param>
/// <exception cref="ArgumentNullException"><paramref name="skeleton"/> is null.</exception>
/// <exception cref="InvalidOperationException">A skeleton with <paramref name="interfaceId"/> was already added.</exception>
public void AddInterface(ulong interfaceId, Skeleton skeleton)
{
if (skeleton == null)
throw new ArgumentNullException(nameof(skeleton));
skeleton.Claim();
_ifmap.Add(interfaceId, skeleton);
}
internal void AddInterface(Skeleton skeleton)
{
AddInterface(((IMonoSkeleton)skeleton).InterfaceId, skeleton);
}
/// <summary>
/// Calls an interface method of this capability.
/// </summary>
/// <param name="interfaceId">ID of interface to call</param>
/// <param name="methodId">ID of method to call</param>
/// <param name="args">Method arguments ("params struct")</param>
/// <param name="cancellationToken">Cancellation token, indicating when the call should cancelled.</param>
/// <returns>A Task which will resolve to the call result</returns>
public override Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default)
{
if (_ifmap.TryGetValue(interfaceId, out var skel))
return skel.Invoke(interfaceId, methodId, args, cancellationToken);
throw new NotImplementedException("Unknown interface id");
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected override void Dispose(bool disposing)
{
foreach (var cap in _ifmap.Values)
{
cap.Relinquish();
}
base.Dispose(disposing);
}
internal override void Bind(object impl)
{
foreach (Skeleton skel in _ifmap.Values)
{
skel.Bind(impl);
}
}
}
}

View File

@ -0,0 +1,259 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class PromisedCapability : RemoteResolvingCapability
{
readonly uint _remoteId;
readonly object _reentrancyBlocker = new object();
readonly TaskCompletionSource<Proxy> _resolvedCap = new TaskCompletionSource<Proxy>();
bool _released;
public PromisedCapability(IRpcEndpoint ep, uint remoteId): base(ep)
{
_remoteId = remoteId;
}
public override Task<Proxy> WhenResolved => _resolvedCap.Task;
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
lock (_reentrancyBlocker)
{
if (_resolvedCap.Task.IsCompleted && _pendingCallsOnPromise == 0)
{
try
{
_resolvedCap.Task.Result.Freeze(out boundEndpoint);
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
}
else
{
Debug.Assert(!_released);
++_pendingCallsOnPromise;
boundEndpoint = _ep;
}
}
}
internal override void Unfreeze()
{
bool release = false;
lock (_reentrancyBlocker)
{
if (_pendingCallsOnPromise == 0)
{
_resolvedCap.Task.Result.Unfreeze();
}
else
{
Debug.Assert(_pendingCallsOnPromise > 0);
Debug.Assert(!_released);
if (--_pendingCallsOnPromise == 0 && _resolvedCap.Task.IsCompleted)
{
release = true;
_released = true;
}
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_reentrancyBlocker)
{
if (_resolvedCap.Task.IsCompletedSuccessfully)
{
_resolvedCap.Task.Result.Export(endpoint, writer);
}
else
{
if (_ep == endpoint)
{
writer.which = CapDescriptor.WHICH.ReceiverHosted;
writer.ReceiverHosted = _remoteId;
Debug.Assert(!_released);
++_pendingCallsOnPromise;
_ep.RequestPostAction(() =>
{
bool release = false;
lock (_reentrancyBlocker)
{
if (--_pendingCallsOnPromise == 0 && _resolvedCap.Task.IsCompleted)
{
_released = true;
release = true;
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
});
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
}
}
}
}
async void TrackCall(Task call)
{
try
{
await call;
}
catch
{
}
finally
{
bool release = false;
lock (_reentrancyBlocker)
{
if (--_pendingCallsOnPromise == 0 && _resolvedCap.Task.IsCompleted)
{
release = true;
_released = true;
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
}
}
protected override Proxy ResolvedCap
{
get
{
try
{
return _resolvedCap.Task.IsCompleted ? _resolvedCap.Task.Result : null;
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
}
}
protected override void GetMessageTarget(MessageTarget.WRITER wr)
{
wr.which = MessageTarget.WHICH.ImportedCap;
wr.ImportedCap = _remoteId;
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
lock (_reentrancyBlocker)
{
if (_resolvedCap.Task.IsCompleted)
{
return CallOnResolution(interfaceId, methodId, args, pipeline);
}
else
{
Debug.Assert(!_released);
++_pendingCallsOnPromise;
}
}
var promisedAnswer = base.DoCall(interfaceId, methodId, args, pipeline);
TrackCall(promisedAnswer.WhenReturned);
return promisedAnswer;
}
public void ResolveTo(ConsumedCapability resolvedCap)
{
bool release = false;
lock (_reentrancyBlocker)
{
_resolvedCap.SetResult(new Proxy(resolvedCap));
if (_pendingCallsOnPromise == 0)
{
release = true;
_released = true;
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
}
public void Break(string message)
{
bool release = false;
lock (_reentrancyBlocker)
{
#if false
_resolvedCap.SetException(new RpcException(message));
#else
_resolvedCap.SetResult(new Proxy(LazyCapability.CreateBrokenCap(message)));
#endif
if (_pendingCallsOnPromise == 0)
{
release = true;
_released = true;
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
}
protected override void ReleaseRemotely()
{
if (!_released)
{
_ep.ReleaseImport(_remoteId);
}
_ep.RemoveImport(_remoteId);
this.DisposeWhenResolved();
}
protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)
{
var call = base.SetupMessage(args, interfaceId, methodId);
call.Target.which = MessageTarget.WHICH.ImportedCap;
call.Target.ImportedCap = _remoteId;
return call;
}
}
}

View File

@ -0,0 +1,213 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Application-level wrapper for consumer-side capabilities.
/// The code generator will produce a Proxy specialization for each capability interface.
/// </summary>
public class Proxy : IDisposable, IResolvingCapability
{
#if DebugFinalizers
ILogger Logger { get; } = Logging.CreateLogger<Proxy>();
#endif
bool _disposedValue = false;
/// <summary>
/// Will eventually give the resolved capability, if this is a promised capability.
/// </summary>
public Task<Proxy> WhenResolved
{
get
{
if (ConsumedCap is IResolvingCapability resolving)
{
return resolving.WhenResolved;
}
else
{
return Task.FromResult(this);
}
}
}
protected internal ConsumedCapability ConsumedCap { get; private set; }
/// <summary>
/// Whether is this a broken capability.
/// </summary>
public bool IsNull => ConsumedCap == null;
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer)
{
try
{
await answer.WhenReturned;
}
catch
{
}
finally
{
ctr.Dispose();
}
}
/// <summary>
/// Calls a method of this capability.
/// </summary>
/// <param name="interfaceId">Interface ID to call</param>
/// <param name="methodId">Method ID to call</param>
/// <param name="args">Method arguments ("param struct")</param>
/// <param name="tailCall">Whether it is a tail call</param>
/// <param name="cancellationToken">For cancelling an ongoing method call</param>
/// <returns>An answer promise</returns>
/// <exception cref="ObjectDisposedException">This instance was disposed, or transport-layer stream was disposed.</exception>
/// <exception cref="InvalidOperationException">Capability is broken.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
protected internal IPromisedAnswer Call(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool tailCall, CancellationToken cancellationToken = default)
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
if (ConsumedCap == null)
throw new InvalidOperationException("Cannot call null capability");
var answer = ConsumedCap.DoCall(interfaceId, methodId, args, tailCall);
if (cancellationToken.CanBeCanceled)
{
DisposeCtrWhenReturned(cancellationToken.Register(answer.Dispose), answer);
}
return answer;
}
public Proxy()
{
}
internal Proxy(ConsumedCapability cap)
{
Bind(cap);
}
internal void Bind(ConsumedCapability cap)
{
if (ConsumedCap != null)
throw new InvalidOperationException("Proxy was already bound");
if (cap == null)
return;
ConsumedCap = cap;
cap.AddRef();
}
internal IProvidedCapability GetProvider()
{
switch (ConsumedCap)
{
case LocalCapability lcap:
return lcap.ProvidedCap;
case null:
return null;
default:
return Vine.Create(ConsumedCap);
}
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
ConsumedCap?.Release();
_disposedValue = true;
}
}
~Proxy()
{
#if DebugFinalizers
Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}.");
#endif
Dispose(false);
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Casts this Proxy to a different capability interface.
/// </summary>
/// <typeparam name="T">Desired capability interface</typeparam>
/// <param name="disposeThis">Whether to Dispose() this Proxy instance</param>
/// <returns>Proxy for desired capability interface</returns>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception>
/// <exception cref="InvalidOperationException">This capability is broken, or mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public T Cast<T>(bool disposeThis) where T: class
{
if (IsNull)
throw new InvalidOperationException("Capability is broken");
using (disposeThis ? this : null)
{
return CapabilityReflection.CreateProxy<T>(ConsumedCap) as T;
}
}
internal void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
if (ConsumedCap == null)
writer.which = CapDescriptor.WHICH.None;
else
ConsumedCap.Export(endpoint, writer);
}
internal void Freeze(out IRpcEndpoint boundEndpoint)
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
boundEndpoint = null;
ConsumedCap?.Freeze(out boundEndpoint);
}
internal void Unfreeze()
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
ConsumedCap?.Unfreeze();
}
#if DebugFinalizers
public string CreatorMemberName { get; set; }
public string CreatorFilePath { get; set; }
public int CreatorLineNumber { get; set; }
#endif
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Capnp.Rpc
{
/// <summary>
/// Annotates a capability interface with its Proxy implementation.
/// </summary>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public class ProxyAttribute : Attribute
{
/// <summary>
/// Constructs this attribute.
/// </summary>
/// <param name="proxyClass">Proxy type. This must be a class which inherits from <see cref="Proxy"/> and
/// exposes a public parameterless constructor. Moreover, it must have same amount of generic type
/// parameters like the annotated interface, with identical generic constraints.</param>
/// <exception cref="ArgumentNullException"><paramref name="proxyClass"/> is null.</exception>
public ProxyAttribute(Type proxyClass)
{
ProxyClass = proxyClass ?? throw new ArgumentNullException(nameof(proxyClass));
}
/// <summary>
/// The Proxy type.
/// </summary>
public Type ProxyClass { get; }
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
abstract class RefCountingCapability: ConsumedCapability
{
// Note on reference counting: Works in analogy to COM. AddRef() adds a reference,
// Release() removes it. When the reference count reaches zero, the capability must be
// released remotely, i.e. we need to tell the remote peer that it can remove this
// capability from its export table. To be on the safe side,
// this class also implements a finalizer which will auto-release this capability
// remotely. This might happen if one forgets to Dispose() a Proxy. It might also happen
// if no Proxy is ever created. The latter situation occurs if the using client never
// deserializes the capability Proxy from its RPC state. This situation is nearly
// impossible to handle without relying on GC, since we never know when deserialization
// happens, and there is no RAII like in C++. Since this situation is expected to happen rarely,
// it seems acceptable to rely on the finalizer. There are three possible states.
// A: Initial state after construction: No reference, capability is *not* released.
// B: Some positive reference count.
// C: Released state: No reference anymore, capability *is* released.
// In order to distinguish state A from C, the member _refCount stores the reference count *plus one*.
// Value 0 has the special meaning of being in state C.
int _refCount = 1;
~RefCountingCapability()
{
Dispose(false);
}
/// <summary>
/// Part of the Dispose pattern implementation.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
try
{
ReleaseRemotely();
}
catch
{
}
_refCount = 0;
}
else
{
if (_refCount > 0)
{
Task.Run(() =>
{
try
{
ReleaseRemotely();
}
catch
{
}
});
}
}
}
internal sealed override void AddRef()
{
if (Interlocked.Increment(ref _refCount) <= 1)
{
throw new ObjectDisposedException(nameof(ConsumedCapability));
}
}
internal sealed override void Release()
{
if (1 == Interlocked.Decrement(ref _refCount))
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
}

View File

@ -0,0 +1,248 @@
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class RemoteAnswerCapability : RemoteResolvingCapability
{
// Set DebugEmbargos to true to get logging output for calls. RPC calls are expected to
// be on the critical path, hence very relevant for performance. We just can't afford
// additional stuff on this path. Even if the logger filters the outputs away, there is
// overhead for creating the Logger object, calling the Logger methods and deciding to
// filter the output. This justifies the precompiler switch.
#if DebugEmbargos
ILogger Logger { get; } = Logging.CreateLogger<RemoteAnswerCapability>();
#endif
readonly PendingQuestion _question;
readonly MemberAccessPath _access;
Proxy _resolvedCap;
public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint)
{
_question = question ?? throw new ArgumentNullException(nameof(question));
_access = access ?? throw new ArgumentNullException(nameof(access));
}
async void ReAllowFinishWhenDone(Task task)
{
try
{
++_pendingCallsOnPromise;
await task;
}
catch
{
}
finally
{
lock (_question.ReentrancyBlocker)
{
--_pendingCallsOnPromise;
_question.AllowFinish();
}
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_resolvedCap?.Dispose();
}
protected override Proxy ResolvedCap
{
get
{
if (_resolvedCap == null && !_question.IsTailCall && _question.IsReturned)
{
DeserializerState result;
try
{
result = _question.WhenReturned.Result;
}
catch (AggregateException exception)
{
throw exception.InnerException;
}
_resolvedCap = new Proxy(_access.Eval(result));
}
return _resolvedCap;
}
}
async Task<Proxy> AwaitWhenResolved()
{
await _question.WhenReturned;
return ResolvedCap;
}
public override Task<Proxy> WhenResolved => AwaitWhenResolved();
protected override void GetMessageTarget(MessageTarget.WRITER wr)
{
wr.which = MessageTarget.WHICH.PromisedAnswer;
wr.PromisedAnswer.QuestionId = _question.QuestionId;
_access.Serialize(wr.PromisedAnswer);
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall))
{
if (ResolvedCap == null)
{
throw new RpcException("Answer did not resolve to expected capability");
}
return CallOnResolution(interfaceId, methodId, args, pipeline);
}
else
{
#if DebugEmbargos
Logger.LogDebug("Call by proxy");
#endif
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
{
throw new ObjectDisposedException(nameof(PendingQuestion));
}
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
{
throw new InvalidOperationException("Finish request was already sent");
}
_question.DisallowFinish();
++_pendingCallsOnPromise;
var promisedAnswer = base.DoCall(interfaceId, methodId, args, pipeline);
ReAllowFinishWhenDone(promisedAnswer.WhenReturned);
async void DecrementPendingCallsOnPromiseWhenReturned()
{
try
{
await promisedAnswer.WhenReturned;
}
catch
{
}
finally
{
lock (_question.ReentrancyBlocker)
{
--_pendingCallsOnPromise;
}
}
}
DecrementPendingCallsOnPromiseWhenReturned();
return promisedAnswer;
}
}
}
protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)
{
var call = base.SetupMessage(args, interfaceId, methodId);
call.Target.which = MessageTarget.WHICH.PromisedAnswer;
call.Target.PromisedAnswer.QuestionId = _question.QuestionId;
_access.Serialize(call.Target.PromisedAnswer);
return call;
}
internal override void Freeze(out IRpcEndpoint boundEndpoint)
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
_pendingCallsOnPromise == 0)
{
if (ResolvedCap == null)
{
throw new RpcException("Answer did not resolve to expected capability");
}
ResolvedCap.Freeze(out boundEndpoint);
}
else
{
++_pendingCallsOnPromise;
_question.DisallowFinish();
boundEndpoint = _ep;
}
}
}
internal override void Unfreeze()
{
lock (_question.ReentrancyBlocker)
{
if (_pendingCallsOnPromise > 0)
{
--_pendingCallsOnPromise;
_question.AllowFinish();
}
else
{
ResolvedCap?.Unfreeze();
}
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
throw new ObjectDisposedException(nameof(PendingQuestion));
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
{
ResolvedCap.Export(endpoint, writer);
}
else
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
throw new InvalidOperationException("Finish request was already sent");
if (endpoint == _ep)
{
writer.which = CapDescriptor.WHICH.ReceiverAnswer;
_access.Serialize(writer.ReceiverAnswer);
writer.ReceiverAnswer.QuestionId = _question.QuestionId;
}
else if (_question.IsTailCall)
{
// FIXME: Resource management! We should prevent finishing this
// cap as long as it is exported. Unfortunately, we cannot determine
// when it gets removed from the export table.
var vine = Vine.Create(this);
uint id = endpoint.AllocateExport(vine, out bool first);
writer.which = CapDescriptor.WHICH.SenderHosted;
writer.SenderHosted = id;
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
}
}
}
}
protected override void ReleaseRemotely()
{
this.DisposeWhenResolved();
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
abstract class RemoteCapability : RefCountingCapability
{
protected readonly IRpcEndpoint _ep;
protected RemoteCapability(IRpcEndpoint ep)
{
_ep = ep;
}
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool tailCall)
{
var call = SetupMessage(args, interfaceId, methodId);
Debug.Assert(call.Target.which != MessageTarget.WHICH.undefined);
return _ep.BeginQuestion(this, args);
}
protected virtual Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)
{
var callMsg = args.MsgBuilder.BuildRoot<Message.WRITER>();
callMsg.which = Message.WHICH.Call;
var call = callMsg.Call;
call.AllowThirdPartyTailCall = false;
call.InterfaceId = interfaceId;
call.MethodId = methodId;
call.Params.Content = args;
return call;
}
}
}

View File

@ -0,0 +1,126 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
abstract class RemoteResolvingCapability : RemoteCapability, IResolvingCapability
{
// Set DebugEmbargos to true to get logging output for calls. RPC calls are expected to
// be on the critical path, hence very relevant for performance. We just can't afford
// additional stuff on this path. Even if the logger filters the outputs away, there is
// overhead for creating the Logger object, calling the Logger methods and deciding to
// filter the output. This justifies the precompiler switch.
#if DebugEmbargos
ILogger Logger { get; } = Logging.CreateLogger<RemoteResolvingCapability>();
#endif
public abstract Task<Proxy> WhenResolved { get; }
protected RemoteResolvingCapability(IRpcEndpoint ep) : base(ep)
{
}
protected int _pendingCallsOnPromise;
Task _disembargo;
protected abstract Proxy ResolvedCap { get; }
protected abstract void GetMessageTarget(MessageTarget.WRITER wr);
protected IPromisedAnswer CallOnResolution(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool pipeline)
{
try
{
ResolvedCap.Freeze(out var resolvedCapEndpoint);
try
{
if (resolvedCapEndpoint != null && resolvedCapEndpoint != _ep)
{
// Carol lives in a different Vat C.
throw new NotImplementedException("Sorry, level 3 RPC is not yet supported.");
}
if (ResolvedCap.IsNull ||
// If the capability resolves to null, disembargo must not be requested.
// Take the direct path, well-knowing that the call will result in an exception.
resolvedCapEndpoint != null ||
//# Note that in the case where Carol actually lives in Vat B (i.e., the same vat that the promise
//# already pointed at), no embargo is needed, because the pipelined calls are delivered over the
//# same path as the later direct calls.
(_disembargo == null && _pendingCallsOnPromise == 0) ||
// No embargo is needed since all outstanding replies have returned
_disembargo?.IsCompleted == true
// Disembargo has returned
)
{
#if DebugEmbargos
Logger.LogDebug("Direct call");
#endif
return ResolvedCap.Call(interfaceId, methodId, args, pipeline);
}
else
{
if (_disembargo == null)
{
#if DebugEmbargos
Logger.LogDebug("Requesting disembargo");
#endif
_disembargo = _ep.RequestSenderLoopback(GetMessageTarget);
}
else
{
#if DebugEmbargos
Logger.LogDebug("Waiting for requested disembargo");
#endif
}
var cancellationTokenSource = new CancellationTokenSource();
var callAfterDisembargo = _disembargo.ContinueWith(_ =>
{
// Two reasons for ignoring exceptions on the previous task (i.e. not _.Wait()ing):
// 1. A faulting predecessor, especially due to cancellation, must not have any impact on this one.
// 2. A faulting disembargo request would be a fatal protocol error, resulting in Abort() - we're dead anyway.
cancellationTokenSource.Token.ThrowIfCancellationRequested();
return ResolvedCap.Call(interfaceId, methodId, args, pipeline);
}, TaskContinuationOptions.ExecuteSynchronously);
_disembargo = callAfterDisembargo;
async Task<DeserializerState> AwaitAnswer()
{
var promisedAnswer = await callAfterDisembargo;
using (cancellationTokenSource.Token.Register(promisedAnswer.Dispose))
{
return await promisedAnswer.WhenReturned;
}
}
return new LocalAnswer(cancellationTokenSource, AwaitAnswer());
}
}
finally
{
ResolvedCap.Unfreeze();
}
}
catch (System.Exception exception)
{
// Wrap exception into local answer, since otherwise we'd get an AggregateException (which we don't want).
return new LocalAnswer(
new CancellationTokenSource(),
Task.FromException<DeserializerState>(exception));
}
}
}
}

View File

@ -0,0 +1,44 @@
namespace Capnp.Rpc
{
static class ResolvingCapabilityExtensions
{
public static void ExportAsSenderPromise<T>(this T cap, IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
where T: ConsumedCapability, IResolvingCapability
{
var vine = Vine.Create(cap);
uint preliminaryId = endpoint.AllocateExport(vine, out bool first);
writer.which = CapDescriptor.WHICH.SenderPromise;
writer.SenderPromise = preliminaryId;
if (first)
{
endpoint.RequestPostAction(async () => {
try
{
var resolvedCap = await cap.WhenResolved;
endpoint.Resolve(preliminaryId, vine, () => resolvedCap.ConsumedCap);
}
catch (System.Exception exception)
{
endpoint.Resolve(preliminaryId, vine, () => throw exception);
}
});
}
}
public static async void DisposeWhenResolved(this IResolvingCapability cap)
{
try
{
(await cap.WhenResolved)?.Dispose();
}
catch
{
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
namespace Capnp.Rpc
{
/// <summary>
/// Thrown when an RPC-related error condition occurs.
/// </summary>
public class RpcException : System.Exception
{
public RpcException(string message) : base(message)
{
}
public RpcException(string message, System.Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,6 @@
namespace Capnp.Rpc
{
class RpcUnimplementedException : System.Exception
{
}
}

View File

@ -0,0 +1,244 @@
using Microsoft.Extensions.Logging;
using System;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// A skeleton is a wrapper around a capability interface implementation which adapts it in the way it is
/// expected by the <see cref="RpcEngine"/>.
/// </summary>
public abstract class Skeleton: IProvidedCapability
{
class SkeletonRelinquisher: IDisposable
{
readonly Skeleton _skeleton;
public SkeletonRelinquisher(Skeleton skeleton)
{
_skeleton = skeleton;
}
public void Dispose()
{
_skeleton.Relinquish();
}
}
static readonly ConditionalWeakTable<object, Skeleton> _implMap =
new ConditionalWeakTable<object, Skeleton>();
internal static Skeleton GetOrCreateSkeleton<T>(T impl, bool addRef)
where T: class
{
if (impl is Skeleton skel)
return skel;
skel = _implMap.GetValue(impl, _ => CapabilityReflection.CreateSkeleton(_));
if (addRef)
{
skel.Claim();
}
return skel;
}
/// <summary>
/// Claims ownership on the given capability, preventing its automatic disposal.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <param name="impl">Capability implementation</param>
/// <returns>A disposable object. Calling Dispose() on the returned instance relinquishes ownership again.</returns>
public static IDisposable Claim<T>(T impl) where T: class
{
return new SkeletonRelinquisher(GetOrCreateSkeleton(impl, true));
}
int _refCount = 0;
/// <summary>
/// Calls an interface method of this capability.
/// </summary>
/// <param name="interfaceId">ID of interface to call</param>
/// <param name="methodId">ID of method to call</param>
/// <param name="args">Method arguments ("params struct")</param>
/// <param name="cancellationToken">Cancellation token, indicating when the call should cancelled.</param>
/// <returns>A Task which will resolve to the call result</returns>
public abstract Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default);
internal void Claim()
{
Interlocked.Increment(ref _refCount);
}
internal void Relinquish()
{
int count = Interlocked.Decrement(ref _refCount);
if (0 == count)
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
internal void Relinquish(int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
while (count-- > 0)
Relinquish();
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected virtual void Dispose(bool disposing)
{
}
~Skeleton()
{
Dispose(false);
}
internal virtual void Bind(object impl)
{
throw new NotSupportedException();
}
}
/// <summary>
/// Skeleton for a specific capability interface.
/// </summary>
/// <typeparam name="T">Capability interface</typeparam>
public abstract class Skeleton<T> : Skeleton, IMonoSkeleton
{
#if DebugEmbargos
ILogger Logger { get; } = Logging.CreateLogger<Skeleton<T>>();
#endif
Func<DeserializerState, CancellationToken, Task<AnswerOrCounterquestion>>[] _methods;
CancellationTokenSource _disposed = new CancellationTokenSource();
readonly object _reentrancyBlocker = new object();
int _pendingCalls;
/// <summary>
/// Constructs an instance.
/// </summary>
public Skeleton()
{
}
/// <summary>
/// Populates this skeleton's method table. The method table maps method IDs (which are consecutively numbered from 0
/// onwards) to the underlying capability's method implementations.
/// </summary>
/// <param name="methods">The method table. Index is method ID.</param>
protected void SetMethodTable(params Func<DeserializerState, CancellationToken, Task<AnswerOrCounterquestion>>[] methods)
{
_methods = methods;
}
/// <summary>
/// Gets the underlying capability implementation.
/// </summary>
protected T Impl { get; private set; }
/// <summary>
/// Gets the ID of the implemented interface.
/// </summary>
public abstract ulong InterfaceId { get; }
/// <summary>
/// Calls an interface method of this capability.
/// </summary>
/// <param name="interfaceId">ID of interface to call</param>
/// <param name="methodId">ID of method to call</param>
/// <param name="args">Method arguments ("params struct")</param>
/// <param name="cancellationToken">Cancellation token, indicating when the call should cancelled.</param>
/// <returns>A Task which will resolve to the call result</returns>
/// <exception cref="ObjectDisposedException">This Skeleton was disposed</exception>
public override async Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default)
{
if (InterfaceId != InterfaceId)
throw new NotImplementedException("Wrong interface id");
if (methodId >= _methods.Length)
throw new NotImplementedException("Wrong method id");
lock (_reentrancyBlocker)
{
if (_disposed == null || _disposed.IsCancellationRequested)
{
throw new ObjectDisposedException(nameof(Skeleton<T>));
}
++_pendingCalls;
}
var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_disposed.Token, cancellationToken);
try
{
return await _methods[methodId](args, linkedSource.Token);
}
catch (System.Exception)
{
throw;
}
finally
{
lock (_reentrancyBlocker)
{
--_pendingCalls;
}
linkedSource.Dispose();
CheckCtsDisposal();
}
}
void CheckCtsDisposal()
{
if (_pendingCalls == 0 && _disposed != null && _disposed.IsCancellationRequested)
{
_disposed.Dispose();
_disposed = null;
}
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected override void Dispose(bool disposing)
{
lock (_reentrancyBlocker)
{
if (_disposed == null || _disposed.IsCancellationRequested)
return;
_disposed.Cancel();
CheckCtsDisposal();
}
if (Impl is IDisposable disposable)
{
disposable.Dispose();
}
}
internal override void Bind(object impl)
{
if (Impl != null)
throw new InvalidOperationException("Skeleton was already bound");
Impl = (T)impl;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace Capnp.Rpc
{
/// <summary>
/// Annotates a capability interface with its Skeleton implementation.
/// </summary>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public class SkeletonAttribute: Attribute
{
/// <summary>
/// Constructs this attribute.
/// </summary>
/// <param name="skeletonClass">Skeleton type. This must be a class which inherits from <see cref="Skeleton"/> and
/// exposes a public parameterless constructor. Moreover, it must have same amount of generic type
/// parameters like the annotated interface, with identical generic constraints.</param>
/// <exception cref="ArgumentNullException"><paramref name="skeletonClass"/> is null.</exception>
public SkeletonAttribute(Type skeletonClass)
{
if (skeletonClass == null)
throw new ArgumentNullException(nameof(skeletonClass));
if (!typeof(Skeleton).IsAssignableFrom(skeletonClass))
throw new ArgumentException("Must inherit from Skeleton");
SkeletonClass = skeletonClass;
}
/// <summary>
/// Gets the skeleton type.
/// </summary>
public Type SkeletonClass { get; }
}
}

View File

@ -0,0 +1,184 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// TCP-based RPC implementation which will establish a connection to a TCP server implementing
/// the Cap'n Proto RPC protocol.
/// </summary>
public class TcpRpcClient: IDisposable
{
ILogger Logger { get; } = Logging.CreateLogger<TcpRpcClient>();
class OutboundTcpEndpoint : IEndpoint
{
readonly TcpRpcClient _client;
readonly FramePump _pump;
public OutboundTcpEndpoint(TcpRpcClient client, FramePump pump)
{
_client = client;
_pump = pump;
}
public void Dismiss()
{
_pump.Dispose();
}
public void Forward(WireFrame frame)
{
_pump.Send(frame);
}
}
readonly RpcEngine _rpcEngine;
readonly TcpClient _client;
RpcEngine.RpcEndpoint _inboundEndpoint;
OutboundTcpEndpoint _outboundEndpoint;
FramePump _pump;
Thread _pumpThread;
/// <summary>
/// Gets a Task which completes when TCP is connected.
/// </summary>
public Task WhenConnected { get; }
async Task ConnectAsync(string host, int port)
{
try
{
await _client.ConnectAsync(host, port);
}
catch (SocketException exception)
{
throw new RpcException("TcpRpcClient is unable to connect", exception);
}
}
async Task Connect(string host, int port)
{
await ConnectAsync(host, port);
_pump = new FramePump(_client.GetStream());
_outboundEndpoint = new OutboundTcpEndpoint(this, _pump);
_inboundEndpoint = _rpcEngine.AddEndpoint(_outboundEndpoint);
_pumpThread = new Thread(() =>
{
try
{
Thread.CurrentThread.Name = $"TCP RPC Client Thread {Thread.CurrentThread.ManagedThreadId}";
_pump.Run();
}
finally
{
_outboundEndpoint.Dismiss();
_inboundEndpoint.Dismiss();
_pump.Dispose();
}
});
_pump.FrameReceived += _inboundEndpoint.Forward;
_pumpThread.Start();
}
/// <summary>
/// Constructs an instance and attempts to connect it to given host.
/// </summary>
/// <param name="host">The DNS name of the remote RPC host</param>
/// <param name="port">The port number of the remote RPC host</param>
/// <exception cref="ArgumentNullException"><paramref name="host"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort.</exception>
/// <exception cref="System.Net.Sockets.SocketException">An error occurred when accessing the socket.</exception>
public TcpRpcClient(string host, int port)
{
_rpcEngine = new RpcEngine();
_client = new TcpClient();
WhenConnected = Connect(host, port);
}
/// <summary>
/// Returns the remote bootstrap capability.
/// </summary>
/// <typeparam name="TProxy">Bootstrap capability interface</typeparam>
/// <returns>A proxy for the bootstrap capability</returns>
public TProxy GetMain<TProxy>() where TProxy: class
{
if (!WhenConnected.IsCompleted)
{
throw new InvalidOperationException("Connection not yet established");
}
if (!WhenConnected.IsCompletedSuccessfully)
{
throw new InvalidOperationException("Connection not successfully established");
}
Debug.Assert(_inboundEndpoint != null);
return CapabilityReflection.CreateProxy<TProxy>(_inboundEndpoint.QueryMain()) as TProxy;
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
public void Dispose()
{
_client.Dispose();
try
{
if (!WhenConnected.Wait(500))
{
Logger.LogError("Unable to join connection task within timeout");
}
}
catch (System.Exception)
{
}
if (_pumpThread != null && !_pumpThread.Join(500))
{
Logger.LogError("Unable to join pump thread within timeout");
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the number of RPC protocol messages sent by this client so far.
/// </summary>
public long SendCount => _inboundEndpoint.SendCount;
/// <summary>
/// Gets the number of RPC protocol messages received by this client so far.
/// </summary>
public long RecvCount => _inboundEndpoint.RecvCount;
/// <summary>
/// Gets the remote port number which this client is connected to,
/// or null if the connection is not yet established.
/// </summary>
public int? RemotePort => ((IPEndPoint)_client.Client?.RemoteEndPoint)?.Port;
/// <summary>
/// Whether the I/O thread is currently running
/// </summary>
public bool IsComputing => _pumpThread.ThreadState == System.Threading.ThreadState.Running;
/// <summary>
/// Whether the I/O thread is waiting for data to receive
/// </summary>
public bool IsWaitingForData => _pump.IsWaitingForData;
}
}

View File

@ -0,0 +1,232 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Cap'n Proto RPC TCP server.
/// </summary>
public class TcpRpcServer: IDisposable
{
public interface IConnection
{
int LocalPort { get; }
long RecvCount { get; }
long SendCount { get; }
bool IsComputing { get; }
bool IsWaitingForData { get; }
}
ILogger Logger { get; } = Logging.CreateLogger<TcpRpcServer>();
class OutboundTcpEndpoint : IEndpoint
{
readonly TcpRpcServer _server;
readonly FramePump _pump;
public OutboundTcpEndpoint(TcpRpcServer server, FramePump pump)
{
_server = server;
_pump = pump;
}
public void Dismiss()
{
_pump.Dispose();
}
public void Forward(WireFrame frame)
{
_pump.Send(frame);
}
}
class Connection: IConnection
{
public Connection(TcpRpcServer server, TcpClient client, FramePump pump, OutboundTcpEndpoint outboundEp, RpcEngine.RpcEndpoint inboundEp)
{
Client = client;
Pump = pump;
OutboundEp = outboundEp;
InboundEp = inboundEp;
PumpRunner = new Thread(o =>
{
try
{
Thread.CurrentThread.Name = $"TCP RPC Server Thread {Thread.CurrentThread.ManagedThreadId}";
Pump.Run();
}
finally
{
OutboundEp.Dismiss();
InboundEp.Dismiss();
Pump.Dispose();
lock (server._reentrancyBlocker)
{
--server.ConnectionCount;
server._connections.Remove(this);
}
}
});
}
public TcpClient Client { get; private set; }
public FramePump Pump { get; private set; }
public OutboundTcpEndpoint OutboundEp { get; private set; }
public RpcEngine.RpcEndpoint InboundEp { get; private set; }
public Thread PumpRunner { get; private set; }
public int LocalPort => ((IPEndPoint)Client.Client.LocalEndPoint).Port;
public long RecvCount => InboundEp.RecvCount;
public long SendCount => InboundEp.SendCount;
public bool IsComputing => PumpRunner.ThreadState == ThreadState.Running;
public bool IsWaitingForData => Pump.IsWaitingForData;
}
readonly RpcEngine _rpcEngine;
readonly TcpListener _listener;
readonly object _reentrancyBlocker = new object();
readonly Thread _acceptorThread;
readonly List<Connection> _connections = new List<Connection>();
/// <summary>
/// Gets the number of currently active inbound TCP connections.
/// </summary>
public int ConnectionCount { get; private set; }
void AcceptClients()
{
try
{
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"TCP RPC Acceptor Thread {Thread.CurrentThread.ManagedThreadId}";
while (true)
{
var client = _listener.AcceptTcpClient();
var pump = new FramePump(client.GetStream());
var outboundEndpoint = new OutboundTcpEndpoint(this, pump);
var inboundEndpoint = _rpcEngine.AddEndpoint(outboundEndpoint);
pump.FrameReceived += inboundEndpoint.Forward;
var connection = new Connection(this, client, pump, outboundEndpoint, inboundEndpoint);
lock (_reentrancyBlocker)
{
++ConnectionCount;
_connections.Add(connection);
}
connection.PumpRunner.Start();
}
}
catch (SocketException)
{
// Listener was stopped. Maybe a little bit rude, but this is
// our way of shutting down the acceptor thread.
}
catch (System.Exception exception)
{
// Any other exception might be due to some other problem.
Logger.LogError(exception.Message);
}
}
/// <summary>
/// Stops accepting incoming attempts and closes all existing connections.
/// </summary>
public void Dispose()
{
try
{
_listener.Stop();
}
catch (SocketException)
{
}
var connections = new List<Connection>();
lock (_reentrancyBlocker)
{
connections.AddRange(_connections);
}
foreach (var connection in connections)
{
connection.Client.Dispose();
connection.Pump.Dispose();
if (!connection.PumpRunner.Join(500))
{
Logger.LogError("Unable to join frame pumping thread within timeout");
}
}
if (!_acceptorThread.Join(500))
{
Logger.LogError("Unable to join TCP acceptor thread within timeout");
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Constructs an instance.
/// </summary>
/// <param name="localAddr">An System.Net.IPAddress that represents the local IP address.</param>
/// <param name="port">The port on which to listen for incoming connection attempts.</param>
/// <exception cref="ArgumentNullException"><paramref name="localAddr"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort.</exception>
public TcpRpcServer(IPAddress localAddr, int port)
{
_rpcEngine = new RpcEngine();
_listener = new TcpListener(localAddr, port);
_listener.Start();
_acceptorThread = new Thread(() =>
{
AcceptClients();
});
_acceptorThread.Start();
}
/// <summary>
/// Whether the thread which is responsible for acception incoming attempts is still alive.
/// The thread will die upon disposal, but also in case of a socket error condition.
/// Errors which occur on a particular connection will just close that connection and won't interfere
/// with the acceptor thread.
/// </summary>
public bool IsAlive => _acceptorThread.IsAlive;
/// <summary>
/// Sets the bootstrap capability. It must be an object which implements a valid capability interface
/// (<see cref="SkeletonAttribute"/>).
/// </summary>
public object Main
{
set { _rpcEngine.BootstrapCap = Skeleton.GetOrCreateSkeleton(value, false); }
}
/// <summary>
/// Gets a snapshot of currently active connections.
/// </summary>
public IConnection[] Connections
{
get
{
lock (_reentrancyBlocker)
{
return _connections.ToArray();
}
}
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
class Vine : Skeleton
{
public static Skeleton Create(ConsumedCapability cap)
{
if (cap is LocalCapability lcap)
return lcap.ProvidedCap;
else
return new Vine(cap);
}
Vine(ConsumedCapability consumedCap)
{
Proxy = new Proxy(consumedCap ?? throw new ArgumentNullException(nameof(consumedCap)));
}
internal override void Bind(object impl)
{
throw new NotImplementedException();
}
public Proxy Proxy { get; }
public async override Task<AnswerOrCounterquestion> Invoke(
ulong interfaceId, ushort methodId, DeserializerState args,
CancellationToken cancellationToken = default)
{
var promisedAnswer = Proxy.Call(interfaceId, methodId, (DynamicSerializerState)args, false);
if (promisedAnswer is PendingQuestion pendingQuestion && pendingQuestion.RpcEndpoint == Impatient.AskingEndpoint)
{
async void SetupCancellation()
{
using (var registration = cancellationToken.Register(promisedAnswer.Dispose))
{
await promisedAnswer.WhenReturned;
}
}
SetupCancellation();
return pendingQuestion;
}
else
{
using (var registration = cancellationToken.Register(promisedAnswer.Dispose))
{
return (DynamicSerializerState)await promisedAnswer.WhenReturned;
}
}
}
protected override void Dispose(bool disposing)
{
Proxy.Dispose();
base.Dispose(disposing);
}
}
}

2641
Capnp.Net.Runtime/Rpc/rpc.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
namespace Capnp
{
/// <summary>
/// Provides the security bounds for defeating amplification and stack overflow DoS attacks.
/// See https://capnproto.org/encoding.html#security-considerations for details.
/// </summary>
public static class SecurityOptions
{
/// <summary>
/// The traversal limit, see https://capnproto.org/encoding.html#amplification-attack
/// </summary>
public static uint TraversalLimit { get; set; } = 64 * 1024 * 1024;
/// <summary>
/// The recursion limit, see https://capnproto.org/encoding.html#stack-overflow-dos-attack
/// </summary>
public static int RecursionLimit { get; set; } = 64;
}
}

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Capnp
{
/// <summary>
/// The segment allocator default implementation.
/// </summary>
public class SegmentAllocator : ISegmentAllocator
{
class Segment
{
public Segment(Memory<ulong> mem, uint id)
{
Mem = mem;
Id = id;
}
public uint Id { get; }
public Memory<ulong> Mem { get; }
public int FreeOffset { get; set; }
public bool TryAllocate(uint nwords, out int offset)
{
if (checked(FreeOffset + (int)nwords) <= Mem.Length)
{
offset = FreeOffset;
int count = (int)nwords;
FreeOffset += count;
return true;
}
else
{
offset = -1;
return false;
}
}
public bool IsFull => FreeOffset >= Mem.Length;
}
readonly int _defaultSegmentSize;
readonly List<Segment> _segments = new List<Segment>();
readonly List<Segment> _nonFullSegments = new List<Segment>();
/// <summary>
/// Constructs an instance.
/// </summary>
/// <param name="defaultSegmentSize">Default size (in words) of a newly allocated segment. If a single allocation requires
/// a bigger size, a bigger dedicated segment will be allocated. On the wire, segments will be truncated to their actual
/// occupancies.</param>
public SegmentAllocator(int defaultSegmentSize = 128)
{
_defaultSegmentSize = defaultSegmentSize;
}
/// <summary>
/// The list of currently allocated segments, each one truncated to its actual occupancy.
/// </summary>
public IReadOnlyList<Memory<ulong>> Segments => _segments.LazyListSelect(s => s.Mem.Slice(0, s.FreeOffset));
/// <summary>
/// Allocates memory.
/// </summary>
/// <param name="nwords">Number of words to allocate</param>
/// <param name="preferredSegmentIndex">Preferred segment index. If enough space is available,
/// memory will be allocated inside that segment. Otherwise, a different segment will be chosen, or
/// a new one will be allocated, or allocation will fail (depending on <paramref name="forcePreferredSegment"/>).</param>
/// <param name="result">The allocated memory slice in case of success (<code>default(SegmentSlice) otherwise)</code></param>
/// <param name="forcePreferredSegment">Whether using the preferred segment is mandatory. If it is and there is not
/// enough space available, allocation will fail.</param>
/// <returns>Whether allocation was successful.</returns>
public bool Allocate(uint nwords, uint preferredSegmentIndex, out SegmentSlice result, bool forcePreferredSegment)
{
result = default;
Segment segment;
if (preferredSegmentIndex < _segments.Count)
{
segment = _segments[(int)preferredSegmentIndex];
if (segment.TryAllocate(nwords, out result.Offset))
{
result.SegmentIndex = preferredSegmentIndex;
return true;
}
}
if (forcePreferredSegment)
{
return false;
}
for (int i = 0; i < _nonFullSegments.Count; i++)
{
segment = _nonFullSegments[i];
if (segment.TryAllocate(nwords, out result.Offset))
{
result.SegmentIndex = segment.Id;
if (segment.IsFull)
{
int n = _nonFullSegments.Count - 1;
var tmp = _nonFullSegments[i];
_nonFullSegments[i] = _nonFullSegments[n];
_nonFullSegments[n] = tmp;
_nonFullSegments.RemoveAt(n);
}
else if (i > 0)
{
var tmp = _nonFullSegments[i];
_nonFullSegments[i] = _nonFullSegments[0];
_nonFullSegments[0] = tmp;
}
return true;
}
}
int size = Math.Max((int)nwords, _defaultSegmentSize);
var storage = new ulong[size];
var mem = new Memory<ulong>(storage);
segment = new Segment(mem, (uint)_segments.Count);
_segments.Add(segment);
if (!segment.TryAllocate(nwords, out result.Offset))
throw new InvalidProgramException();
result.SegmentIndex = segment.Id;
if (!segment.IsFull)
{
_nonFullSegments.Add(segment);
int n = _nonFullSegments.Count - 1;
var tmp = _nonFullSegments[0];
_nonFullSegments[0] = _nonFullSegments[n];
_nonFullSegments[n] = tmp;
}
return true;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Capnp
{
/// <summary>
/// Helper struct to represent the tuple (segment index, offset)
/// </summary>
public struct SegmentSlice
{
public uint SegmentIndex;
public int Offset;
}
}

View File

@ -0,0 +1,341 @@
using System;
using System.Runtime.InteropServices;
namespace Capnp
{
/// <summary>
/// Provides extensions to the <see cref="IStructDeserializer"/> and <see cref="IStructSerializer"/> interfaces for type-safe reading and writing.
/// </summary>
public static class SerializerExtensions
{
/// <summary>
/// Reads a boolean field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static bool ReadDataBool<T>(this T d, ulong bitOffset, bool defaultValue = false)
where T: IStructDeserializer
{
return (d.StructReadData(bitOffset, 1) != 0) != defaultValue;
}
/// <summary>
/// Writes a boolean field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, bool value, bool defaultValue = false)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 1, value != defaultValue ? 1ul : 0);
}
/// <summary>
/// Reads a byte field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static byte ReadDataByte<T>(this T d, ulong bitOffset, byte defaultValue = 0)
where T : IStructDeserializer
{
return (byte)(d.StructReadData(bitOffset, 8) ^ defaultValue);
}
/// <summary>
/// Writes a byte field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, byte value, byte defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 8, (byte)(value ^ defaultValue));
}
/// <summary>
/// Reads a signed byte field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static sbyte ReadDataSByte<T>(this T d, ulong bitOffset, sbyte defaultValue = 0)
where T : IStructDeserializer
{
return (sbyte)((sbyte)d.StructReadData(bitOffset, 8) ^ defaultValue);
}
/// <summary>
/// Writes a signed byte field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, sbyte value, sbyte defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 8, unchecked((ulong)(value ^ defaultValue)));
}
/// <summary>
/// Reads a ushort field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static ushort ReadDataUShort<T>(this T d, ulong bitOffset, ushort defaultValue = 0)
where T : IStructDeserializer
{
return (ushort)(d.StructReadData(bitOffset, 16) ^ defaultValue);
}
/// <summary>
/// Writes a ushort field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, ushort value, ushort defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 16, (ushort)(value ^ defaultValue));
}
/// <summary>
/// Reads a short field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static short ReadDataShort<T>(this T d, ulong bitOffset, short defaultValue = 0)
where T : IStructDeserializer
{
return (short)((short)d.StructReadData(bitOffset, 16) ^ defaultValue);
}
/// <summary>
/// Writes a short field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, short value, short defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 16, unchecked((ulong)(value ^ defaultValue)));
}
/// <summary>
/// Reads a uint field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static uint ReadDataUInt<T>(this T d, ulong bitOffset, uint defaultValue = 0)
where T : IStructDeserializer
{
return (uint)(d.StructReadData(bitOffset, 32) ^ defaultValue);
}
/// <summary>
/// Writes a uint field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, uint value, uint defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 32, value ^ defaultValue);
}
/// <summary>
/// Performs a "reinterpret cast" of the struct's data section and returns a reference to a single element of the cast result.
/// </summary>
/// <typeparam name="U">The cast target type. Must be a primitive type which qualifies for <code><![CDATA[MemoryMarshal.Cast<ulong, U>()]]></code></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="woffset">Index within the cast result, conceptually into <code>U[]</code></param>
/// <returns>A reference to the data element</returns>
public static ref U RefData<U>(this IStructSerializer d, int woffset)
where U: struct
{
var data = MemoryMarshal.Cast<ulong, U>(d.StructDataSection);
return ref MemoryMarshal.GetReference(data.Slice(woffset, 1));
}
/// <summary>
/// Reads an int field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static int ReadDataInt<T>(this T d, ulong bitOffset, int defaultValue = 0)
where T : IStructDeserializer
{
return (int)d.StructReadData(bitOffset, 32) ^ defaultValue;
}
/// <summary>
/// Writes an int field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, int value, int defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 32, unchecked((ulong)(value ^ defaultValue)));
}
/// <summary>
/// Reads a ulong field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static ulong ReadDataULong<T>(this T d, ulong bitOffset, ulong defaultValue = 0)
where T : IStructDeserializer
{
return d.StructReadData(bitOffset, 64) ^ defaultValue;
}
/// <summary>
/// Writes a ulong field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, ulong value, ulong defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 64, value ^ defaultValue);
}
/// <summary>
/// Reads a long field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (will be XORed with the result)</param>
/// <returns>The read value</returns>
public static long ReadDataLong<T>(this T d, ulong bitOffset, long defaultValue = 0)
where T : IStructDeserializer
{
return (long)d.StructReadData(bitOffset, 64) ^ defaultValue;
}
/// <summary>
/// Writes a long field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, long value, long defaultValue = 0)
where T : IStructSerializer
{
d.StructWriteData(bitOffset, 64, unchecked((ulong)(value ^ defaultValue)));
}
/// <summary>
/// Reads a float field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (raw bits will be XORed with the result)</param>
/// <returns>The read value</returns>
public static float ReadDataFloat<T>(this T d, ulong bitOffset, float defaultValue = 0)
where T : IStructDeserializer
{
int defaultBits = BitConverter.SingleToInt32Bits(defaultValue);
int bits = (int)d.StructReadData(bitOffset, 32) ^ defaultBits;
return BitConverter.Int32BitsToSingle(bits);
}
/// <summary>
/// Writes a float field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (raw bits will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, float value, float defaultValue = 0.0f)
where T : IStructSerializer
{
int bits = BitConverter.SingleToInt32Bits(value);
int defaultBits = BitConverter.SingleToInt32Bits(defaultValue);
WriteData(d, bitOffset, bits, defaultBits);
}
/// <summary>
/// Reads a double field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructDeserializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="defaultValue">Field default value (raw bits will be XORed with the result)</param>
/// <returns>The read value</returns>
public static double ReadDataDouble<T>(this T d, ulong bitOffset, double defaultValue = 0)
where T : IStructDeserializer
{
long defaultBits = BitConverter.DoubleToInt64Bits(defaultValue);
long bits = (long)d.StructReadData(bitOffset, 64) ^ defaultBits;
return BitConverter.Int64BitsToDouble(bits);
}
/// <summary>
/// Writes a double field.
/// </summary>
/// <typeparam name="T">Type implementing <see cref="IStructSerializer"/></typeparam>
/// <param name="d">"this" instance</param>
/// <param name="bitOffset">Start bit</param>
/// <param name="value">Value to write</param>
/// <param name="defaultValue">Field default value (raw bits will be XORed with the value to write)</param>
public static void WriteData<T>(this T d, ulong bitOffset, double value, double defaultValue = 0.0)
where T : IStructSerializer
{
long bits = BitConverter.DoubleToInt64Bits(value);
long defaultBits = BitConverter.DoubleToInt64Bits(defaultValue);
WriteData(d, bitOffset, bits, defaultBits);
}
}
}

Some files were not shown because too many files have changed in this diff Show More