mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 06:41:50 +01:00
Initial commit
This commit is contained in:
parent
d85a0a019a
commit
cbf2144ef4
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal 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
|
24
Capnp.Net.Runtime.Tests/CallContext.cs
Normal file
24
Capnp.Net.Runtime.Tests/CallContext.cs
Normal 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; }
|
||||
}
|
||||
}
|
26
Capnp.Net.Runtime.Tests/Capnp.Net.Runtime.Tests.csproj
Normal file
26
Capnp.Net.Runtime.Tests/Capnp.Net.Runtime.Tests.csproj
Normal 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>
|
357
Capnp.Net.Runtime.Tests/DeserializationTests.cs
Normal file
357
Capnp.Net.Runtime.Tests/DeserializationTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
927
Capnp.Net.Runtime.Tests/DynamicSerializerStateTests.cs
Normal file
927
Capnp.Net.Runtime.Tests/DynamicSerializerStateTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
184
Capnp.Net.Runtime.Tests/FramePumpTests.cs
Normal file
184
Capnp.Net.Runtime.Tests/FramePumpTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
71
Capnp.Net.Runtime.Tests/General.cs
Normal file
71
Capnp.Net.Runtime.Tests/General.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
145
Capnp.Net.Runtime.Tests/JobUtil.cs
Normal file
145
Capnp.Net.Runtime.Tests/JobUtil.cs
Normal 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
|
||||
}
|
65
Capnp.Net.Runtime.Tests/MessageBuilderTests.cs
Normal file
65
Capnp.Net.Runtime.Tests/MessageBuilderTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
Capnp.Net.Runtime.Tests/ProvidedCapabilityMock.cs
Normal file
24
Capnp.Net.Runtime.Tests/ProvidedCapabilityMock.cs
Normal 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>();
|
||||
}
|
||||
}
|
23
Capnp.Net.Runtime.Tests/ProvidedCapabilityMultiCallMock.cs
Normal file
23
Capnp.Net.Runtime.Tests/ProvidedCapabilityMultiCallMock.cs
Normal 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();
|
||||
}
|
||||
}
|
369
Capnp.Net.Runtime.Tests/RpcSchemaTests.cs
Normal file
369
Capnp.Net.Runtime.Tests/RpcSchemaTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
Capnp.Net.Runtime.Tests/SegmentAllocatorTests.cs
Normal file
41
Capnp.Net.Runtime.Tests/SegmentAllocatorTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
697
Capnp.Net.Runtime.Tests/TcpRpc.cs
Normal file
697
Capnp.Net.Runtime.Tests/TcpRpc.cs
Normal 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.Logging;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
Capnp.Net.Runtime.Tests/TcpRpcAdvancedStuff.cs
Normal file
92
Capnp.Net.Runtime.Tests/TcpRpcAdvancedStuff.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1028
Capnp.Net.Runtime.Tests/TcpRpcInterop.cs
Normal file
1028
Capnp.Net.Runtime.Tests/TcpRpcInterop.cs
Normal file
File diff suppressed because it is too large
Load Diff
622
Capnp.Net.Runtime.Tests/TcpRpcPorted.cs
Normal file
622
Capnp.Net.Runtime.Tests/TcpRpcPorted.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
Capnp.Net.Runtime.Tests/TcpRpcStress.cs
Normal file
103
Capnp.Net.Runtime.Tests/TcpRpcStress.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
89
Capnp.Net.Runtime.Tests/TestBase.cs
Normal file
89
Capnp.Net.Runtime.Tests/TestBase.cs
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
745
Capnp.Net.Runtime.Tests/TestCapImplementations.cs
Normal file
745
Capnp.Net.Runtime.Tests/TestCapImplementations.cs
Normal 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
|
||||
}
|
102
Capnp.Net.Runtime.Tests/TestInterfaces.cs
Normal file
102
Capnp.Net.Runtime.Tests/TestInterfaces.cs
Normal 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();
|
||||
// }
|
||||
}
|
||||
|
228
Capnp.Net.Runtime.Tests/WirePointerTests.cs
Normal file
228
Capnp.Net.Runtime.Tests/WirePointerTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
16200
Capnp.Net.Runtime.Tests/test.cs
Normal file
16200
Capnp.Net.Runtime.Tests/test.cs
Normal file
File diff suppressed because it is too large
Load Diff
31
Capnp.Net.Runtime/AnyPointer.cs
Normal file
31
Capnp.Net.Runtime/AnyPointer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
17
Capnp.Net.Runtime/Capnp.Net.Runtime.csproj
Normal file
17
Capnp.Net.Runtime/Capnp.Net.Runtime.csproj
Normal 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>
|
173
Capnp.Net.Runtime/CapnpSerializable.cs
Normal file
173
Capnp.Net.Runtime/CapnpSerializable.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
19
Capnp.Net.Runtime/DeserializationException.cs
Normal file
19
Capnp.Net.Runtime/DeserializationException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
686
Capnp.Net.Runtime/DeserializerState.cs
Normal file
686
Capnp.Net.Runtime/DeserializerState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
227
Capnp.Net.Runtime/DynamicSerializerState.cs
Normal file
227
Capnp.Net.Runtime/DynamicSerializerState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
Capnp.Net.Runtime/EmptyList.cs
Normal file
38
Capnp.Net.Runtime/EmptyList.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
88
Capnp.Net.Runtime/EmptyListDeserializer.cs
Normal file
88
Capnp.Net.Runtime/EmptyListDeserializer.cs
Normal 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>();
|
||||
}
|
||||
}
|
206
Capnp.Net.Runtime/FramePump.cs
Normal file
206
Capnp.Net.Runtime/FramePump.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
Capnp.Net.Runtime/Framing.cs
Normal file
60
Capnp.Net.Runtime/Framing.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
Capnp.Net.Runtime/ICapnpSerializable.cs
Normal file
21
Capnp.Net.Runtime/ICapnpSerializable.cs
Normal 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);
|
||||
}
|
||||
}
|
29
Capnp.Net.Runtime/ISegmentAllocator.cs
Normal file
29
Capnp.Net.Runtime/ISegmentAllocator.cs
Normal 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);
|
||||
}
|
||||
}
|
22
Capnp.Net.Runtime/IStructDeserializer.cs
Normal file
22
Capnp.Net.Runtime/IStructDeserializer.cs
Normal 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);
|
||||
}
|
||||
}
|
27
Capnp.Net.Runtime/IStructSerializer.cs
Normal file
27
Capnp.Net.Runtime/IStructSerializer.cs
Normal 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; }
|
||||
}
|
||||
}
|
403
Capnp.Net.Runtime/ListDeserializer.cs
Normal file
403
Capnp.Net.Runtime/ListDeserializer.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
48
Capnp.Net.Runtime/ListKind.cs
Normal file
48
Capnp.Net.Runtime/ListKind.cs
Normal 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
|
||||
}
|
||||
}
|
79
Capnp.Net.Runtime/ListOfBitsDeserializer.cs
Normal file
79
Capnp.Net.Runtime/ListOfBitsDeserializer.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
98
Capnp.Net.Runtime/ListOfBitsSerializer.cs
Normal file
98
Capnp.Net.Runtime/ListOfBitsSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
72
Capnp.Net.Runtime/ListOfCapsDeserializer.cs
Normal file
72
Capnp.Net.Runtime/ListOfCapsDeserializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
111
Capnp.Net.Runtime/ListOfCapsSerializer.cs
Normal file
111
Capnp.Net.Runtime/ListOfCapsSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
66
Capnp.Net.Runtime/ListOfEmptyDeserializer.cs
Normal file
66
Capnp.Net.Runtime/ListOfEmptyDeserializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
33
Capnp.Net.Runtime/ListOfEmptySerializer.cs
Normal file
33
Capnp.Net.Runtime/ListOfEmptySerializer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
91
Capnp.Net.Runtime/ListOfPointersDeserializer.cs
Normal file
91
Capnp.Net.Runtime/ListOfPointersDeserializer.cs
Normal 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>());
|
||||
}
|
||||
}
|
||||
}
|
118
Capnp.Net.Runtime/ListOfPointersSerializer.cs
Normal file
118
Capnp.Net.Runtime/ListOfPointersSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
234
Capnp.Net.Runtime/ListOfPrimitivesDeserializer.cs
Normal file
234
Capnp.Net.Runtime/ListOfPrimitivesDeserializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
96
Capnp.Net.Runtime/ListOfPrimitivesSerializer.cs
Normal file
96
Capnp.Net.Runtime/ListOfPrimitivesSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
81
Capnp.Net.Runtime/ListOfStructsDeserializer.cs
Normal file
81
Capnp.Net.Runtime/ListOfStructsDeserializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
95
Capnp.Net.Runtime/ListOfStructsSerializer.cs
Normal file
95
Capnp.Net.Runtime/ListOfStructsSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
112
Capnp.Net.Runtime/ListOfTextSerializer.cs
Normal file
112
Capnp.Net.Runtime/ListOfTextSerializer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
Capnp.Net.Runtime/Logging.cs
Normal file
23
Capnp.Net.Runtime/Logging.cs
Normal 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>();
|
||||
}
|
||||
}
|
102
Capnp.Net.Runtime/MessageBuilder.cs
Normal file
102
Capnp.Net.Runtime/MessageBuilder.cs
Normal 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;
|
||||
}
|
||||
}
|
73
Capnp.Net.Runtime/ObjectKind.cs
Normal file
73
Capnp.Net.Runtime/ObjectKind.cs
Normal 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
|
||||
}
|
||||
}
|
47
Capnp.Net.Runtime/PrimitiveCoder.cs
Normal file
47
Capnp.Net.Runtime/PrimitiveCoder.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
73
Capnp.Net.Runtime/ReadOnlyListExtensions.cs
Normal file
73
Capnp.Net.Runtime/ReadOnlyListExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
99
Capnp.Net.Runtime/Reserializing.cs
Normal file
99
Capnp.Net.Runtime/Reserializing.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
28
Capnp.Net.Runtime/Rpc/AnswerOrCounterquestion.cs
Normal file
28
Capnp.Net.Runtime/Rpc/AnswerOrCounterquestion.cs
Normal 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;
|
||||
}
|
||||
}
|
53
Capnp.Net.Runtime/Rpc/BareProxy.cs
Normal file
53
Capnp.Net.Runtime/Rpc/BareProxy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
285
Capnp.Net.Runtime/Rpc/CapabilityReflection.cs
Normal file
285
Capnp.Net.Runtime/Rpc/CapabilityReflection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
33
Capnp.Net.Runtime/Rpc/ConsumedCapability.cs
Normal file
33
Capnp.Net.Runtime/Rpc/ConsumedCapability.cs
Normal 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
|
||||
}
|
||||
}
|
18
Capnp.Net.Runtime/Rpc/IEndpoint.cs
Normal file
18
Capnp.Net.Runtime/Rpc/IEndpoint.cs
Normal 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();
|
||||
}
|
||||
}
|
13
Capnp.Net.Runtime/Rpc/IMonoSkeleton.cs
Normal file
13
Capnp.Net.Runtime/Rpc/IMonoSkeleton.cs
Normal 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; }
|
||||
}
|
||||
}
|
27
Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs
Normal file
27
Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs
Normal 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);
|
||||
}
|
||||
}
|
24
Capnp.Net.Runtime/Rpc/IProvidedCapability.cs
Normal file
24
Capnp.Net.Runtime/Rpc/IProvidedCapability.cs
Normal 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);
|
||||
}
|
||||
}
|
15
Capnp.Net.Runtime/Rpc/IResolvingCapability.cs
Normal file
15
Capnp.Net.Runtime/Rpc/IResolvingCapability.cs
Normal 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; }
|
||||
}
|
||||
}
|
22
Capnp.Net.Runtime/Rpc/IRpcEndpoint.cs
Normal file
22
Capnp.Net.Runtime/Rpc/IRpcEndpoint.cs
Normal 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);
|
||||
}
|
||||
}
|
148
Capnp.Net.Runtime/Rpc/Impatient.cs
Normal file
148
Capnp.Net.Runtime/Rpc/Impatient.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
55
Capnp.Net.Runtime/Rpc/ImportedCapability.cs
Normal file
55
Capnp.Net.Runtime/Rpc/ImportedCapability.cs
Normal 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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
Capnp.Net.Runtime/Rpc/InvalidCapabilityInterfaceException.cs
Normal file
18
Capnp.Net.Runtime/Rpc/InvalidCapabilityInterfaceException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
110
Capnp.Net.Runtime/Rpc/LazyCapability.cs
Normal file
110
Capnp.Net.Runtime/Rpc/LazyCapability.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
52
Capnp.Net.Runtime/Rpc/LocalAnswer.cs
Normal file
52
Capnp.Net.Runtime/Rpc/LocalAnswer.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
91
Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs
Normal file
91
Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
71
Capnp.Net.Runtime/Rpc/LocalCapability.cs
Normal file
71
Capnp.Net.Runtime/Rpc/LocalCapability.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
181
Capnp.Net.Runtime/Rpc/MemberAccessPath.cs
Normal file
181
Capnp.Net.Runtime/Rpc/MemberAccessPath.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
310
Capnp.Net.Runtime/Rpc/PendingAnswer.cs
Normal file
310
Capnp.Net.Runtime/Rpc/PendingAnswer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
311
Capnp.Net.Runtime/Rpc/PendingQuestion.cs
Normal file
311
Capnp.Net.Runtime/Rpc/PendingQuestion.cs
Normal 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
|
||||
}
|
||||
}
|
73
Capnp.Net.Runtime/Rpc/PolySkeleton.cs
Normal file
73
Capnp.Net.Runtime/Rpc/PolySkeleton.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
259
Capnp.Net.Runtime/Rpc/PromisedCapability.cs
Normal file
259
Capnp.Net.Runtime/Rpc/PromisedCapability.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
213
Capnp.Net.Runtime/Rpc/Proxy.cs
Normal file
213
Capnp.Net.Runtime/Rpc/Proxy.cs
Normal 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
|
||||
}
|
||||
}
|
30
Capnp.Net.Runtime/Rpc/ProxyAttribute.cs
Normal file
30
Capnp.Net.Runtime/Rpc/ProxyAttribute.cs
Normal 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; }
|
||||
}
|
||||
}
|
84
Capnp.Net.Runtime/Rpc/RefCountingCapability.cs
Normal file
84
Capnp.Net.Runtime/Rpc/RefCountingCapability.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
248
Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs
Normal file
248
Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
40
Capnp.Net.Runtime/Rpc/RemoteCapability.cs
Normal file
40
Capnp.Net.Runtime/Rpc/RemoteCapability.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
126
Capnp.Net.Runtime/Rpc/RemoteResolvingCapability.cs
Normal file
126
Capnp.Net.Runtime/Rpc/RemoteResolvingCapability.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
Capnp.Net.Runtime/Rpc/ResolvingCapabilityExtensions.cs
Normal file
44
Capnp.Net.Runtime/Rpc/ResolvingCapabilityExtensions.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1520
Capnp.Net.Runtime/Rpc/RpcEngine.cs
Normal file
1520
Capnp.Net.Runtime/Rpc/RpcEngine.cs
Normal file
File diff suppressed because it is too large
Load Diff
16
Capnp.Net.Runtime/Rpc/RpcException.cs
Normal file
16
Capnp.Net.Runtime/Rpc/RpcException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
6
Capnp.Net.Runtime/Rpc/RpcUnimplementedException.cs
Normal file
6
Capnp.Net.Runtime/Rpc/RpcUnimplementedException.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
class RpcUnimplementedException : System.Exception
|
||||
{
|
||||
}
|
||||
}
|
244
Capnp.Net.Runtime/Rpc/Skeleton.cs
Normal file
244
Capnp.Net.Runtime/Rpc/Skeleton.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
34
Capnp.Net.Runtime/Rpc/SkeletonAttribute.cs
Normal file
34
Capnp.Net.Runtime/Rpc/SkeletonAttribute.cs
Normal 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; }
|
||||
}
|
||||
}
|
184
Capnp.Net.Runtime/Rpc/TcpRpcClient.cs
Normal file
184
Capnp.Net.Runtime/Rpc/TcpRpcClient.cs
Normal 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;
|
||||
}
|
||||
}
|
232
Capnp.Net.Runtime/Rpc/TcpRpcServer.cs
Normal file
232
Capnp.Net.Runtime/Rpc/TcpRpcServer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
Capnp.Net.Runtime/Rpc/Vine.cs
Normal file
66
Capnp.Net.Runtime/Rpc/Vine.cs
Normal 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
2641
Capnp.Net.Runtime/Rpc/rpc.cs
Normal file
File diff suppressed because it is too large
Load Diff
18
Capnp.Net.Runtime/SecurityOptions.cs
Normal file
18
Capnp.Net.Runtime/SecurityOptions.cs
Normal 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;
|
||||
}
|
||||
}
|
147
Capnp.Net.Runtime/SegmentAllocator.cs
Normal file
147
Capnp.Net.Runtime/SegmentAllocator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
Capnp.Net.Runtime/SegmentSlice.cs
Normal file
13
Capnp.Net.Runtime/SegmentSlice.cs
Normal 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;
|
||||
}
|
||||
}
|
341
Capnp.Net.Runtime/SerializerExtensions.cs
Normal file
341
Capnp.Net.Runtime/SerializerExtensions.cs
Normal 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
Loading…
x
Reference in New Issue
Block a user