diff --git a/Capnp.Net.Runtime.Tests/RpcSchemaTests.cs b/Capnp.Net.Runtime.Tests/RpcSchemaTests.cs index f8841ec..82ab2d0 100644 --- a/Capnp.Net.Runtime.Tests/RpcSchemaTests.cs +++ b/Capnp.Net.Runtime.Tests/RpcSchemaTests.cs @@ -1,5 +1,7 @@ using Capnp.Rpc; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; namespace Capnp.Net.Runtime.Tests { @@ -366,5 +368,402 @@ namespace Capnp.Net.Runtime.Tests Assert.AreEqual("reason", r.Unimplemented.Resolve.Exception.Reason); } } + + void ConstructReconstructTest(Func construct, Action verify) + where TD : class, ICapnpSerializable, new() + where TW: SerializerState, new() + { + var obj = construct(); + + var mb = MessageBuilder.Create(); + var root = mb.BuildRoot(); + obj.Serialize(root); + var d = (DeserializerState)root; + var obj2 = new TD(); + obj2.Deserialize(d); + + verify(obj2); + } + + [TestMethod] + public void DcMessageAbort() + { + ConstructReconstructTest( + () => new Message() + { + Abort = new Rpc.Exception() + { + Reason = "problem" + } + }, + msg => + { + Assert.IsNotNull(msg.Abort); + Assert.IsNull(msg.Accept); + Assert.AreEqual("problem", msg.Abort.Reason); + } + ); + } + + [TestMethod] + public void DcMessageAccept() + { + ConstructReconstructTest( + () => new Message() + { + Accept = new Accept() + { + QuestionId = 123u, + Embargo = true + } + }, + msg => + { + Assert.IsNotNull(msg.Accept); + Assert.AreEqual(123u, msg.Accept.QuestionId); + Assert.IsTrue(msg.Accept.Embargo); + } + ); + } + + [TestMethod] + public void DcMessageUnimplementedJoin() + { + ConstructReconstructTest( + () => new Message() + { + Unimplemented = new Message() + { + Join = new Join() + { + QuestionId = 456u, + Target = new MessageTarget() + { + ImportedCap = 789u + } + } + } + }, + msg => + { + Assert.IsNotNull(msg.Unimplemented); + Assert.IsNotNull(msg.Unimplemented.Join); + Assert.AreEqual(456u, msg.Unimplemented.Join.QuestionId); + Assert.IsNotNull(msg.Unimplemented.Join.Target); + Assert.AreEqual(789u, msg.Unimplemented.Join.Target.ImportedCap); + } + ); + } + + [TestMethod] + public void DcMessageCall() + { + ConstructReconstructTest( + () => new Message() + { + Call = new Call() + { + AllowThirdPartyTailCall = true, + InterfaceId = 0x12345678abcdef, + MethodId = 0x5555, + Params = new Payload() + { + CapTable = new List() + { + new CapDescriptor() + { + ReceiverAnswer = new PromisedAnswer() + { + QuestionId = 42u, + Transform = new List() + { + new PromisedAnswer.Op() + { + GetPointerField = 3 + } + } + }, + }, + new CapDescriptor() + { + ReceiverHosted = 7u + }, + new CapDescriptor() + { + SenderHosted = 8u + }, + new CapDescriptor() + { + SenderPromise = 9u + }, + new CapDescriptor() + { + ThirdPartyHosted = new ThirdPartyCapDescriptor() + { + VineId = 10u + } + } + } + }, + QuestionId = 57u, + SendResultsTo = new Call.sendResultsTo() + { + which = Call.sendResultsTo.WHICH.Caller + }, + Target = new MessageTarget() + { + PromisedAnswer = new PromisedAnswer() + { + QuestionId = 12u + } + } + } + }, + msg => + { + Assert.IsNotNull(msg.Call); + Assert.IsTrue(msg.Call.AllowThirdPartyTailCall); + Assert.AreEqual(0x12345678abcdeful, msg.Call.InterfaceId); + Assert.AreEqual(0x5555u, msg.Call.MethodId); + Assert.IsNotNull(msg.Call.Params); + Assert.IsNotNull(msg.Call.Params.CapTable); + Assert.AreEqual(5, msg.Call.Params.CapTable.Count); + Assert.IsNotNull(msg.Call.Params.CapTable[0].ReceiverAnswer); + Assert.AreEqual(42u, msg.Call.Params.CapTable[0].ReceiverAnswer.QuestionId); + Assert.IsNotNull(msg.Call.Params.CapTable[0].ReceiverAnswer.Transform); + Assert.AreEqual(1, msg.Call.Params.CapTable[0].ReceiverAnswer.Transform.Count); + Assert.AreEqual((ushort)3, msg.Call.Params.CapTable[0].ReceiverAnswer.Transform[0].GetPointerField); + Assert.AreEqual(7u, msg.Call.Params.CapTable[1].ReceiverHosted); + Assert.AreEqual(8u, msg.Call.Params.CapTable[2].SenderHosted); + Assert.AreEqual(9u, msg.Call.Params.CapTable[3].SenderPromise); + Assert.IsNotNull(msg.Call.Params.CapTable[4].ThirdPartyHosted); + Assert.AreEqual(10u, msg.Call.Params.CapTable[4].ThirdPartyHosted.VineId); + Assert.AreEqual(57u, msg.Call.QuestionId); + Assert.IsNotNull(msg.Call.SendResultsTo); + Assert.AreEqual(Call.sendResultsTo.WHICH.Caller, msg.Call.SendResultsTo.which); + Assert.IsNotNull(msg.Call.Target); + Assert.IsNotNull(msg.Call.Target.PromisedAnswer); + Assert.AreEqual(12u, msg.Call.Target.PromisedAnswer.QuestionId); + } + ); + } + + [TestMethod] + public void DcMessageReturnResults() + { + ConstructReconstructTest( + () => new Message() + { + Return = new Return() + { + AnswerId = 123u, + ReleaseParamCaps = false, + Results = new Payload() + { + CapTable = new List() + } + } + }, + msg => + { + Assert.IsNotNull(msg.Return); + Assert.AreEqual(123u, msg.Return.AnswerId); + Assert.IsFalse(msg.Return.ReleaseParamCaps); + Assert.IsNotNull(msg.Return.Results); + Assert.IsNotNull(msg.Return.Results.CapTable); + Assert.AreEqual(0, msg.Return.Results.CapTable.Count); + } + ); + } + + [TestMethod] + public void DcMessageReturnException() + { + ConstructReconstructTest( + () => new Message() + { + Return = new Return() + { + AnswerId = 123u, + ReleaseParamCaps = true, + Exception = new Rpc.Exception() + { + Reason = "bad", + TheType = Rpc.Exception.Type.overloaded + } + } + }, + msg => + { + Assert.IsNotNull(msg.Return); + Assert.AreEqual(123u, msg.Return.AnswerId); + Assert.IsTrue(msg.Return.ReleaseParamCaps); + Assert.IsNotNull(msg.Return.Exception); + Assert.AreEqual("bad", msg.Return.Exception.Reason); + Assert.AreEqual(Rpc.Exception.Type.overloaded, msg.Return.Exception.TheType); + } + ); + } + + [TestMethod] + public void DcMessageReturnTakeFromOtherQuestion() + { + ConstructReconstructTest( + () => new Message() + { + Return = new Return() + { + AnswerId = 123u, + ReleaseParamCaps = true, + TakeFromOtherQuestion = 321u + } + }, + msg => + { + Assert.IsNotNull(msg.Return); + Assert.AreEqual(123u, msg.Return.AnswerId); + Assert.IsTrue(msg.Return.ReleaseParamCaps); + Assert.AreEqual(321u, msg.Return.TakeFromOtherQuestion); + } + ); + } + + [TestMethod] + public void DcMessageFinish() + { + ConstructReconstructTest( + () => new Message() + { + Finish = new Finish() + { + QuestionId = 321u, + ReleaseResultCaps = true + } + }, + msg => + { + Assert.IsNotNull(msg.Finish); + Assert.AreEqual(321u, msg.Finish.QuestionId); + Assert.IsTrue(msg.Finish.ReleaseResultCaps); + } + ); + } + + [TestMethod] + public void DcMessageResolve() + { + ConstructReconstructTest( + () => new Message() + { + Resolve = new Resolve() + { + PromiseId = 555u, + Exception = new Rpc.Exception() + { + Reason = "error", + TheType = Rpc.Exception.Type.failed + } + } + }, + msg => + { + Assert.IsNotNull(msg.Resolve); + Assert.AreEqual(555u, msg.Resolve.PromiseId); + Assert.AreEqual("error", msg.Resolve.Exception.Reason); + Assert.AreEqual(Rpc.Exception.Type.failed, msg.Resolve.Exception.TheType); + } + ); + } + + [TestMethod] + public void DcMessageRelease() + { + ConstructReconstructTest( + () => new Message() + { + Release = new Release() + { + Id = 6000u, + ReferenceCount = 6u + } + }, + msg => + { + Assert.IsNotNull(msg.Release); + Assert.AreEqual(6000u, msg.Release.Id); + Assert.AreEqual(6u, msg.Release.ReferenceCount); + } + ); + } + + [TestMethod] + public void DcMessageBootstrap() + { + ConstructReconstructTest( + () => new Message() + { + Bootstrap = new Bootstrap() + { + QuestionId = 99u + } + }, + msg => + { + Assert.IsNotNull(msg.Bootstrap); + Assert.AreEqual(99u, msg.Bootstrap.QuestionId); + } + ); + } + + [TestMethod] + public void DcMessageProvide() + { + ConstructReconstructTest( + () => new Message() + { + Provide = new Provide() + { + Target = new MessageTarget() + { + ImportedCap = 7u + } + } + }, + msg => + { + Assert.IsNotNull(msg.Provide); + Assert.IsNotNull(msg.Provide.Target); + Assert.AreEqual(7u, msg.Provide.Target.ImportedCap); + } + ); + } + + [TestMethod] + public void DcMessageDisembargo() + { + ConstructReconstructTest( + () => new Message() + { + Disembargo = new Disembargo() + { + Context = new Disembargo.context() + { + ReceiverLoopback = 900u + }, + Target = new MessageTarget() + } + }, + msg => + { + Assert.IsNotNull(msg.Disembargo); + Assert.IsNotNull(msg.Disembargo.Context); + Assert.AreEqual(900u, msg.Disembargo.Context.ReceiverLoopback); + Assert.IsNotNull(msg.Disembargo.Target); + Assert.IsNull(msg.Disembargo.Target.ImportedCap); + Assert.IsNull(msg.Disembargo.Target.PromisedAnswer); + Assert.AreEqual(MessageTarget.WHICH.undefined, msg.Disembargo.Target.which); + } + ); + } } } diff --git a/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs b/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs index 4edf578..ce0205a 100644 --- a/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs +++ b/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs @@ -229,7 +229,7 @@ namespace Capnp.Rpc } /// - /// Checkes whether a given type qualifies as cpapbility interface./> on failure. + /// Checks whether a given type qualifies as cpapbility interface. /// /// type to check /// true when is a capability interface diff --git a/Capnp.Net.Runtime/Rpc/PendingQuestion.cs b/Capnp.Net.Runtime/Rpc/PendingQuestion.cs index 6d4f34c..b660d6d 100644 --- a/Capnp.Net.Runtime/Rpc/PendingQuestion.cs +++ b/Capnp.Net.Runtime/Rpc/PendingQuestion.cs @@ -165,7 +165,7 @@ namespace Capnp.Rpc SetReturned(); } - _tcs.TrySetException(new RpcException(exception.Reason)); + _tcs.TrySetException(new RpcException(exception.Reason ?? "unknown reason")); } internal void OnException(System.Exception exception) @@ -313,7 +313,7 @@ namespace Capnp.Rpc } var msg = (Message.WRITER)inParams!.MsgBuilder!.Root!; - Debug.Assert(msg.Call.Target.which != MessageTarget.WHICH.undefined); + Debug.Assert(msg.Call!.Target.which != MessageTarget.WHICH.undefined); var call = msg.Call; call.QuestionId = QuestionId; call.SendResultsTo.which = IsTailCall ? diff --git a/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs b/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs index e2a49ac..fd47935 100644 --- a/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs +++ b/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs @@ -101,7 +101,7 @@ namespace Capnp.Rpc protected override void GetMessageTarget(MessageTarget.WRITER wr) { wr.which = MessageTarget.WHICH.PromisedAnswer; - wr.PromisedAnswer.QuestionId = _question.QuestionId; + wr.PromisedAnswer!.QuestionId = _question.QuestionId; _access.Serialize(wr.PromisedAnswer); } @@ -178,7 +178,7 @@ namespace Capnp.Rpc var call = base.SetupMessage(args, interfaceId, methodId); call.Target.which = MessageTarget.WHICH.PromisedAnswer; - call.Target.PromisedAnswer.QuestionId = _question.QuestionId; + call.Target.PromisedAnswer!.QuestionId = _question.QuestionId; _access.Serialize(call.Target.PromisedAnswer); return call; @@ -243,8 +243,8 @@ namespace Capnp.Rpc if (endpoint == _ep) { writer.which = CapDescriptor.WHICH.ReceiverAnswer; - _access.Serialize(writer.ReceiverAnswer); - writer.ReceiverAnswer.QuestionId = _question.QuestionId; + _access.Serialize(writer.ReceiverAnswer!); + writer.ReceiverAnswer!.QuestionId = _question.QuestionId; } else if (_question.IsTailCall) { diff --git a/Capnp.Net.Runtime/Rpc/RemoteCapability.cs b/Capnp.Net.Runtime/Rpc/RemoteCapability.cs index f198b29..1852c56 100644 --- a/Capnp.Net.Runtime/Rpc/RemoteCapability.cs +++ b/Capnp.Net.Runtime/Rpc/RemoteCapability.cs @@ -29,7 +29,7 @@ namespace Capnp.Rpc callMsg.which = Message.WHICH.Call; - var call = callMsg.Call; + var call = callMsg.Call!; call.AllowThirdPartyTailCall = false; call.InterfaceId = interfaceId; call.MethodId = methodId; diff --git a/Capnp.Net.Runtime/Rpc/RpcEngine.cs b/Capnp.Net.Runtime/Rpc/RpcEngine.cs index 195fcd5..11c99b5 100644 --- a/Capnp.Net.Runtime/Rpc/RpcEngine.cs +++ b/Capnp.Net.Runtime/Rpc/RpcEngine.cs @@ -210,7 +210,7 @@ namespace Capnp.Rpc var mb = MessageBuilder.Create(); var msg = mb.BuildRoot(); msg.which = Message.WHICH.Abort; - msg.Abort.Reason = reason; + msg.Abort!.Reason = reason; Tx(mb.Frame); } @@ -233,18 +233,18 @@ namespace Capnp.Rpc var mb = MessageBuilder.Create(); var msg = mb.BuildRoot(); msg.which = Message.WHICH.Resolve; - var resolve = msg.Resolve; + var resolve = msg.Resolve!; try { var resolvedCap = resolvedCapGetter(); resolve.which = Resolve.WHICH.Cap; - resolvedCap.Export(this, resolve.Cap); + resolvedCap.Export(this, resolve.Cap!); } catch (System.Exception ex) { resolve.which = Resolve.WHICH.Exception; - resolve.Exception.Reason = ex.Message; + resolve.Exception!.Reason = ex.Message; } resolve.PromiseId = preliminaryId; @@ -364,7 +364,7 @@ namespace Capnp.Rpc var ans = bootstrap.MsgBuilder!.BuildRoot(); ans.which = Message.WHICH.Return; - var ret = ans.Return; + var ret = ans.Return!; ret.AnswerId = q; Task bootstrapTask; @@ -374,7 +374,7 @@ namespace Capnp.Rpc { ret.which = Return.WHICH.Results; bootstrap.SetCapability(bootstrap.ProvideCapability(LocalCapability.Create(bootstrapCap))); - ret.Results.Content = bootstrap; + ret.Results!.Content = bootstrap; bootstrapTask = Task.FromResult(bootstrap); } @@ -383,7 +383,7 @@ namespace Capnp.Rpc Logger.LogWarning("Peer asked for bootstrap capability, but no bootstrap capability was set."); ret.which = Return.WHICH.Exception; - ret.Exception.Reason = "No bootstrap capability present"; + ret.Exception!.Reason = "No bootstrap capability present"; bootstrapTask = Task.FromException(new RpcException(ret.Exception.Reason)); } @@ -403,7 +403,7 @@ namespace Capnp.Rpc } - if (bootstrapCap != null) + if (ret.Results != null) { ExportCapTableAndSend(bootstrap, ret.Results); } @@ -420,7 +420,7 @@ namespace Capnp.Rpc { var rmsg = mb.BuildRoot(); rmsg.which = Message.WHICH.Return; - var ret = rmsg.Return; + var ret = rmsg.Return!; ret.AnswerId = req.QuestionId; return ret; @@ -488,7 +488,7 @@ namespace Capnp.Rpc { case Call.sendResultsTo.WHICH.Caller: ret.which = Return.WHICH.Results; - ret.Results.Content = results.Rewrap(); + ret.Results!.Content = results.Rewrap(); ret.ReleaseParamCaps = releaseParamCaps; ExportCapTableAndSend(results, ret.Results); pendingAnswer.CapTable = ret.Results.CapTable; @@ -530,7 +530,7 @@ namespace Capnp.Rpc ReturnCall(ret => { ret.which = Return.WHICH.Exception; - ret.Exception.Reason = exception.Message; + ret.Exception!.Reason = exception.Message; ret.ReleaseParamCaps = releaseParamCaps; }); } @@ -824,7 +824,7 @@ namespace Capnp.Rpc break; case Resolve.WHICH.Exception: - resolvableCap.Break(resolve.Exception.Reason); + resolvableCap.Break(resolve.Exception.Reason ?? "unknown reason"); break; default: @@ -844,8 +844,8 @@ namespace Capnp.Rpc mb.InitCapTable(); var wr = mb.BuildRoot(); wr.which = Message.WHICH.Disembargo; - wr.Disembargo.Context.which = Disembargo.context.WHICH.ReceiverLoopback; - wr.Disembargo.Context.ReceiverLoopback = disembargo.Context.SenderLoopback; + wr.Disembargo!.Context.which = Disembargo.context.WHICH.ReceiverLoopback; + wr.Disembargo!.Context.ReceiverLoopback = disembargo.Context.SenderLoopback; var reply = wr.Disembargo; switch (disembargo.Target.which) @@ -871,7 +871,7 @@ namespace Capnp.Rpc var promisedAnswer = disembargo.Target.PromisedAnswer; reply.Target.which = MessageTarget.WHICH.PromisedAnswer; var replyPromisedAnswer = reply.Target.PromisedAnswer; - replyPromisedAnswer.QuestionId = disembargo.Target.PromisedAnswer.QuestionId; + replyPromisedAnswer!.QuestionId = disembargo.Target.PromisedAnswer.QuestionId; int count = promisedAnswer.Transform.Count; replyPromisedAnswer.Transform.Init(count); @@ -1162,7 +1162,7 @@ namespace Capnp.Rpc var req = mb.BuildRoot(); req.which = Message.WHICH.Bootstrap; var pendingBootstrap = AllocateQuestion(null, null); - req.Bootstrap.QuestionId = pendingBootstrap.QuestionId; + req.Bootstrap!.QuestionId = pendingBootstrap.QuestionId; Tx(mb.Frame); @@ -1237,7 +1237,7 @@ namespace Capnp.Rpc mb.InitCapTable(); var reply = mb.BuildRoot(); reply.which = Message.WHICH.Unimplemented; - Reserializing.DeepCopy(msg, reply.Unimplemented.Rewrap()); + Reserializing.DeepCopy(msg, reply.Unimplemented!.Rewrap()); Tx(mb.Frame); } @@ -1472,8 +1472,8 @@ namespace Capnp.Rpc var mb = MessageBuilder.Create(); var msg = mb.BuildRoot(); msg.which = Message.WHICH.Finish; - msg.Finish.QuestionId = questionId; - msg.Finish.ReleaseResultCaps = false; + msg.Finish!.QuestionId = questionId; + msg.Finish!.ReleaseResultCaps = false; try { @@ -1518,8 +1518,8 @@ namespace Capnp.Rpc var mb = MessageBuilder.Create(); var msg = mb.BuildRoot(); msg.which = Message.WHICH.Release; - msg.Release.Id = importId; - msg.Release.ReferenceCount = (uint)count; + msg.Release!.Id = importId; + msg.Release!.ReferenceCount = (uint)count; try { @@ -1540,7 +1540,7 @@ namespace Capnp.Rpc mb.InitCapTable(); var msg = mb.BuildRoot(); msg.which = Message.WHICH.Disembargo; - describe(msg.Disembargo.Target); + describe(msg.Disembargo!.Target); var ctx = msg.Disembargo.Context; ctx.which = Disembargo.context.WHICH.SenderLoopback; ctx.SenderLoopback = id; diff --git a/Capnp.Net.Runtime/Rpc/rpc.cs b/Capnp.Net.Runtime/Rpc/rpc.cs index 066bd98..632c62e 100644 --- a/Capnp.Net.Runtime/Rpc/rpc.cs +++ b/Capnp.Net.Runtime/Rpc/rpc.cs @@ -1,17 +1,19 @@ #pragma warning disable CS1591 -#nullable disable using Capnp; using Capnp.Rpc; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; namespace Capnp.Rpc { + [TypeId(0x91b79f1f808db032UL)] public class Message : ICapnpSerializable { + public const UInt64 typeId = 0x91b79f1f808db032UL; public enum WHICH : ushort { Unimplemented = 0, @@ -58,13 +60,13 @@ namespace Capnp.Rpc Release = CapnpSerializable.Create(reader.Release); break; case WHICH.ObsoleteSave: - ObsoleteSave = CapnpSerializable.Create(reader.ObsoleteSave); + ObsoleteSave = CapnpSerializable.Create(reader.ObsoleteSave); break; case WHICH.Bootstrap: Bootstrap = CapnpSerializable.Create(reader.Bootstrap); break; case WHICH.ObsoleteDelete: - ObsoleteDelete = CapnpSerializable.Create(reader.ObsoleteDelete); + ObsoleteDelete = CapnpSerializable.Create(reader.ObsoleteDelete); break; case WHICH.Provide: Provide = CapnpSerializable.Create(reader.Provide); @@ -84,7 +86,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -147,46 +149,46 @@ namespace Capnp.Rpc switch (which) { case WHICH.Unimplemented: - Unimplemented?.serialize(writer.Unimplemented); + Unimplemented?.serialize(writer.Unimplemented!); break; case WHICH.Abort: - Abort?.serialize(writer.Abort); + Abort?.serialize(writer.Abort!); break; case WHICH.Call: - Call?.serialize(writer.Call); + Call?.serialize(writer.Call!); break; case WHICH.Return: - Return?.serialize(writer.Return); + Return?.serialize(writer.Return!); break; case WHICH.Finish: - Finish?.serialize(writer.Finish); + Finish?.serialize(writer.Finish!); break; case WHICH.Resolve: - Resolve?.serialize(writer.Resolve); + Resolve?.serialize(writer.Resolve!); break; case WHICH.Release: - Release?.serialize(writer.Release); + Release?.serialize(writer.Release!); break; case WHICH.ObsoleteSave: - writer.ObsoleteSave.SetObject(ObsoleteSave); + writer.ObsoleteSave!.SetObject(ObsoleteSave); break; case WHICH.Bootstrap: - Bootstrap?.serialize(writer.Bootstrap); + Bootstrap?.serialize(writer.Bootstrap!); break; case WHICH.ObsoleteDelete: - writer.ObsoleteDelete.SetObject(ObsoleteDelete); + writer.ObsoleteDelete!.SetObject(ObsoleteDelete); break; case WHICH.Provide: - Provide?.serialize(writer.Provide); + Provide?.serialize(writer.Provide!); break; case WHICH.Accept: - Accept?.serialize(writer.Accept); + Accept?.serialize(writer.Accept!); break; case WHICH.Join: - Join?.serialize(writer.Join); + Join?.serialize(writer.Join!); break; case WHICH.Disembargo: - Disembargo?.serialize(writer.Disembargo); + Disembargo?.serialize(writer.Disembargo!); break; } } @@ -200,9 +202,9 @@ namespace Capnp.Rpc { } - public Capnp.Rpc.Message Unimplemented + public Capnp.Rpc.Message? Unimplemented { - get => _which == WHICH.Unimplemented ? (Capnp.Rpc.Message)_content : null; + get => _which == WHICH.Unimplemented ? (Capnp.Rpc.Message?)_content : null; set { _which = WHICH.Unimplemented; @@ -210,9 +212,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Exception Abort + public Capnp.Rpc.Exception? Abort { - get => _which == WHICH.Abort ? (Capnp.Rpc.Exception)_content : null; + get => _which == WHICH.Abort ? (Capnp.Rpc.Exception?)_content : null; set { _which = WHICH.Abort; @@ -220,9 +222,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Call Call + public Capnp.Rpc.Call? Call { - get => _which == WHICH.Call ? (Capnp.Rpc.Call)_content : null; + get => _which == WHICH.Call ? (Capnp.Rpc.Call?)_content : null; set { _which = WHICH.Call; @@ -230,9 +232,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Return Return + public Capnp.Rpc.Return? Return { - get => _which == WHICH.Return ? (Capnp.Rpc.Return)_content : null; + get => _which == WHICH.Return ? (Capnp.Rpc.Return?)_content : null; set { _which = WHICH.Return; @@ -240,9 +242,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Finish Finish + public Capnp.Rpc.Finish? Finish { - get => _which == WHICH.Finish ? (Capnp.Rpc.Finish)_content : null; + get => _which == WHICH.Finish ? (Capnp.Rpc.Finish?)_content : null; set { _which = WHICH.Finish; @@ -250,9 +252,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Resolve Resolve + public Capnp.Rpc.Resolve? Resolve { - get => _which == WHICH.Resolve ? (Capnp.Rpc.Resolve)_content : null; + get => _which == WHICH.Resolve ? (Capnp.Rpc.Resolve?)_content : null; set { _which = WHICH.Resolve; @@ -260,9 +262,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Release Release + public Capnp.Rpc.Release? Release { - get => _which == WHICH.Release ? (Capnp.Rpc.Release)_content : null; + get => _which == WHICH.Release ? (Capnp.Rpc.Release?)_content : null; set { _which = WHICH.Release; @@ -270,9 +272,9 @@ namespace Capnp.Rpc } } - public AnyPointer ObsoleteSave + public object? ObsoleteSave { - get => _which == WHICH.ObsoleteSave ? (AnyPointer)_content : null; + get => _which == WHICH.ObsoleteSave ? (object?)_content : null; set { _which = WHICH.ObsoleteSave; @@ -280,9 +282,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Bootstrap Bootstrap + public Capnp.Rpc.Bootstrap? Bootstrap { - get => _which == WHICH.Bootstrap ? (Capnp.Rpc.Bootstrap)_content : null; + get => _which == WHICH.Bootstrap ? (Capnp.Rpc.Bootstrap?)_content : null; set { _which = WHICH.Bootstrap; @@ -290,9 +292,9 @@ namespace Capnp.Rpc } } - public AnyPointer ObsoleteDelete + public object? ObsoleteDelete { - get => _which == WHICH.ObsoleteDelete ? (AnyPointer)_content : null; + get => _which == WHICH.ObsoleteDelete ? (object?)_content : null; set { _which = WHICH.ObsoleteDelete; @@ -300,9 +302,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Provide Provide + public Capnp.Rpc.Provide? Provide { - get => _which == WHICH.Provide ? (Capnp.Rpc.Provide)_content : null; + get => _which == WHICH.Provide ? (Capnp.Rpc.Provide?)_content : null; set { _which = WHICH.Provide; @@ -310,9 +312,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Accept Accept + public Capnp.Rpc.Accept? Accept { - get => _which == WHICH.Accept ? (Capnp.Rpc.Accept)_content : null; + get => _which == WHICH.Accept ? (Capnp.Rpc.Accept?)_content : null; set { _which = WHICH.Accept; @@ -320,9 +322,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Join Join + public Capnp.Rpc.Join? Join { - get => _which == WHICH.Join ? (Capnp.Rpc.Join)_content : null; + get => _which == WHICH.Join ? (Capnp.Rpc.Join?)_content : null; set { _which = WHICH.Join; @@ -330,9 +332,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Disembargo Disembargo + public Capnp.Rpc.Disembargo? Disembargo { - get => _which == WHICH.Disembargo ? (Capnp.Rpc.Disembargo)_content : null; + get => _which == WHICH.Disembargo ? (Capnp.Rpc.Disembargo?)_content : null; set { _which = WHICH.Disembargo; @@ -381,99 +383,115 @@ namespace Capnp.Rpc set => this.WriteData(0U, (ushort)value, (ushort)0); } - public Capnp.Rpc.Message.WRITER Unimplemented + [DisallowNull] + public Capnp.Rpc.Message.WRITER? Unimplemented { get => which == WHICH.Unimplemented ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Exception.WRITER Abort + [DisallowNull] + public Capnp.Rpc.Exception.WRITER? Abort { get => which == WHICH.Abort ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Call.WRITER Call + [DisallowNull] + public Capnp.Rpc.Call.WRITER? Call { get => which == WHICH.Call ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Return.WRITER Return + [DisallowNull] + public Capnp.Rpc.Return.WRITER? Return { get => which == WHICH.Return ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Finish.WRITER Finish + [DisallowNull] + public Capnp.Rpc.Finish.WRITER? Finish { get => which == WHICH.Finish ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Resolve.WRITER Resolve + [DisallowNull] + public Capnp.Rpc.Resolve.WRITER? Resolve { get => which == WHICH.Resolve ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Release.WRITER Release + [DisallowNull] + public Capnp.Rpc.Release.WRITER? Release { get => which == WHICH.Release ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public DynamicSerializerState ObsoleteSave + [DisallowNull] + public DynamicSerializerState? ObsoleteSave { get => which == WHICH.ObsoleteSave ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Bootstrap.WRITER Bootstrap + [DisallowNull] + public Capnp.Rpc.Bootstrap.WRITER? Bootstrap { get => which == WHICH.Bootstrap ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public DynamicSerializerState ObsoleteDelete + [DisallowNull] + public DynamicSerializerState? ObsoleteDelete { get => which == WHICH.ObsoleteDelete ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Provide.WRITER Provide + [DisallowNull] + public Capnp.Rpc.Provide.WRITER? Provide { get => which == WHICH.Provide ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Accept.WRITER Accept + [DisallowNull] + public Capnp.Rpc.Accept.WRITER? Accept { get => which == WHICH.Accept ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Join.WRITER Join + [DisallowNull] + public Capnp.Rpc.Join.WRITER? Join { get => which == WHICH.Join ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Disembargo.WRITER Disembargo + [DisallowNull] + public Capnp.Rpc.Disembargo.WRITER? Disembargo { get => which == WHICH.Disembargo ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } } } + [TypeId(0xe94ccf8031176ec4UL)] public class Bootstrap : ICapnpSerializable { + public const UInt64 typeId = 0xe94ccf8031176ec4UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); QuestionId = reader.QuestionId; - DeprecatedObjectId = CapnpSerializable.Create(reader.DeprecatedObjectId); + DeprecatedObjectId = CapnpSerializable.Create(reader.DeprecatedObjectId); applyDefaults(); } @@ -498,7 +516,7 @@ namespace Capnp.Rpc set; } - public AnyPointer DeprecatedObjectId + public object? DeprecatedObjectId { get; set; @@ -540,8 +558,10 @@ namespace Capnp.Rpc } } + [TypeId(0x836a53ce789d4cd4UL)] public class Call : ICapnpSerializable { + public const UInt64 typeId = 0x836a53ce789d4cd4UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); @@ -550,7 +570,7 @@ namespace Capnp.Rpc InterfaceId = reader.InterfaceId; MethodId = reader.MethodId; Params = CapnpSerializable.Create(reader.Params); - SendResultsTo = CapnpSerializable.Create(reader.SendResultsTo); + SendResultsTo = CapnpSerializable.Create(reader.SendResultsTo); AllowThirdPartyTailCall = reader.AllowThirdPartyTailCall; applyDefaults(); } @@ -581,7 +601,7 @@ namespace Capnp.Rpc set; } - public Capnp.Rpc.MessageTarget Target + public Capnp.Rpc.MessageTarget? Target { get; set; @@ -599,13 +619,13 @@ namespace Capnp.Rpc set; } - public Capnp.Rpc.Payload Params + public Capnp.Rpc.Payload? Params { get; set; } - public Capnp.Rpc.Call.@sendResultsTo SendResultsTo + public Capnp.Rpc.Call.sendResultsTo? SendResultsTo { get; set; @@ -634,7 +654,7 @@ namespace Capnp.Rpc public ulong InterfaceId => ctx.ReadDataULong(64UL, 0UL); public ushort MethodId => ctx.ReadDataUShort(32UL, (ushort)0); public Capnp.Rpc.Payload.READER Params => ctx.ReadStruct(1, Capnp.Rpc.Payload.READER.create); - public @sendResultsTo.READER SendResultsTo => new @sendResultsTo.READER(ctx); + public sendResultsTo.READER SendResultsTo => new sendResultsTo.READER(ctx); public bool AllowThirdPartyTailCall => ctx.ReadDataBool(128UL, false); } @@ -675,9 +695,9 @@ namespace Capnp.Rpc set => Link(1, value); } - public @sendResultsTo.WRITER SendResultsTo + public sendResultsTo.WRITER SendResultsTo { - get => Rewrap<@sendResultsTo.WRITER>(); + get => Rewrap(); } public bool AllowThirdPartyTailCall @@ -687,8 +707,10 @@ namespace Capnp.Rpc } } - public class @sendResultsTo : ICapnpSerializable + [TypeId(0xdae8b0f61aab5f99UL)] + public class sendResultsTo : ICapnpSerializable { + public const UInt64 typeId = 0xdae8b0f61aab5f99UL; public enum WHICH : ushort { Caller = 0, @@ -709,7 +731,7 @@ namespace Capnp.Rpc which = reader.which; break; case WHICH.ThirdParty: - ThirdParty = CapnpSerializable.Create(reader.ThirdParty); + ThirdParty = CapnpSerializable.Create(reader.ThirdParty); break; } @@ -717,7 +739,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -749,7 +771,7 @@ namespace Capnp.Rpc case WHICH.Yourself: break; case WHICH.ThirdParty: - writer.ThirdParty.SetObject(ThirdParty); + writer.ThirdParty!.SetObject(ThirdParty); break; } } @@ -763,9 +785,9 @@ namespace Capnp.Rpc { } - public AnyPointer ThirdParty + public object? ThirdParty { - get => _which == WHICH.ThirdParty ? (AnyPointer)_content : null; + get => _which == WHICH.ThirdParty ? (object?)_content : null; set { _which = WHICH.ThirdParty; @@ -800,17 +822,20 @@ namespace Capnp.Rpc set => this.WriteData(48U, (ushort)value, (ushort)0); } - public DynamicSerializerState ThirdParty + [DisallowNull] + public DynamicSerializerState? ThirdParty { get => which == WHICH.ThirdParty ? BuildPointer(2) : default; - set => Link(2, value); + set => Link(2, value!); } } } } + [TypeId(0x9e19b28d3db3573aUL)] public class Return : ICapnpSerializable { + public const UInt64 typeId = 0x9e19b28d3db3573aUL; public enum WHICH : ushort { Results = 0, @@ -843,7 +868,7 @@ namespace Capnp.Rpc TakeFromOtherQuestion = reader.TakeFromOtherQuestion; break; case WHICH.AcceptFromThirdParty: - AcceptFromThirdParty = CapnpSerializable.Create(reader.AcceptFromThirdParty); + AcceptFromThirdParty = CapnpSerializable.Create(reader.AcceptFromThirdParty); break; } @@ -853,7 +878,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -890,20 +915,20 @@ namespace Capnp.Rpc switch (which) { case WHICH.Results: - Results?.serialize(writer.Results); + Results?.serialize(writer.Results!); break; case WHICH.Exception: - Exception?.serialize(writer.Exception); + Exception?.serialize(writer.Exception!); break; case WHICH.Canceled: break; case WHICH.ResultsSentElsewhere: break; case WHICH.TakeFromOtherQuestion: - writer.TakeFromOtherQuestion = TakeFromOtherQuestion.Value; + writer.TakeFromOtherQuestion = TakeFromOtherQuestion!.Value; break; case WHICH.AcceptFromThirdParty: - writer.AcceptFromThirdParty.SetObject(AcceptFromThirdParty); + writer.AcceptFromThirdParty!.SetObject(AcceptFromThirdParty); break; } @@ -933,9 +958,9 @@ namespace Capnp.Rpc } = true; - public Capnp.Rpc.Payload Results + public Capnp.Rpc.Payload? Results { - get => _which == WHICH.Results ? (Capnp.Rpc.Payload)_content : null; + get => _which == WHICH.Results ? (Capnp.Rpc.Payload?)_content : null; set { _which = WHICH.Results; @@ -943,9 +968,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Exception Exception + public Capnp.Rpc.Exception? Exception { - get => _which == WHICH.Exception ? (Capnp.Rpc.Exception)_content : null; + get => _which == WHICH.Exception ? (Capnp.Rpc.Exception?)_content : null; set { _which = WHICH.Exception; @@ -955,7 +980,7 @@ namespace Capnp.Rpc public uint? TakeFromOtherQuestion { - get => _which == WHICH.TakeFromOtherQuestion ? (uint? )_content : null; + get => _which == WHICH.TakeFromOtherQuestion ? (uint?)_content : null; set { _which = WHICH.TakeFromOtherQuestion; @@ -963,9 +988,9 @@ namespace Capnp.Rpc } } - public AnyPointer AcceptFromThirdParty + public object? AcceptFromThirdParty { - get => _which == WHICH.AcceptFromThirdParty ? (AnyPointer)_content : null; + get => _which == WHICH.AcceptFromThirdParty ? (object?)_content : null; set { _which = WHICH.AcceptFromThirdParty; @@ -1018,16 +1043,18 @@ namespace Capnp.Rpc set => this.WriteData(32UL, value, true); } - public Capnp.Rpc.Payload.WRITER Results + [DisallowNull] + public Capnp.Rpc.Payload.WRITER? Results { get => which == WHICH.Results ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Exception.WRITER Exception + [DisallowNull] + public Capnp.Rpc.Exception.WRITER? Exception { get => which == WHICH.Exception ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } public uint TakeFromOtherQuestion @@ -1036,16 +1063,19 @@ namespace Capnp.Rpc set => this.WriteData(64UL, value, 0U); } - public DynamicSerializerState AcceptFromThirdParty + [DisallowNull] + public DynamicSerializerState? AcceptFromThirdParty { get => which == WHICH.AcceptFromThirdParty ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } } } + [TypeId(0xd37d2eb2c2f80e63UL)] public class Finish : ICapnpSerializable { + public const UInt64 typeId = 0xd37d2eb2c2f80e63UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); @@ -1118,8 +1148,10 @@ namespace Capnp.Rpc } } + [TypeId(0xbbc29655fa89086eUL)] public class Resolve : ICapnpSerializable { + public const UInt64 typeId = 0xbbc29655fa89086eUL; public enum WHICH : ushort { Cap = 0, @@ -1145,7 +1177,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -1172,10 +1204,10 @@ namespace Capnp.Rpc switch (which) { case WHICH.Cap: - Cap?.serialize(writer.Cap); + Cap?.serialize(writer.Cap!); break; case WHICH.Exception: - Exception?.serialize(writer.Exception); + Exception?.serialize(writer.Exception!); break; } @@ -1197,9 +1229,9 @@ namespace Capnp.Rpc set; } - public Capnp.Rpc.CapDescriptor Cap + public Capnp.Rpc.CapDescriptor? Cap { - get => _which == WHICH.Cap ? (Capnp.Rpc.CapDescriptor)_content : null; + get => _which == WHICH.Cap ? (Capnp.Rpc.CapDescriptor?)_content : null; set { _which = WHICH.Cap; @@ -1207,9 +1239,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.Exception Exception + public Capnp.Rpc.Exception? Exception { - get => _which == WHICH.Exception ? (Capnp.Rpc.Exception)_content : null; + get => _which == WHICH.Exception ? (Capnp.Rpc.Exception?)_content : null; set { _which = WHICH.Exception; @@ -1253,22 +1285,26 @@ namespace Capnp.Rpc set => this.WriteData(0UL, value, 0U); } - public Capnp.Rpc.CapDescriptor.WRITER Cap + [DisallowNull] + public Capnp.Rpc.CapDescriptor.WRITER? Cap { get => which == WHICH.Cap ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.Exception.WRITER Exception + [DisallowNull] + public Capnp.Rpc.Exception.WRITER? Exception { get => which == WHICH.Exception ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } } } + [TypeId(0xad1a6c0d7dd07497UL)] public class Release : ICapnpSerializable { + public const UInt64 typeId = 0xad1a6c0d7dd07497UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); @@ -1340,13 +1376,15 @@ namespace Capnp.Rpc } } + [TypeId(0xf964368b0fbd3711UL)] public class Disembargo : ICapnpSerializable { + public const UInt64 typeId = 0xf964368b0fbd3711UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); Target = CapnpSerializable.Create(reader.Target); - Context = CapnpSerializable.Create(reader.Context); + Context = CapnpSerializable.Create(reader.Context); applyDefaults(); } @@ -1365,13 +1403,13 @@ namespace Capnp.Rpc { } - public Capnp.Rpc.MessageTarget Target + public Capnp.Rpc.MessageTarget? Target { get; set; } - public Capnp.Rpc.Disembargo.@context Context + public Capnp.Rpc.Disembargo.context? Context { get; set; @@ -1389,7 +1427,7 @@ namespace Capnp.Rpc public static implicit operator DeserializerState(READER reader) => reader.ctx; public static implicit operator READER(DeserializerState ctx) => new READER(ctx); public Capnp.Rpc.MessageTarget.READER Target => ctx.ReadStruct(0, Capnp.Rpc.MessageTarget.READER.create); - public @context.READER Context => new @context.READER(ctx); + public context.READER Context => new context.READER(ctx); } public class WRITER : SerializerState @@ -1405,14 +1443,16 @@ namespace Capnp.Rpc set => Link(0, value); } - public @context.WRITER Context + public context.WRITER Context { - get => Rewrap<@context.WRITER>(); + get => Rewrap(); } } - public class @context : ICapnpSerializable + [TypeId(0xd562b4df655bdd4dUL)] + public class context : ICapnpSerializable { + public const UInt64 typeId = 0xd562b4df655bdd4dUL; public enum WHICH : ushort { SenderLoopback = 0, @@ -1445,7 +1485,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -1477,15 +1517,15 @@ namespace Capnp.Rpc switch (which) { case WHICH.SenderLoopback: - writer.SenderLoopback = SenderLoopback.Value; + writer.SenderLoopback = SenderLoopback!.Value; break; case WHICH.ReceiverLoopback: - writer.ReceiverLoopback = ReceiverLoopback.Value; + writer.ReceiverLoopback = ReceiverLoopback!.Value; break; case WHICH.Accept: break; case WHICH.Provide: - writer.Provide = Provide.Value; + writer.Provide = Provide!.Value; break; } } @@ -1501,7 +1541,7 @@ namespace Capnp.Rpc public uint? SenderLoopback { - get => _which == WHICH.SenderLoopback ? (uint? )_content : null; + get => _which == WHICH.SenderLoopback ? (uint?)_content : null; set { _which = WHICH.SenderLoopback; @@ -1511,7 +1551,7 @@ namespace Capnp.Rpc public uint? ReceiverLoopback { - get => _which == WHICH.ReceiverLoopback ? (uint? )_content : null; + get => _which == WHICH.ReceiverLoopback ? (uint?)_content : null; set { _which = WHICH.ReceiverLoopback; @@ -1521,7 +1561,7 @@ namespace Capnp.Rpc public uint? Provide { - get => _which == WHICH.Provide ? (uint? )_content : null; + get => _which == WHICH.Provide ? (uint?)_content : null; set { _which = WHICH.Provide; @@ -1579,14 +1619,16 @@ namespace Capnp.Rpc } } + [TypeId(0x9c6a046bfbc1ac5aUL)] public class Provide : ICapnpSerializable { + public const UInt64 typeId = 0x9c6a046bfbc1ac5aUL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); QuestionId = reader.QuestionId; Target = CapnpSerializable.Create(reader.Target); - Recipient = CapnpSerializable.Create(reader.Recipient); + Recipient = CapnpSerializable.Create(reader.Recipient); applyDefaults(); } @@ -1612,13 +1654,13 @@ namespace Capnp.Rpc set; } - public Capnp.Rpc.MessageTarget Target + public Capnp.Rpc.MessageTarget? Target { get; set; } - public AnyPointer Recipient + public object? Recipient { get; set; @@ -1667,13 +1709,15 @@ namespace Capnp.Rpc } } + [TypeId(0xd4c9b56290554016UL)] public class Accept : ICapnpSerializable { + public const UInt64 typeId = 0xd4c9b56290554016UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); QuestionId = reader.QuestionId; - Provision = CapnpSerializable.Create(reader.Provision); + Provision = CapnpSerializable.Create(reader.Provision); Embargo = reader.Embargo; applyDefaults(); } @@ -1700,7 +1744,7 @@ namespace Capnp.Rpc set; } - public AnyPointer Provision + public object? Provision { get; set; @@ -1755,14 +1799,16 @@ namespace Capnp.Rpc } } + [TypeId(0xfbe1980490e001afUL)] public class Join : ICapnpSerializable { + public const UInt64 typeId = 0xfbe1980490e001afUL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); QuestionId = reader.QuestionId; Target = CapnpSerializable.Create(reader.Target); - KeyPart = CapnpSerializable.Create(reader.KeyPart); + KeyPart = CapnpSerializable.Create(reader.KeyPart); applyDefaults(); } @@ -1788,13 +1834,13 @@ namespace Capnp.Rpc set; } - public Capnp.Rpc.MessageTarget Target + public Capnp.Rpc.MessageTarget? Target { get; set; } - public AnyPointer KeyPart + public object? KeyPart { get; set; @@ -1843,8 +1889,10 @@ namespace Capnp.Rpc } } + [TypeId(0x95bc14545813fbc1UL)] public class MessageTarget : ICapnpSerializable { + public const UInt64 typeId = 0x95bc14545813fbc1UL; public enum WHICH : ushort { ImportedCap = 0, @@ -1869,7 +1917,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -1896,10 +1944,10 @@ namespace Capnp.Rpc switch (which) { case WHICH.ImportedCap: - writer.ImportedCap = ImportedCap.Value; + writer.ImportedCap = ImportedCap!.Value; break; case WHICH.PromisedAnswer: - PromisedAnswer?.serialize(writer.PromisedAnswer); + PromisedAnswer?.serialize(writer.PromisedAnswer!); break; } } @@ -1915,7 +1963,7 @@ namespace Capnp.Rpc public uint? ImportedCap { - get => _which == WHICH.ImportedCap ? (uint? )_content : null; + get => _which == WHICH.ImportedCap ? (uint?)_content : null; set { _which = WHICH.ImportedCap; @@ -1923,9 +1971,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.PromisedAnswer PromisedAnswer + public Capnp.Rpc.PromisedAnswer? PromisedAnswer { - get => _which == WHICH.PromisedAnswer ? (Capnp.Rpc.PromisedAnswer)_content : null; + get => _which == WHICH.PromisedAnswer ? (Capnp.Rpc.PromisedAnswer?)_content : null; set { _which = WHICH.PromisedAnswer; @@ -1968,21 +2016,24 @@ namespace Capnp.Rpc set => this.WriteData(0UL, value, 0U); } - public Capnp.Rpc.PromisedAnswer.WRITER PromisedAnswer + [DisallowNull] + public Capnp.Rpc.PromisedAnswer.WRITER? PromisedAnswer { get => which == WHICH.PromisedAnswer ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } } } + [TypeId(0x9a0e61223d96743bUL)] public class Payload : ICapnpSerializable { + public const UInt64 typeId = 0x9a0e61223d96743bUL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); - Content = CapnpSerializable.Create(reader.Content); - CapTable = reader.CapTable.ToReadOnlyList(_ => CapnpSerializable.Create(_)); + Content = CapnpSerializable.Create(reader.Content); + CapTable = reader.CapTable?.ToReadOnlyList(_ => CapnpSerializable.Create(_)!); applyDefaults(); } @@ -2001,13 +2052,13 @@ namespace Capnp.Rpc { } - public AnyPointer Content + public object? Content { get; set; } - public IReadOnlyList CapTable + public IReadOnlyList? CapTable { get; set; @@ -2049,8 +2100,10 @@ namespace Capnp.Rpc } } + [TypeId(0x8523ddc40b86b8b0UL)] public class CapDescriptor : ICapnpSerializable { + public const UInt64 typeId = 0x8523ddc40b86b8b0UL; public enum WHICH : ushort { None = 0, @@ -2091,7 +2144,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -2131,19 +2184,19 @@ namespace Capnp.Rpc case WHICH.None: break; case WHICH.SenderHosted: - writer.SenderHosted = SenderHosted.Value; + writer.SenderHosted = SenderHosted!.Value; break; case WHICH.SenderPromise: - writer.SenderPromise = SenderPromise.Value; + writer.SenderPromise = SenderPromise!.Value; break; case WHICH.ReceiverHosted: - writer.ReceiverHosted = ReceiverHosted.Value; + writer.ReceiverHosted = ReceiverHosted!.Value; break; case WHICH.ReceiverAnswer: - ReceiverAnswer?.serialize(writer.ReceiverAnswer); + ReceiverAnswer?.serialize(writer.ReceiverAnswer!); break; case WHICH.ThirdPartyHosted: - ThirdPartyHosted?.serialize(writer.ThirdPartyHosted); + ThirdPartyHosted?.serialize(writer.ThirdPartyHosted!); break; } } @@ -2159,7 +2212,7 @@ namespace Capnp.Rpc public uint? SenderHosted { - get => _which == WHICH.SenderHosted ? (uint? )_content : null; + get => _which == WHICH.SenderHosted ? (uint?)_content : null; set { _which = WHICH.SenderHosted; @@ -2169,7 +2222,7 @@ namespace Capnp.Rpc public uint? SenderPromise { - get => _which == WHICH.SenderPromise ? (uint? )_content : null; + get => _which == WHICH.SenderPromise ? (uint?)_content : null; set { _which = WHICH.SenderPromise; @@ -2179,7 +2232,7 @@ namespace Capnp.Rpc public uint? ReceiverHosted { - get => _which == WHICH.ReceiverHosted ? (uint? )_content : null; + get => _which == WHICH.ReceiverHosted ? (uint?)_content : null; set { _which = WHICH.ReceiverHosted; @@ -2187,9 +2240,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.PromisedAnswer ReceiverAnswer + public Capnp.Rpc.PromisedAnswer? ReceiverAnswer { - get => _which == WHICH.ReceiverAnswer ? (Capnp.Rpc.PromisedAnswer)_content : null; + get => _which == WHICH.ReceiverAnswer ? (Capnp.Rpc.PromisedAnswer?)_content : null; set { _which = WHICH.ReceiverAnswer; @@ -2197,9 +2250,9 @@ namespace Capnp.Rpc } } - public Capnp.Rpc.ThirdPartyCapDescriptor ThirdPartyHosted + public Capnp.Rpc.ThirdPartyCapDescriptor? ThirdPartyHosted { - get => _which == WHICH.ThirdPartyHosted ? (Capnp.Rpc.ThirdPartyCapDescriptor)_content : null; + get => _which == WHICH.ThirdPartyHosted ? (Capnp.Rpc.ThirdPartyCapDescriptor?)_content : null; set { _which = WHICH.ThirdPartyHosted; @@ -2257,27 +2310,31 @@ namespace Capnp.Rpc set => this.WriteData(32UL, value, 0U); } - public Capnp.Rpc.PromisedAnswer.WRITER ReceiverAnswer + [DisallowNull] + public Capnp.Rpc.PromisedAnswer.WRITER? ReceiverAnswer { get => which == WHICH.ReceiverAnswer ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } - public Capnp.Rpc.ThirdPartyCapDescriptor.WRITER ThirdPartyHosted + [DisallowNull] + public Capnp.Rpc.ThirdPartyCapDescriptor.WRITER? ThirdPartyHosted { get => which == WHICH.ThirdPartyHosted ? BuildPointer(0) : default; - set => Link(0, value); + set => Link(0, value!); } } } + [TypeId(0xd800b1d6cd6f1ca0UL)] public class PromisedAnswer : ICapnpSerializable { + public const UInt64 typeId = 0xd800b1d6cd6f1ca0UL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); QuestionId = reader.QuestionId; - Transform = reader.Transform.ToReadOnlyList(_ => CapnpSerializable.Create(_)); + Transform = reader.Transform?.ToReadOnlyList(_ => CapnpSerializable.Create(_)!); applyDefaults(); } @@ -2302,7 +2359,7 @@ namespace Capnp.Rpc set; } - public IReadOnlyList Transform + public IReadOnlyList? Transform { get; set; @@ -2343,8 +2400,10 @@ namespace Capnp.Rpc } } + [TypeId(0xf316944415569081UL)] public class Op : ICapnpSerializable { + public const UInt64 typeId = 0xf316944415569081UL; public enum WHICH : ushort { Noop = 0, @@ -2369,7 +2428,7 @@ namespace Capnp.Rpc } private WHICH _which = WHICH.undefined; - private object _content; + private object? _content; public WHICH which { get => _which; @@ -2397,7 +2456,7 @@ namespace Capnp.Rpc case WHICH.Noop: break; case WHICH.GetPointerField: - writer.GetPointerField = GetPointerField.Value; + writer.GetPointerField = GetPointerField!.Value; break; } } @@ -2413,7 +2472,7 @@ namespace Capnp.Rpc public ushort? GetPointerField { - get => _which == WHICH.GetPointerField ? (ushort? )_content : null; + get => _which == WHICH.GetPointerField ? (ushort?)_content : null; set { _which = WHICH.GetPointerField; @@ -2458,12 +2517,14 @@ namespace Capnp.Rpc } } + [TypeId(0xd37007fde1f0027dUL)] public class ThirdPartyCapDescriptor : ICapnpSerializable { + public const UInt64 typeId = 0xd37007fde1f0027dUL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); - Id = CapnpSerializable.Create(reader.Id); + Id = CapnpSerializable.Create(reader.Id); VineId = reader.VineId; applyDefaults(); } @@ -2483,7 +2544,7 @@ namespace Capnp.Rpc { } - public AnyPointer Id + public object? Id { get; set; @@ -2531,8 +2592,10 @@ namespace Capnp.Rpc } } + [TypeId(0xd625b7063acf691aUL)] public class Exception : ICapnpSerializable { + public const UInt64 typeId = 0xd625b7063acf691aUL; void ICapnpSerializable.Deserialize(DeserializerState arg_) { var reader = READER.create(arg_); @@ -2560,7 +2623,7 @@ namespace Capnp.Rpc { } - public string Reason + public string? Reason { get; set; @@ -2595,7 +2658,7 @@ namespace Capnp.Rpc public static READER create(DeserializerState ctx) => new READER(ctx); public static implicit operator DeserializerState(READER reader) => reader.ctx; public static implicit operator READER(DeserializerState ctx) => new READER(ctx); - public string Reason => ctx.ReadText(0, ""); + public string? Reason => ctx.ReadText(0, null); public bool ObsoleteIsCallersFault => ctx.ReadDataBool(0UL, false); public ushort ObsoleteDurability => ctx.ReadDataUShort(16UL, (ushort)0); public Capnp.Rpc.Exception.Type TheType => (Capnp.Rpc.Exception.Type)ctx.ReadDataUShort(32UL, (ushort)0); @@ -2608,10 +2671,10 @@ namespace Capnp.Rpc this.SetStruct(1, 1); } - public string Reason + public string? Reason { - get => this.ReadText(0, ""); - set => this.WriteText(0, value, ""); + get => this.ReadText(0, null); + set => this.WriteText(0, value, null); } public bool ObsoleteIsCallersFault @@ -2633,6 +2696,7 @@ namespace Capnp.Rpc } } + [TypeId(0xb28c96e23f4cbd58UL)] public enum Type : ushort { failed, @@ -2641,6 +2705,4 @@ namespace Capnp.Rpc unimplemented } } -} - -#nullable restore \ No newline at end of file +} \ No newline at end of file diff --git a/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature b/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature index 6d8d53f..7baa276 100644 --- a/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature +++ b/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature @@ -70,4 +70,5 @@ Examples: | NullableDisable2.capnp.bin | false | false | errors | | NullableDisable2.capnp.bin | false | true | success | | NullableEnable2.capnp.bin | false | false | errors | - | NullableEnable2.capnp.bin | false | true | success | \ No newline at end of file + | NullableEnable2.capnp.bin | false | true | success | + | rpc-csharp.capnp.bin | true | true | warnings | \ No newline at end of file diff --git a/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature.cs b/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature.cs index 3fa5c2c..10e1f06 100644 --- a/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature.cs +++ b/CapnpC.CSharp.Generator.Tests/CodeGenerator.feature.cs @@ -647,6 +647,21 @@ this.ValidGeneratorOutput("NullableEnable2.capnp.bin", "false", "false", "errors { #line 50 this.ValidGeneratorOutput("NullableEnable2.capnp.bin", "false", "true", "success", ((string[])(null))); +#line hidden + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Valid generator output: Variant 15")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("VariantName", "Variant 15")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("Parameter:bin", "rpc-csharp.capnp.bin")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("Parameter:nullablegen", "true")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("Parameter:nullablesupp", "true")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("Parameter:outcome", "warnings")] + public virtual void ValidGeneratorOutput_Variant15() + { +#line 50 +this.ValidGeneratorOutput("rpc-csharp.capnp.bin", "true", "true", "warnings", ((string[])(null))); #line hidden } } diff --git a/CapnpC.CSharp.Generator.Tests/Embedded Resources/rpc-csharp.capnp.bin b/CapnpC.CSharp.Generator.Tests/Embedded Resources/rpc-csharp.capnp.bin new file mode 100644 index 0000000..7209077 Binary files /dev/null and b/CapnpC.CSharp.Generator.Tests/Embedded Resources/rpc-csharp.capnp.bin differ diff --git a/CapnpC.CSharp.Generator.Tests/FeatureSteps/CodeGeneratorSteps.cs b/CapnpC.CSharp.Generator.Tests/FeatureSteps/CodeGeneratorSteps.cs index 8537a48..12b5070 100644 --- a/CapnpC.CSharp.Generator.Tests/FeatureSteps/CodeGeneratorSteps.cs +++ b/CapnpC.CSharp.Generator.Tests/FeatureSteps/CodeGeneratorSteps.cs @@ -241,14 +241,12 @@ namespace CapnpC.CSharp.Generator.Tests } } - catch (AssertFailedException) + finally { string generated = _result.GeneratedFiles.Single().GeneratedContent; string path = Path.ChangeExtension(Path.GetTempFileName(), ".capnp.cs"); File.WriteAllText(path, generated); Console.WriteLine($"Generated code was saved to {path}"); - - throw; } } } diff --git a/CapnpC.CSharp.Generator.Tests/No Resources/rpc-csharp.capnp b/CapnpC.CSharp.Generator.Tests/No Resources/rpc-csharp.capnp new file mode 100644 index 0000000..fa0f149 --- /dev/null +++ b/CapnpC.CSharp.Generator.Tests/No Resources/rpc-csharp.capnp @@ -0,0 +1,1415 @@ +# Copyright (c) 2020 Christian Köllner and contributors +# This is a modified version of rpc.capnp, found in the original distribution +# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +# Licensed under the MIT License: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +@0xb312981b2552a250; +# Recall that Cap'n Proto RPC allows messages to contain references to remote objects that +# implement interfaces. These references are called "capabilities", because they both designate +# the remote object to use and confer permission to use it. +# +# Recall also that Cap'n Proto RPC has the feature that when a method call itself returns a +# capability, the caller can begin calling methods on that capability _before the first call has +# returned_. The caller essentially sends a message saying "Hey server, as soon as you finish +# that previous call, do this with the result!". Cap'n Proto's RPC protocol makes this possible. +# +# The protocol is significantly more complicated than most RPC protocols. However, this is +# implementation complexity that underlies an easy-to-grasp higher-level model of object oriented +# programming. That is, just like TCP is a surprisingly complicated protocol that implements a +# conceptually-simple byte stream abstraction, Cap'n Proto is a surprisingly complicated protocol +# that implements a conceptually-simple object abstraction. +# +# Cap'n Proto RPC is based heavily on CapTP, the object-capability protocol used by the E +# programming language: +# http://www.erights.org/elib/distrib/captp/index.html +# +# Cap'n Proto RPC takes place between "vats". A vat hosts some set of objects and talks to other +# vats through direct bilateral connections. Typically, there is a 1:1 correspondence between vats +# and processes (in the unix sense of the word), although this is not strictly always true (one +# process could run multiple vats, or a distributed virtual vat might live across many processes). +# +# Cap'n Proto does not distinguish between "clients" and "servers" -- this is up to the application. +# Either end of any connection can potentially hold capabilities pointing to the other end, and +# can call methods on those capabilities. In the doc comments below, we use the words "sender" +# and "receiver". These refer to the sender and receiver of an instance of the struct or field +# being documented. Sometimes we refer to a "third-party" that is neither the sender nor the +# receiver. Documentation is generally written from the point of view of the sender. +# +# It is generally up to the vat network implementation to securely verify that connections are made +# to the intended vat as well as to encrypt transmitted data for privacy and integrity. See the +# `VatNetwork` example interface near the end of this file. +# +# When a new connection is formed, the only interesting things that can be done are to send a +# `Bootstrap` (level 0) or `Accept` (level 3) message. +# +# Unless otherwise specified, messages must be delivered to the receiving application in the same +# order in which they were initiated by the sending application. The goal is to support "E-Order", +# which states that two calls made on the same reference must be delivered in the order which they +# were made: +# http://erights.org/elib/concurrency/partial-order.html +# +# Since the full protocol is complicated, we define multiple levels of support that an +# implementation may target. For many applications, level 1 support will be sufficient. +# Comments in this file indicate which level requires the corresponding feature to be +# implemented. +# +# * **Level 0:** The implementation does not support object references. Only the bootstrap interface +# can be called. At this level, the implementation does not support object-oriented protocols and +# is similar in complexity to JSON-RPC or Protobuf services. This level should be considered only +# a temporary stepping-stone toward level 1 as the lack of object references drastically changes +# how protocols are designed. Applications _should not_ attempt to design their protocols around +# the limitations of level 0 implementations. +# +# * **Level 1:** The implementation supports simple bilateral interaction with object references +# and promise pipelining, but interactions between three or more parties are supported only via +# proxying of objects. E.g. if Alice (in Vat A) wants to send Bob (in Vat B) a capability +# pointing to Carol (in Vat C), Alice must create a proxy of Carol within Vat A and send Bob a +# reference to that; Bob cannot form a direct connection to Carol. Level 1 implementations do +# not support checking if two capabilities received from different vats actually point to the +# same object ("join"), although they should be able to do this check on capabilities received +# from the same vat. +# +# * **Level 2:** The implementation supports saving persistent capabilities -- i.e. capabilities +# that remain valid even after disconnect, and can be restored on a future connection. When a +# capability is saved, the requester receives a `SturdyRef`, which is a token that can be used +# to restore the capability later. +# +# * **Level 3:** The implementation supports three-way interactions. That is, if Alice (in Vat A) +# sends Bob (in Vat B) a capability pointing to Carol (in Vat C), then Vat B will automatically +# form a direct connection to Vat C rather than have requests be proxied through Vat A. +# +# * **Level 4:** The entire protocol is implemented, including joins (checking if two capabilities +# are equivalent). +# +# Note that an implementation must also support specific networks (transports), as described in +# the "Network-specific Parameters" section below. An implementation might have different levels +# depending on the network used. +# +# New implementations of Cap'n Proto should start out targeting the simplistic two-party network +# type as defined in `rpc-twoparty.capnp`. With this network type, level 3 is irrelevant and +# levels 2 and 4 are much easier than usual to implement. When such an implementation is paired +# with a container proxy, the contained app effectively gets to make full use of the proxy's +# network at level 4. And since Cap'n Proto IPC is extremely fast, it may never make sense to +# bother implementing any other vat network protocol -- just use the correct container type and get +# it for free. + +using CSharp = import "/csharp.capnp"; +$CSharp.namespace("Capnp.Rpc"); +$CSharp.nullableEnable(true); +$CSharp.emitNullableDirective(false); +$CSharp.emitDomainClassesAndInterfaces(true); +$CSharp.typeVisibility(public); + +# ======================================================================================== +# The Four Tables +# +# Cap'n Proto RPC connections are stateful (although an application built on Cap'n Proto could +# export a stateless interface). As in CapTP, for each open connection, a vat maintains four state +# tables: questions, answers, imports, and exports. See the diagram at: +# http://www.erights.org/elib/distrib/captp/4tables.html +# +# The question table corresponds to the other end's answer table, and the imports table corresponds +# to the other end's exports table. +# +# The entries in each table are identified by ID numbers (defined below as 32-bit integers). These +# numbers are always specific to the connection; a newly-established connection starts with no +# valid IDs. Since low-numbered IDs will pack better, it is suggested that IDs be assigned like +# Unix file descriptors -- prefer the lowest-number ID that is currently available. +# +# IDs in the questions/answers tables are chosen by the questioner and generally represent method +# calls that are in progress. +# +# IDs in the imports/exports tables are chosen by the exporter and generally represent objects on +# which methods may be called. Exports may be "settled", meaning the exported object is an actual +# object living in the exporter's vat, or they may be "promises", meaning the exported object is +# the as-yet-unknown result of an ongoing operation and will eventually be resolved to some other +# object once that operation completes. Calls made to a promise will be forwarded to the eventual +# target once it is known. The eventual replacement object does *not* get the same ID as the +# promise, as it may turn out to be an object that is already exported (so already has an ID) or +# may even live in a completely different vat (and so won't get an ID on the same export table +# at all). +# +# IDs can be reused over time. To make this safe, we carefully define the lifetime of IDs. Since +# messages using the ID could be traveling in both directions simultaneously, we must define the +# end of life of each ID _in each direction_. The ID is only safe to reuse once it has been +# released by both sides. +# +# When a Cap'n Proto connection is lost, everything on the four tables is lost. All questions are +# canceled and throw exceptions. All imports become broken (all future calls to them throw +# exceptions). All exports and answers are implicitly released. The only things not lost are +# persistent capabilities (`SturdyRef`s). The application must plan for this and should respond by +# establishing a new connection and restoring from these persistent capabilities. + +using QuestionId = UInt32; +# **(level 0)** +# +# Identifies a question in the sender's question table (which corresponds to the receiver's answer +# table). The questioner (caller) chooses an ID when making a call. The ID remains valid in +# caller -> callee messages until a Finish message is sent, and remains valid in callee -> caller +# messages until a Return message is sent. + +using AnswerId = QuestionId; +# **(level 0)** +# +# Identifies an answer in the sender's answer table (which corresponds to the receiver's question +# table). +# +# AnswerId is physically equivalent to QuestionId, since the question and answer tables correspond, +# but we define a separate type for documentation purposes: we always use the type representing +# the sender's point of view. + +using ExportId = UInt32; +# **(level 1)** +# +# Identifies an exported capability or promise in the sender's export table (which corresponds +# to the receiver's import table). The exporter chooses an ID before sending a capability over the +# wire. If the capability is already in the table, the exporter should reuse the same ID. If the +# ID is a promise (as opposed to a settled capability), this must be indicated at the time the ID +# is introduced (e.g. by using `senderPromise` instead of `senderHosted` in `CapDescriptor`); in +# this case, the importer shall expect a later `Resolve` message that replaces the promise. +# +# ExportId/ImportIds are subject to reference counting. Whenever an `ExportId` is sent over the +# wire (from the exporter to the importer), the export's reference count is incremented (unless +# otherwise specified). The reference count is later decremented by a `Release` message. Since +# the `Release` message can specify an arbitrary number by which to reduce the reference count, the +# importer should usually batch reference decrements and only send a `Release` when it believes the +# reference count has hit zero. Of course, it is possible that a new reference to the export is +# in-flight at the time that the `Release` message is sent, so it is necessary for the exporter to +# keep track of the reference count on its end as well to avoid race conditions. +# +# When a connection is lost, all exports are implicitly released. It is not possible to restore +# a connection state after disconnect (although a transport layer could implement a concept of +# persistent connections if it is transparent to the RPC layer). + +using ImportId = ExportId; +# **(level 1)** +# +# Identifies an imported capability or promise in the sender's import table (which corresponds to +# the receiver's export table). +# +# ImportId is physically equivalent to ExportId, since the export and import tables correspond, +# but we define a separate type for documentation purposes: we always use the type representing +# the sender's point of view. +# +# An `ImportId` remains valid in importer -> exporter messages until the importer has sent +# `Release` messages that (it believes) have reduced the reference count to zero. + +# ======================================================================================== +# Messages + +struct Message { + # An RPC connection is a bi-directional stream of Messages. + + union { + unimplemented @0 :Message; + # The sender previously received this message from the peer but didn't understand it or doesn't + # yet implement the functionality that was requested. So, the sender is echoing the message + # back. In some cases, the receiver may be able to recover from this by pretending the sender + # had taken some appropriate "null" action. + # + # For example, say `resolve` is received by a level 0 implementation (because a previous call + # or return happened to contain a promise). The level 0 implementation will echo it back as + # `unimplemented`. The original sender can then simply release the cap to which the promise + # had resolved, thus avoiding a leak. + # + # For any message type that introduces a question, if the message comes back unimplemented, + # the original sender may simply treat it as if the question failed with an exception. + # + # In cases where there is no sensible way to react to an `unimplemented` message (without + # resource leaks or other serious problems), the connection may need to be aborted. This is + # a gray area; different implementations may take different approaches. + + abort @1 :Exception; + # Sent when a connection is being aborted due to an unrecoverable error. This could be e.g. + # because the sender received an invalid or nonsensical message or because the sender had an + # internal error. The sender will shut down the outgoing half of the connection after `abort` + # and will completely close the connection shortly thereafter (it's up to the sender how much + # of a time buffer they want to offer for the client to receive the `abort` before the + # connection is reset). + + # Level 0 features ----------------------------------------------- + + bootstrap @8 :Bootstrap; # Request the peer's bootstrap interface. + call @2 :Call; # Begin a method call. + return @3 :Return; # Complete a method call. + finish @4 :Finish; # Release a returned answer / cancel a call. + + # Level 1 features ----------------------------------------------- + + resolve @5 :Resolve; # Resolve a previously-sent promise. + release @6 :Release; # Release a capability so that the remote object can be deallocated. + disembargo @13 :Disembargo; # Lift an embargo used to enforce E-order over promise resolution. + + # Level 2 features ----------------------------------------------- + + obsoleteSave @7 :AnyPointer; + # Obsolete request to save a capability, resulting in a SturdyRef. This has been replaced + # by the `Persistent` interface defined in `persistent.capnp`. This operation was never + # implemented. + + obsoleteDelete @9 :AnyPointer; + # Obsolete way to delete a SturdyRef. This operation was never implemented. + + # Level 3 features ----------------------------------------------- + + provide @10 :Provide; # Provide a capability to a third party. + accept @11 :Accept; # Accept a capability provided by a third party. + + # Level 4 features ----------------------------------------------- + + join @12 :Join; # Directly connect to the common root of two or more proxied caps. + } +} + +# Level 0 message types ---------------------------------------------- + +struct Bootstrap { + # **(level 0)** + # + # Get the "bootstrap" interface exported by the remote vat. + # + # For level 0, 1, and 2 implementations, the "bootstrap" interface is simply the main interface + # exported by a vat. If the vat acts as a server fielding connections from clients, then the + # bootstrap interface defines the basic functionality available to a client when it connects. + # The exact interface definition obviously depends on the application. + # + # We call this a "bootstrap" because in an ideal Cap'n Proto world, bootstrap interfaces would + # never be used. In such a world, any time you connect to a new vat, you do so because you + # received an introduction from some other vat (see `ThirdPartyCapId`). Thus, the first message + # you send is `Accept`, and further communications derive from there. `Bootstrap` is not used. + # + # In such an ideal world, DNS itself would support Cap'n Proto -- performing a DNS lookup would + # actually return a new Cap'n Proto capability, thus introducing you to the target system via + # level 3 RPC. Applications would receive the capability to talk to DNS in the first place as + # an initial endowment or part of a Powerbox interaction. Therefore, an app can form arbitrary + # connections without ever using `Bootstrap`. + # + # Of course, in the real world, DNS is not Cap'n-Proto-based, and we don't want Cap'n Proto to + # require a whole new internet infrastructure to be useful. Therefore, we offer bootstrap + # interfaces as a way to get up and running without a level 3 introduction. Thus, bootstrap + # interfaces are used to "bootstrap" from other, non-Cap'n-Proto-based means of service discovery, + # such as legacy DNS. + # + # Note that a vat need not provide a bootstrap interface, and in fact many vats (especially those + # acting as clients) do not. In this case, the vat should either reply to `Bootstrap` with a + # `Return` indicating an exception, or should return a dummy capability with no methods. + + questionId @0 :QuestionId; + # A new question ID identifying this request, which will eventually receive a Return message + # containing the restored capability. + + deprecatedObjectId @1 :AnyPointer; + # ** DEPRECATED ** + # + # A Vat may export multiple bootstrap interfaces. In this case, `deprecatedObjectId` specifies + # which one to return. If this pointer is null, then the default bootstrap interface is returned. + # + # As of verison 0.5, use of this field is deprecated. If a service wants to export multiple + # bootstrap interfaces, it should instead define a single bootstrap interface that has methods + # that return each of the other interfaces. + # + # **History** + # + # In the first version of Cap'n Proto RPC (0.4.x) the `Bootstrap` message was called `Restore`. + # At the time, it was thought that this would eventually serve as the way to restore SturdyRefs + # (level 2). Meanwhile, an application could offer its "main" interface on a well-known + # (non-secret) SturdyRef. + # + # Since level 2 RPC was not implemented at the time, the `Restore` message was in practice only + # used to obtain the main interface. Since most applications had only one main interface that + # they wanted to restore, they tended to designate this with a null `objectId`. + # + # Unfortunately, the earliest version of the EZ RPC interfaces set a precedent of exporting + # multiple main interfaces by allowing them to be exported under string names. In this case, + # `objectId` was a Text value specifying the name. + # + # All of this proved problematic for several reasons: + # + # - The arrangement assumed that a client wishing to restore a SturdyRef would know exactly what + # machine to connect to and would be able to immediately restore a SturdyRef on connection. + # However, in practice, the ability to restore SturdyRefs is itself a capability that may + # require going through an authentication process to obtain. Thus, it makes more sense to + # define a "restorer service" as a full Cap'n Proto interface. If this restorer interface is + # offered as the vat's bootstrap interface, then this is equivalent to the old arrangement. + # + # - Overloading "Restore" for the purpose of obtaining well-known capabilities encouraged the + # practice of exporting singleton services with string names. If singleton services are desired, + # it is better to have one main interface that has methods that can be used to obtain each + # service, in order to get all the usual benefits of schemas and type checking. + # + # - Overloading "Restore" also had a security problem: Often, "main" or "well-known" + # capabilities exported by a vat are in fact not public: they are intended to be accessed only + # by clients who are capable of forming a connection to the vat. This can lead to trouble if + # the client itself has other clients and wishes to foward some `Restore` requests from those + # external clients -- it has to be very careful not to allow through `Restore` requests + # addressing the default capability. + # + # For example, consider the case of a sandboxed Sandstorm application and its supervisor. The + # application exports a default capability to its supervisor that provides access to + # functionality that only the supervisor is supposed to access. Meanwhile, though, applications + # may publish other capabilities that may be persistent, in which case the application needs + # to field `Restore` requests that could come from anywhere. These requests of course have to + # pass through the supervisor, as all communications with the outside world must. But, the + # supervisor has to be careful not to honor an external request addressing the application's + # default capability, since this capability is privileged. Unfortunately, the default + # capability cannot be given an unguessable name, because then the supervisor itself would not + # be able to address it! + # + # As of Cap'n Proto 0.5, `Restore` has been renamed to `Bootstrap` and is no longer planned for + # use in restoring SturdyRefs. + # + # Note that 0.4 also defined a message type called `Delete` that, like `Restore`, addressed a + # SturdyRef, but indicated that the client would not restore the ref again in the future. This + # operation was never implemented, so it was removed entirely. If a "delete" operation is desired, + # it should exist as a method on the same interface that handles restoring SturdyRefs. However, + # the utility of such an operation is questionable. You wouldn't be able to rely on it for + # garbage collection since a client could always disappear permanently without remembering to + # delete all its SturdyRefs, thus leaving them dangling forever. Therefore, it is advisable to + # design systems such that SturdyRefs never represent "owned" pointers. + # + # For example, say a SturdyRef points to an image file hosted on some server. That image file + # should also live inside a collection (a gallery, perhaps) hosted on the same server, owned by + # a user who can delete the image at any time. If the user deletes the image, the SturdyRef + # stops working. On the other hand, if the SturdyRef is discarded, this has no effect on the + # existence of the image in its collection. +} + +struct Call { + # **(level 0)** + # + # Message type initiating a method call on a capability. + + questionId @0 :QuestionId; + # A number, chosen by the caller, that identifies this call in future messages. This number + # must be different from all other calls originating from the same end of the connection (but + # may overlap with question IDs originating from the opposite end). A fine strategy is to use + # sequential question IDs, but the recipient should not assume this. + # + # A question ID can be reused once both: + # - A matching Return has been received from the callee. + # - A matching Finish has been sent from the caller. + + target @1 :MessageTarget; + # The object that should receive this call. + + interfaceId @2 :UInt64; + # The type ID of the interface being called. Each capability may implement multiple interfaces. + + methodId @3 :UInt16; + # The ordinal number of the method to call within the requested interface. + + allowThirdPartyTailCall @8 :Bool = false; + # Indicates whether or not the receiver is allowed to send a `Return` containing + # `acceptFromThirdParty`. Level 3 implementations should set this true. Otherwise, the callee + # will have to proxy the return in the case of a tail call to a third-party vat. + + params @4 :Payload; + # The call parameters. `params.content` is a struct whose fields correspond to the parameters of + # the method. + + sendResultsTo :union { + # Where should the return message be sent? + + caller @5 :Void; + # Send the return message back to the caller (the usual). + + yourself @6 :Void; + # **(level 1)** + # + # Don't actually return the results to the sender. Instead, hold on to them and await + # instructions from the sender regarding what to do with them. In particular, the sender + # may subsequently send a `Return` for some other call (which the receiver had previously made + # to the sender) with `takeFromOtherQuestion` set. The results from this call are then used + # as the results of the other call. + # + # When `yourself` is used, the receiver must still send a `Return` for the call, but sets the + # field `resultsSentElsewhere` in that `Return` rather than including the results. + # + # This feature can be used to implement tail calls in which a call from Vat A to Vat B ends up + # returning the result of a call from Vat B back to Vat A. + # + # In particular, the most common use case for this feature is when Vat A makes a call to a + # promise in Vat B, and then that promise ends up resolving to a capability back in Vat A. + # Vat B must forward all the queued calls on that promise back to Vat A, but can set `yourself` + # in the calls so that the results need not pass back through Vat B. + # + # For example: + # - Alice, in Vat A, calls foo() on Bob in Vat B. + # - Alice makes a pipelined call bar() on the promise returned by foo(). + # - Later on, Bob resolves the promise from foo() to point at Carol, who lives in Vat A (next + # to Alice). + # - Vat B dutifully forwards the bar() call to Carol. Let us call this forwarded call bar'(). + # Notice that bar() and bar'() are travelling in opposite directions on the same network + # link. + # - The `Call` for bar'() has `sendResultsTo` set to `yourself`. + # - Vat B sends a `Return` for bar() with `takeFromOtherQuestion` set in place of the results, + # with the value set to the question ID of bar'(). Vat B does not wait for bar'() to return, + # as doing so would introduce unnecessary round trip latency. + # - Vat A receives bar'() and delivers it to Carol. + # - When bar'() returns, Vat A sends a `Return` for bar'() to Vat B, with `resultsSentElsewhere` + # set in place of results. + # - Vat A sends a `Finish` for the bar() call to Vat B. + # - Vat B receives the `Finish` for bar() and sends a `Finish` for bar'(). + + thirdParty @7 :RecipientId; + # **(level 3)** + # + # The call's result should be returned to a different vat. The receiver (the callee) expects + # to receive an `Accept` message from the indicated vat, and should return the call's result + # to it, rather than to the sender of the `Call`. + # + # This operates much like `yourself`, above, except that Carol is in a separate Vat C. `Call` + # messages are sent from Vat A -> Vat B and Vat B -> Vat C. A `Return` message is sent from + # Vat B -> Vat A that contains `acceptFromThirdParty` in place of results. When Vat A sends + # an `Accept` to Vat C, it receives back a `Return` containing the call's actual result. Vat C + # also sends a `Return` to Vat B with `resultsSentElsewhere`. + } +} + +struct Return { + # **(level 0)** + # + # Message type sent from callee to caller indicating that the call has completed. + + answerId @0 :AnswerId; + # Equal to the QuestionId of the corresponding `Call` message. + + releaseParamCaps @1 :Bool = true; + # If true, all capabilities that were in the params should be considered released. The sender + # must not send separate `Release` messages for them. Level 0 implementations in particular + # should always set this true. This defaults true because if level 0 implementations forget to + # set it they'll never notice (just silently leak caps), but if level >=1 implementations forget + # to set it to false they'll quickly get errors. + # + # The receiver should act as if the sender had sent a release message with count=1 for each + # CapDescriptor in the original Call message. + + union { + results @2 :Payload; + # The result. + # + # For regular method calls, `results.content` points to the result struct. + # + # For a `Return` in response to an `Accept` or `Bootstrap`, `results` contains a single + # capability (rather than a struct), and `results.content` is just a capability pointer with + # index 0. A `Finish` is still required in this case. + + exception @3 :Exception; + # Indicates that the call failed and explains why. + + canceled @4 :Void; + # Indicates that the call was canceled due to the caller sending a Finish message + # before the call had completed. + + resultsSentElsewhere @5 :Void; + # This is set when returning from a `Call` that had `sendResultsTo` set to something other + # than `caller`. + # + # It doesn't matter too much when this is sent, as the receiver doesn't need to do anything + # with it, but the C++ implementation appears to wait for the call to finish before sending + # this. + + takeFromOtherQuestion @6 :QuestionId; + # The sender has also sent (before this message) a `Call` with the given question ID and with + # `sendResultsTo.yourself` set, and the results of that other call should be used as the + # results here. `takeFromOtherQuestion` can only used once per question. + + acceptFromThirdParty @7 :ThirdPartyCapId; + # **(level 3)** + # + # The caller should contact a third-party vat to pick up the results. An `Accept` message + # sent to the vat will return the result. This pairs with `Call.sendResultsTo.thirdParty`. + # It should only be used if the corresponding `Call` had `allowThirdPartyTailCall` set. + } +} + +struct Finish { + # **(level 0)** + # + # Message type sent from the caller to the callee to indicate: + # 1) The questionId will no longer be used in any messages sent by the callee (no further + # pipelined requests). + # 2) If the call has not returned yet, the caller no longer cares about the result. If nothing + # else cares about the result either (e.g. there are no other outstanding calls pipelined on + # the result of this one) then the callee may wish to immediately cancel the operation and + # send back a Return message with "canceled" set. However, implementations are not required + # to support premature cancellation -- instead, the implementation may wait until the call + # actually completes and send a normal `Return` message. + # + # TODO(someday): Should we separate (1) and implicitly releasing result capabilities? It would be + # possible and useful to notify the server that it doesn't need to keep around the response to + # service pipeline requests even though the caller still wants to receive it / hasn't yet + # finished processing it. It could also be useful to notify the server that it need not marshal + # the results because the caller doesn't want them anyway, even if the caller is still sending + # pipelined calls, although this seems less useful (just saving some bytes on the wire). + + questionId @0 :QuestionId; + # ID of the call whose result is to be released. + + releaseResultCaps @1 :Bool = true; + # If true, all capabilities that were in the results should be considered released. The sender + # must not send separate `Release` messages for them. Level 0 implementations in particular + # should always set this true. This defaults true because if level 0 implementations forget to + # set it they'll never notice (just silently leak caps), but if level >=1 implementations forget + # set it false they'll quickly get errors. +} + +# Level 1 message types ---------------------------------------------- + +struct Resolve { + # **(level 1)** + # + # Message type sent to indicate that a previously-sent promise has now been resolved to some other + # object (possibly another promise) -- or broken, or canceled. + # + # Keep in mind that it's possible for a `Resolve` to be sent to a level 0 implementation that + # doesn't implement it. For example, a method call or return might contain a capability in the + # payload. Normally this is fine even if the receiver is level 0, because they will implicitly + # release all such capabilities on return / finish. But if the cap happens to be a promise, then + # a follow-up `Resolve` may be sent regardless of this release. The level 0 receiver will reply + # with an `unimplemented` message, and the sender (of the `Resolve`) can respond to this as if the + # receiver had immediately released any capability to which the promise resolved. + # + # When implementing promise resolution, it's important to understand how embargos work and the + # tricky case of the Tribble 4-way race condition. See the comments for the Disembargo message, + # below. + + promiseId @0 :ExportId; + # The ID of the promise to be resolved. + # + # Unlike all other instances of `ExportId` sent from the exporter, the `Resolve` message does + # _not_ increase the reference count of `promiseId`. In fact, it is expected that the receiver + # will release the export soon after receiving `Resolve`, and the sender will not send this + # `ExportId` again until it has been released and recycled. + # + # When an export ID sent over the wire (e.g. in a `CapDescriptor`) is indicated to be a promise, + # this indicates that the sender will follow up at some point with a `Resolve` message. If the + # same `promiseId` is sent again before `Resolve`, still only one `Resolve` is sent. If the + # same ID is sent again later _after_ a `Resolve`, it can only be because the export's + # reference count hit zero in the meantime and the ID was re-assigned to a new export, therefore + # this later promise does _not_ correspond to the earlier `Resolve`. + # + # If a promise ID's reference count reaches zero before a `Resolve` is sent, the `Resolve` + # message may or may not still be sent (the `Resolve` may have already been in-flight when + # `Release` was sent, but if the `Release` is received before `Resolve` then there is no longer + # any reason to send a `Resolve`). Thus a `Resolve` may be received for a promise of which + # the receiver has no knowledge, because it already released it earlier. In this case, the + # receiver should simply release the capability to which the promise resolved. + + union { + cap @1 :CapDescriptor; + # The object to which the promise resolved. + # + # The sender promises that from this point forth, until `promiseId` is released, it shall + # simply forward all messages to the capability designated by `cap`. This is true even if + # `cap` itself happens to desigate another promise, and that other promise later resolves -- + # messages sent to `promiseId` shall still go to that other promise, not to its resolution. + # This is important in the case that the receiver of the `Resolve` ends up sending a + # `Disembargo` message towards `promiseId` in order to control message ordering -- that + # `Disembargo` really needs to reflect back to exactly the object designated by `cap` even + # if that object is itself a promise. + + exception @2 :Exception; + # Indicates that the promise was broken. + } +} + +struct Release { + # **(level 1)** + # + # Message type sent to indicate that the sender is done with the given capability and the receiver + # can free resources allocated to it. + + id @0 :ImportId; + # What to release. + + referenceCount @1 :UInt32; + # The amount by which to decrement the reference count. The export is only actually released + # when the reference count reaches zero. +} + +struct Disembargo { + # **(level 1)** + # + # Message sent to indicate that an embargo on a recently-resolved promise may now be lifted. + # + # Embargos are used to enforce E-order in the presence of promise resolution. That is, if an + # application makes two calls foo() and bar() on the same capability reference, in that order, + # the calls should be delivered in the order in which they were made. But if foo() is called + # on a promise, and that promise happens to resolve before bar() is called, then the two calls + # may travel different paths over the network, and thus could arrive in the wrong order. In + # this case, the call to `bar()` must be embargoed, and a `Disembargo` message must be sent along + # the same path as `foo()` to ensure that the `Disembargo` arrives after `foo()`. Once the + # `Disembargo` arrives, `bar()` can then be delivered. + # + # There are two particular cases where embargos are important. Consider object Alice, in Vat A, + # who holds a promise P, pointing towards Vat B, that eventually resolves to Carol. The two + # cases are: + # - Carol lives in Vat A, i.e. next to Alice. In this case, Vat A needs to send a `Disembargo` + # message that echos through Vat B and back, to ensure that all pipelined calls on the promise + # have been delivered. + # - Carol lives in a different Vat C. When the promise resolves, a three-party handoff occurs + # (see `Provide` and `Accept`, which constitute level 3 of the protocol). In this case, we + # piggyback on the state that has already been set up to handle the handoff: the `Accept` + # message (from Vat A to Vat C) is embargoed, as are all pipelined messages sent to it, while + # a `Disembargo` message is sent from Vat A through Vat B to Vat C. See `Accept.embargo` for + # an example. + # + # 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. + # + # Keep in mind that promise resolution happens both in the form of Resolve messages as well as + # Return messages (which resolve PromisedAnswers). Embargos apply in both cases. + # + # An alternative strategy for enforcing E-order over promise resolution could be for Vat A to + # implement the embargo internally. When Vat A is notified of promise resolution, it could + # send a dummy no-op call to promise P and wait for it to complete. Until that call completes, + # all calls to the capability are queued locally. This strategy works, but is pessimistic: + # in the three-party case, it requires an A -> B -> C -> B -> A round trip before calls can start + # being delivered directly to from Vat A to Vat C. The `Disembargo` message allows latency to be + # reduced. (In the two-party loopback case, the `Disembargo` message is just a more explicit way + # of accomplishing the same thing as a no-op call, but isn't any faster.) + # + # *The Tribble 4-way Race Condition* + # + # Any implementation of promise resolution and embargos must be aware of what we call the + # "Tribble 4-way race condition", after Dean Tribble, who explained the problem in a lively + # Friam meeting. + # + # Embargos are designed to work in the case where a two-hop path is being shortened to one hop. + # But sometimes there are more hops. Imagine that Alice has a reference to a remote promise P1 + # that eventually resolves to _another_ remote promise P2 (in a third vat), which _at the same + # time_ happens to resolve to Bob (in a fourth vat). In this case, we're shortening from a 3-hop + # path (with four parties) to a 1-hop path (Alice -> Bob). + # + # Extending the embargo/disembargo protocol to be able to shorted multiple hops at once seems + # difficult. Instead, we make a rule that prevents this case from coming up: + # + # One a promise P has been resolved to a remote object reference R, then all further messages + # received addressed to P will be forwarded strictly to R. Even if it turns out later that R is + # itself a promise, and has resolved to some other object Q, messages sent to P will still be + # forwarded to R, not directly to Q (R will of course further forward the messages to Q). + # + # This rule does not cause a significant performance burden because once P has resolved to R, it + # is expected that people sending messages to P will shortly start sending them to R instead and + # drop P. P is at end-of-life anyway, so it doesn't matter if it ignores chances to further + # optimize its path. + + target @0 :MessageTarget; + # What is to be disembargoed. + + using EmbargoId = UInt32; + # Used in `senderLoopback` and `receiverLoopback`, below. + + context :union { + senderLoopback @1 :EmbargoId; + # The sender is requesting a disembargo on a promise that is known to resolve back to a + # capability hosted by the sender. As soon as the receiver has echoed back all pipelined calls + # on this promise, it will deliver the Disembargo back to the sender with `receiverLoopback` + # set to the same value as `senderLoopback`. This value is chosen by the sender, and since + # it is also consumed be the sender, the sender can use whatever strategy it wants to make sure + # the value is unambiguous. + # + # The receiver must verify that the target capability actually resolves back to the sender's + # vat. Otherwise, the sender has committed a protocol error and should be disconnected. + + receiverLoopback @2 :EmbargoId; + # The receiver previously sent a `senderLoopback` Disembargo towards a promise resolving to + # this capability, and that Disembargo is now being echoed back. + + accept @3 :Void; + # **(level 3)** + # + # The sender is requesting a disembargo on a promise that is known to resolve to a third-party + # capability that the sender is currently in the process of accepting (using `Accept`). + # The receiver of this `Disembargo` has an outstanding `Provide` on said capability. The + # receiver should now send a `Disembargo` with `provide` set to the question ID of that + # `Provide` message. + # + # See `Accept.embargo` for an example. + + provide @4 :QuestionId; + # **(level 3)** + # + # The sender is requesting a disembargo on a capability currently being provided to a third + # party. The question ID identifies the `Provide` message previously sent by the sender to + # this capability. On receipt, the receiver (the capability host) shall release the embargo + # on the `Accept` message that it has received from the third party. See `Accept.embargo` for + # an example. + } +} + +# Level 2 message types ---------------------------------------------- + +# See persistent.capnp. + +# Level 3 message types ---------------------------------------------- + +struct Provide { + # **(level 3)** + # + # Message type sent to indicate that the sender wishes to make a particular capability implemented + # by the receiver available to a third party for direct access (without the need for the third + # party to proxy through the sender). + # + # (In CapTP, `Provide` and `Accept` are methods of the global `NonceLocator` object exported by + # every vat. In Cap'n Proto, we bake this into the core protocol.) + + questionId @0 :QuestionId; + # Question ID to be held open until the recipient has received the capability. A result will be + # returned once the third party has successfully received the capability. The sender must at some + # point send a `Finish` message as with any other call, and that message can be used to cancel the + # whole operation. + + target @1 :MessageTarget; + # What is to be provided to the third party. + + recipient @2 :RecipientId; + # Identity of the third party that is expected to pick up the capability. +} + +struct Accept { + # **(level 3)** + # + # Message type sent to pick up a capability hosted by the receiving vat and provided by a third + # party. The third party previously designated the capability using `Provide`. + # + # This message is also used to pick up a redirected return -- see `Return.acceptFromThirdParty`. + + questionId @0 :QuestionId; + # A new question ID identifying this accept message, which will eventually receive a Return + # message containing the provided capability (or the call result in the case of a redirected + # return). + + provision @1 :ProvisionId; + # Identifies the provided object to be picked up. + + embargo @2 :Bool; + # If true, this accept shall be temporarily embargoed. The resulting `Return` will not be sent, + # and any pipelined calls will not be delivered, until the embargo is released. The receiver + # (the capability host) will expect the provider (the vat that sent the `Provide` message) to + # eventually send a `Disembargo` message with the field `context.provide` set to the question ID + # of the original `Provide` message. At that point, the embargo is released and the queued + # messages are delivered. + # + # For example: + # - Alice, in Vat A, holds a promise P, which currently points toward Vat B. + # - Alice calls foo() on P. The `Call` message is sent to Vat B. + # - The promise P in Vat B ends up resolving to Carol, in Vat C. + # - Vat B sends a `Provide` message to Vat C, identifying Vat A as the recipient. + # - Vat B sends a `Resolve` message to Vat A, indicating that the promise has resolved to a + # `ThirdPartyCapId` identifying Carol in Vat C. + # - Vat A sends an `Accept` message to Vat C to pick up the capability. Since Vat A knows that + # it has an outstanding call to the promise, it sets `embargo` to `true` in the `Accept` + # message. + # - Vat A sends a `Disembargo` message to Vat B on promise P, with `context.accept` set. + # - Alice makes a call bar() to promise P, which is now pointing towards Vat C. Alice doesn't + # know anything about the mechanics of promise resolution happening under the hood, but she + # expects that bar() will be delivered after foo() because that is the order in which she + # initiated the calls. + # - Vat A sends the bar() call to Vat C, as a pipelined call on the result of the `Accept` (which + # hasn't returned yet, due to the embargo). Since calls to the newly-accepted capability + # are embargoed, Vat C does not deliver the call yet. + # - At some point, Vat B forwards the foo() call from the beginning of this example on to Vat C. + # - Vat B forwards the `Disembargo` from Vat A on to vat C. It sets `context.provide` to the + # question ID of the `Provide` message it had sent previously. + # - Vat C receives foo() before `Disembargo`, thus allowing it to correctly deliver foo() + # before delivering bar(). + # - Vat C receives `Disembargo` from Vat B. It can now send a `Return` for the `Accept` from + # Vat A, as well as deliver bar(). +} + +# Level 4 message types ---------------------------------------------- + +struct Join { + # **(level 4)** + # + # Message type sent to implement E.join(), which, given a number of capabilities that are + # expected to be equivalent, finds the underlying object upon which they all agree and forms a + # direct connection to it, skipping any proxies that may have been constructed by other vats + # while transmitting the capability. See: + # http://erights.org/elib/equality/index.html + # + # Note that this should only serve to bypass fully-transparent proxies -- proxies that were + # created merely for convenience, without any intention of hiding the underlying object. + # + # For example, say Bob holds two capabilities hosted by Alice and Carol, but he expects that both + # are simply proxies for a capability hosted elsewhere. He then issues a join request, which + # operates as follows: + # - Bob issues Join requests on both Alice and Carol. Each request contains a different piece + # of the JoinKey. + # - Alice is proxying a capability hosted by Dana, so forwards the request to Dana's cap. + # - Dana receives the first request and sees that the JoinKeyPart is one of two. She notes that + # she doesn't have the other part yet, so she records the request and responds with a + # JoinResult. + # - Alice relays the JoinAswer back to Bob. + # - Carol is also proxying a capability from Dana, and so forwards her Join request to Dana as + # well. + # - Dana receives Carol's request and notes that she now has both parts of a JoinKey. She + # combines them in order to form information needed to form a secure connection to Bob. She + # also responds with another JoinResult. + # - Bob receives the responses from Alice and Carol. He uses the returned JoinResults to + # determine how to connect to Dana and attempts to form the connection. Since Bob and Dana now + # agree on a secret key that neither Alice nor Carol ever saw, this connection can be made + # securely even if Alice or Carol is conspiring against the other. (If Alice and Carol are + # conspiring _together_, they can obviously reproduce the key, but this doesn't matter because + # the whole point of the join is to verify that Alice and Carol agree on what capability they + # are proxying.) + # + # If the two capabilities aren't actually proxies of the same object, then the join requests + # will come back with conflicting `hostId`s and the join will fail before attempting to form any + # connection. + + questionId @0 :QuestionId; + # Question ID used to respond to this Join. (Note that this ID only identifies one part of the + # request for one hop; each part has a different ID and relayed copies of the request have + # (probably) different IDs still.) + # + # The receiver will reply with a `Return` whose `results` is a JoinResult. This `JoinResult` + # is relayed from the joined object's host, possibly with transformation applied as needed + # by the network. + # + # Like any return, the result must be released using a `Finish`. However, this release + # should not occur until the joiner has either successfully connected to the joined object. + # Vats relaying a `Join` message similarly must not release the result they receive until the + # return they relayed back towards the joiner has itself been released. This allows the + # joined object's host to detect when the Join operation is canceled before completing -- if + # it receives a `Finish` for one of the join results before the joiner successfully + # connects. It can then free any resources it had allocated as part of the join. + + target @1 :MessageTarget; + # The capability to join. + + keyPart @2 :JoinKeyPart; + # A part of the join key. These combine to form the complete join key, which is used to establish + # a direct connection. + + # TODO(before implementing): Change this so that multiple parts can be sent in a single Join + # message, so that if multiple join parts are going to cross the same connection they can be sent + # together, so that the receive can potentially optimize its handling of them. In the case where + # all parts are bundled together, should the recipient be expected to simply return a cap, so + # that the caller can immediately start pipelining to it? +} + +# ======================================================================================== +# Common structures used in messages + +struct MessageTarget { + # The target of a `Call` or other messages that target a capability. + + union { + importedCap @0 :ImportId; + # This message is to a capability or promise previously imported by the caller (exported by + # the receiver). + + promisedAnswer @1 :PromisedAnswer; + # This message is to a capability that is expected to be returned by another call that has not + # yet been completed. + # + # At level 0, this is supported only for addressing the result of a previous `Bootstrap`, so + # that initial startup doesn't require a round trip. + } +} + +struct Payload { + # Represents some data structure that might contain capabilities. + + content @0 :AnyPointer; + # Some Cap'n Proto data structure. Capability pointers embedded in this structure index into + # `capTable`. + + capTable @1 :List(CapDescriptor); + # Descriptors corresponding to the cap pointers in `content`. +} + +struct CapDescriptor { + # **(level 1)** + # + # When an application-defined type contains an interface pointer, that pointer contains an index + # into the message's capability table -- i.e. the `capTable` part of the `Payload`. Each + # capability in the table is represented as a `CapDescriptor`. The runtime API should not reveal + # the CapDescriptor directly to the application, but should instead wrap it in some kind of + # callable object with methods corresponding to the interface that the capability implements. + # + # Keep in mind that `ExportIds` in a `CapDescriptor` are subject to reference counting. See the + # description of `ExportId`. + # + # Note that it is currently not possible to include a broken capability in the CapDescriptor + # table. Instead, create a new export (`senderPromise`) for each broken capability and then + # immediately follow the payload-bearing Call or Return message with one Resolve message for each + # broken capability, resolving it to an exception. + + union { + none @0 :Void; + # There is no capability here. This `CapDescriptor` should not appear in the payload content. + # A `none` CapDescriptor can be generated when an application inserts a capability into a + # message and then later changes its mind and removes it -- rewriting all of the other + # capability pointers may be hard, so instead a tombstone is left, similar to the way a removed + # struct or list instance is zeroed out of the message but the space is not reclaimed. + # Hopefully this is unusual. + + senderHosted @1 :ExportId; + # The ID of a capability in the sender's export table (receiver's import table). It may be a + # newly allocated table entry, or an existing entry (increments the reference count). + + senderPromise @2 :ExportId; + # A promise that the sender will resolve later. The sender will send exactly one Resolve + # message at a future point in time to replace this promise. Note that even if the same + # `senderPromise` is received multiple times, only one `Resolve` is sent to cover all of + # them. If `senderPromise` is released before the `Resolve` is sent, the sender (of this + # `CapDescriptor`) may choose not to send the `Resolve` at all. + + receiverHosted @3 :ImportId; + # A capability (or promise) previously exported by the receiver (imported by the sender). + + receiverAnswer @4 :PromisedAnswer; + # A capability expected to be returned in the results of a currently-outstanding call posed + # by the sender. + + thirdPartyHosted @5 :ThirdPartyCapDescriptor; + # **(level 3)** + # + # A capability that lives in neither the sender's nor the receiver's vat. The sender needs + # to form a direct connection to a third party to pick up the capability. + # + # Level 1 and 2 implementations that receive a `thirdPartyHosted` may simply send calls to its + # `vine` instead. + } +} + +struct PromisedAnswer { + # **(mostly level 1)** + # + # Specifies how to derive a promise from an unanswered question, by specifying the path of fields + # to follow from the root of the eventual result struct to get to the desired capability. Used + # to address method calls to a not-yet-returned capability or to pass such a capability as an + # input to some other method call. + # + # Level 0 implementations must support `PromisedAnswer` only for the case where the answer is + # to a `Bootstrap` message. In this case, `path` is always empty since `Bootstrap` always returns + # a raw capability. + + questionId @0 :QuestionId; + # ID of the question (in the sender's question table / receiver's answer table) whose answer is + # expected to contain the capability. + + transform @1 :List(Op); + # Operations / transformations to apply to the result in order to get the capability actually + # being addressed. E.g. if the result is a struct and you want to call a method on a capability + # pointed to by a field of the struct, you need a `getPointerField` op. + + struct Op { + union { + noop @0 :Void; + # Does nothing. This member is mostly defined so that we can make `Op` a union even + # though (as of this writing) only one real operation is defined. + + getPointerField @1 :UInt16; + # Get a pointer field within a struct. The number is an index into the pointer section, NOT + # a field ordinal, so that the receiver does not need to understand the schema. + + # TODO(someday): We could add: + # - For lists, the ability to address every member of the list, or a slice of the list, the + # result of which would be another list. This is useful for implementing the equivalent of + # a SQL table join (not to be confused with the `Join` message type). + # - Maybe some ability to test a union. + # - Probably not a good idea: the ability to specify an arbitrary script to run on the + # result. We could define a little stack-based language where `Op` specifies one + # "instruction" or transformation to apply. Although this is not a good idea + # (over-engineered), any narrower additions to `Op` should be designed as if this + # were the eventual goal. + } + } +} + +struct ThirdPartyCapDescriptor { + # **(level 3)** + # + # Identifies a capability in a third-party vat that the sender wants the receiver to pick up. + + id @0 :ThirdPartyCapId; + # Identifies the third-party host and the specific capability to accept from it. + + vineId @1 :ExportId; + # A proxy for the third-party object exported by the sender. In CapTP terminology this is called + # a "vine", because it is an indirect reference to the third-party object that snakes through the + # sender vat. This serves two purposes: + # + # * Level 1 and 2 implementations that don't understand how to connect to a third party may + # simply send calls to the vine. Such calls will be forwarded to the third-party by the + # sender. + # + # * Level 3 implementations must release the vine only once they have successfully picked up the + # object from the third party. This ensures that the capability is not released by the sender + # prematurely. + # + # The sender will close the `Provide` request that it has sent to the third party as soon as + # it receives either a `Call` or a `Release` message directed at the vine. +} + +struct Exception { + # **(level 0)** + # + # Describes an arbitrary error that prevented an operation (e.g. a call) from completing. + # + # Cap'n Proto exceptions always indicate that something went wrong. In other words, in a fantasy + # world where everything always works as expected, no exceptions would ever be thrown. Clients + # should only ever catch exceptions as a means to implement fault-tolerance, where "fault" can + # mean: + # - Bugs. + # - Invalid input. + # - Configuration errors. + # - Network problems. + # - Insufficient resources. + # - Version skew (unimplemented functionality). + # - Other logistical problems. + # + # Exceptions should NOT be used to flag application-specific conditions that a client is expected + # to handle in an application-specific way. Put another way, in the Cap'n Proto world, + # "checked exceptions" (where an interface explicitly defines the exceptions it throws and + # clients are forced by the type system to handle those exceptions) do NOT make sense. + + reason @0 :Text; + # Human-readable failure description. + + type @3 :Type; + # The type of the error. The purpose of this enum is not to describe the error itself, but + # rather to describe how the client might want to respond to the error. + + enum Type { + failed @0; + # A generic problem occurred, and it is believed that if the operation were repeated without + # any change in the state of the world, the problem would occur again. + # + # A client might respond to this error by logging it for investigation by the developer and/or + # displaying it to the user. + + overloaded @1; + # The request was rejected due to a temporary lack of resources. + # + # Examples include: + # - There's not enough CPU time to keep up with incoming requests, so some are rejected. + # - The server ran out of RAM or disk space during the request. + # - The operation timed out (took significantly longer than it should have). + # + # A client might respond to this error by scheduling to retry the operation much later. The + # client should NOT retry again immediately since this would likely exacerbate the problem. + + disconnected @2; + # The method failed because a connection to some necessary capability was lost. + # + # Examples include: + # - The client introduced the server to a third-party capability, the connection to that third + # party was subsequently lost, and then the client requested that the server use the dead + # capability for something. + # - The client previously requested that the server obtain a capability from some third party. + # The server returned a capability to an object wrapping the third-party capability. Later, + # the server's connection to the third party was lost. + # - The capability has been revoked. Revocation does not necessarily mean that the client is + # no longer authorized to use the capability; it is often used simply as a way to force the + # client to repeat the setup process, perhaps to efficiently move them to a new back-end or + # get them to recognize some other change that has occurred. + # + # A client should normally respond to this error by releasing all capabilities it is currently + # holding related to the one it called and then re-creating them by restoring SturdyRefs and/or + # repeating the method calls used to create them originally. In other words, disconnect and + # start over. This should in turn cause the server to obtain a new copy of the capability that + # it lost, thus making everything work. + # + # If the client receives another `disconnencted` error in the process of rebuilding the + # capability and retrying the call, it should treat this as an `overloaded` error: the network + # is currently unreliable, possibly due to load or other temporary issues. + + unimplemented @3; + # The server doesn't implement the requested method. If there is some other method that the + # client could call (perhaps an older and/or slower interface), it should try that instead. + # Otherwise, this should be treated like `failed`. + } + + obsoleteIsCallersFault @1 :Bool; + # OBSOLETE. Ignore. + + obsoleteDurability @2 :UInt16; + # OBSOLETE. See `type` instead. +} + +# ======================================================================================== +# Network-specific Parameters +# +# Some parts of the Cap'n Proto RPC protocol are not specified here because different vat networks +# may wish to use different approaches to solving them. For example, on the public internet, you +# may want to authenticate vats using public-key cryptography, but on a local intranet with trusted +# infrastructure, you may be happy to authenticate based on network address only, or some other +# lightweight mechanism. +# +# To accommodate this, we specify several "parameter" types. Each type is defined here as an +# alias for `AnyPointer`, but a specific network will want to define a specific set of types to use. +# All vats in a vat network must agree on these parameters in order to be able to communicate. +# Inter-network communication can be accomplished through "gateways" that perform translation +# between the primitives used on each network; these gateways may need to be deeply stateful, +# depending on the translations they perform. +# +# For interaction over the global internet between parties with no other prior arrangement, a +# particular set of bindings for these types is defined elsewhere. (TODO(someday): Specify where +# these common definitions live.) +# +# Another common network type is the two-party network, in which one of the parties typically +# interacts with the outside world entirely through the other party. In such a connection between +# Alice and Bob, all objects that exist on Bob's other networks appear to Alice as if they were +# hosted by Bob himself, and similarly all objects on Alice's network (if she even has one) appear +# to Bob as if they were hosted by Alice. This network type is interesting because from the point +# of view of a simple application that communicates with only one other party via the two-party +# protocol, there are no three-party interactions at all, and joins are unusually simple to +# implement, so implementing at level 4 is barely more complicated than implementing at level 1. +# Moreover, if you pair an app implementing the two-party network with a container that implements +# some other network, the app can then participate on the container's network just as if it +# implemented that network directly. The types used by the two-party network are defined in +# `rpc-twoparty.capnp`. +# +# The things that we need to parameterize are: +# - How to store capabilities long-term without holding a connection open (mostly level 2). +# - How to authenticate vats in three-party introductions (level 3). +# - How to implement `Join` (level 4). +# +# Persistent references +# --------------------- +# +# **(mostly level 2)** +# +# We want to allow some capabilities to be stored long-term, even if a connection is lost and later +# recreated. ExportId is a short-term identifier that is specific to a connection, so it doesn't +# help here. We need a way to specify long-term identifiers, as well as a strategy for +# reconnecting to a referenced capability later. +# +# Three-party interactions +# ------------------------ +# +# **(level 3)** +# +# In cases where more than two vats are interacting, we have situations where VatA holds a +# capability hosted by VatB and wants to send that capability to VatC. This can be accomplished +# by VatA proxying requests on the new capability, but doing so has two big problems: +# - It's inefficient, requiring an extra network hop. +# - If VatC receives another capability to the same object from VatD, it is difficult for VatC to +# detect that the two capabilities are really the same and to implement the E "join" operation, +# which is necessary for certain four-or-more-party interactions, such as the escrow pattern. +# See: http://www.erights.org/elib/equality/grant-matcher/index.html +# +# Instead, we want a way for VatC to form a direct, authenticated connection to VatB. +# +# Join +# ---- +# +# **(level 4)** +# +# The `Join` message type and corresponding operation arranges for a direct connection to be formed +# between the joiner and the host of the joined object, and this connection must be authenticated. +# Thus, the details are network-dependent. + +using SturdyRef = AnyPointer; +# **(level 2)** +# +# Identifies a persisted capability that can be restored in the future. How exactly a SturdyRef +# is restored to a live object is specified along with the SturdyRef definition (i.e. not by +# rpc.capnp). +# +# Generally a SturdyRef needs to specify three things: +# - How to reach the vat that can restore the ref (e.g. a hostname or IP address). +# - How to authenticate the vat after connecting (e.g. a public key fingerprint). +# - The identity of a specific object hosted by the vat. Generally, this is an opaque pointer whose +# format is defined by the specific vat -- the client has no need to inspect the object ID. +# It is important that the objec ID be unguessable if the object is not public (and objects +# should almost never be public). +# +# The above are only suggestions. Some networks might work differently. For example, a private +# network might employ a special restorer service whose sole purpose is to restore SturdyRefs. +# In this case, the entire contents of SturdyRef might be opaque, because they are intended only +# to be forwarded to the restorer service. + +using ProvisionId = AnyPointer; +# **(level 3)** +# +# The information that must be sent in an `Accept` message to identify the object being accepted. +# +# In a network where each vat has a public/private key pair, this could simply be the public key +# fingerprint of the provider vat along with a nonce matching the one in the `RecipientId` used +# in the `Provide` message sent from that provider. + +using RecipientId = AnyPointer; +# **(level 3)** +# +# The information that must be sent in a `Provide` message to identify the recipient of the +# capability. +# +# In a network where each vat has a public/private key pair, this could simply be the public key +# fingerprint of the recipient along with a nonce matching the one in the `ProvisionId`. + +using ThirdPartyCapId = AnyPointer; +# **(level 3)** +# +# The information needed to connect to a third party and accept a capability from it. +# +# In a network where each vat has a public/private key pair, this could be a combination of the +# third party's public key fingerprint, hints on how to connect to the third party (e.g. an IP +# address), and the nonce used in the corresponding `Provide` message's `RecipientId` as sent +# to that third party (used to identify which capability to pick up). + +using JoinKeyPart = AnyPointer; +# **(level 4)** +# +# A piece of a secret key. One piece is sent along each path that is expected to lead to the same +# place. Once the pieces are combined, a direct connection may be formed between the sender and +# the receiver, bypassing any men-in-the-middle along the paths. See the `Join` message type. +# +# The motivation for Joins is discussed under "Supporting Equality" in the "Unibus" protocol +# sketch: http://www.erights.org/elib/distrib/captp/unibus.html +# +# In a network where each vat has a public/private key pair and each vat forms no more than one +# connection to each other vat, Joins will rarely -- perhaps never -- be needed, as objects never +# need to be transparently proxied and references to the same object sent over the same connection +# have the same export ID. Thus, a successful join requires only checking that the two objects +# come from the same connection and have the same ID, and then completes immediately. +# +# However, in networks where two vats may form more than one connection between each other, or +# where proxying of objects occurs, joins are necessary. +# +# Typically, each JoinKeyPart would include a fixed-length data value such that all value parts +# XOR'd together forms a shared secret that can be used to form an encrypted connection between +# the joiner and the joined object's host. Each JoinKeyPart should also include an indication of +# how many parts to expect and a hash of the shared secret (used to match up parts). + +using JoinResult = AnyPointer; +# **(level 4)** +# +# Information returned as the result to a `Join` message, needed by the joiner in order to form a +# direct connection to a joined object. This might simply be the address of the joined object's +# host vat, since the `JoinKey` has already been communicated so the two vats already have a shared +# secret to use to authenticate each other. +# +# The `JoinResult` should also contain information that can be used to detect when the Join +# requests ended up reaching different objects, so that this situation can be detected easily. +# This could be a simple matter of including a sequence number -- if the joiner receives two +# `JoinResult`s with sequence number 0, then they must have come from different objects and the +# whole join is a failure. + +# ======================================================================================== +# Network interface sketch +# +# The interfaces below are meant to be pseudo-code to illustrate how the details of a particular +# vat network might be abstracted away. They are written like Cap'n Proto interfaces, but in +# practice you'd probably define these interfaces manually in the target programming language. A +# Cap'n Proto RPC implementation should be able to use these interfaces without knowing the +# definitions of the various network-specific parameters defined above. + +# interface VatNetwork { +# # Represents a vat network, with the ability to connect to particular vats and receive +# # connections from vats. +# # +# # Note that methods returning a `Connection` may return a pre-existing `Connection`, and the +# # caller is expected to find and share state with existing users of the connection. +# +# # Level 0 features ----------------------------------------------- +# +# connect(vatId :VatId) :Connection; +# # Connect to the given vat. The transport should return a promise that does not +# # resolve until authentication has completed, but allows messages to be pipelined in before +# # that; the transport either queues these messages until authenticated, or sends them encrypted +# # such that only the authentic vat would be able to decrypt them. The latter approach avoids a +# # round trip for authentication. +# +# accept() :Connection; +# # Wait for the next incoming connection and return it. Only connections formed by +# # connect() are returned by this method. +# +# # Level 4 features ----------------------------------------------- +# +# newJoiner(count :UInt32) :NewJoinerResponse; +# # Prepare a new Join operation, which will eventually lead to forming a new direct connection +# # to the host of the joined capability. `count` is the number of capabilities to join. +# +# struct NewJoinerResponse { +# joinKeyParts :List(JoinKeyPart); +# # Key parts to send in Join messages to each capability. +# +# joiner :Joiner; +# # Used to establish the final connection. +# } +# +# interface Joiner { +# addJoinResult(result :JoinResult) :Void; +# # Add a JoinResult received in response to one of the `Join` messages. All `JoinResult`s +# # returned from all paths must be added before trying to connect. +# +# connect() :ConnectionAndProvisionId; +# # Try to form a connection to the joined capability's host, verifying that it has received +# # all of the JoinKeyParts. Once the connection is formed, the caller should send an `Accept` +# # message on it with the specified `ProvisionId` in order to receive the final capability. +# } +# +# acceptConnectionFromJoiner(parts :List(JoinKeyPart), paths :List(VatPath)) +# :ConnectionAndProvisionId; +# # Called on a joined capability's host to receive the connection from the joiner, once all +# # key parts have arrived. The caller should expect to receive an `Accept` message over the +# # connection with the given ProvisionId. +# } +# +# interface Connection { +# # Level 0 features ----------------------------------------------- +# +# send(message :Message) :Void; +# # Send the message. Returns successfully when the message (and all preceding messages) has +# # been acknowledged by the recipient. +# +# receive() :Message; +# # Receive the next message, and acknowledges receipt to the sender. Messages are received in +# # the order in which they are sent. +# +# # Level 3 features ----------------------------------------------- +# +# introduceTo(recipient :Connection) :IntroductionInfo; +# # Call before starting a three-way introduction, assuming a `Provide` message is to be sent on +# # this connection and a `ThirdPartyCapId` is to be sent to `recipient`. +# +# struct IntroductionInfo { +# sendToRecipient :ThirdPartyCapId; +# sendToTarget :RecipientId; +# } +# +# connectToIntroduced(capId :ThirdPartyCapId) :ConnectionAndProvisionId; +# # Given a ThirdPartyCapId received over this connection, connect to the third party. The +# # caller should then send an `Accept` message over the new connection. +# +# acceptIntroducedConnection(recipientId :RecipientId) :Connection; +# # Given a RecipientId received in a `Provide` message on this `Connection`, wait for the +# # recipient to connect, and return the connection formed. Usually, the first message received +# # on the new connection will be an `Accept` message. +# } +# +# struct ConnectionAndProvisionId { +# # **(level 3)** +# +# connection :Connection; +# # Connection on which to issue `Accept` message. +# +# provision :ProvisionId; +# # `ProvisionId` to send in the `Accept` message. +# } diff --git a/CapnpC.CSharp.Generator/CodeGen/CodeGenerator.cs b/CapnpC.CSharp.Generator/CodeGen/CodeGenerator.cs index 3b8f809..a0ec9fc 100644 --- a/CapnpC.CSharp.Generator/CodeGen/CodeGenerator.cs +++ b/CapnpC.CSharp.Generator/CodeGen/CodeGenerator.cs @@ -61,9 +61,18 @@ IEnumerable TransformStruct(TypeDefinition def) { - var topDecl = ClassDeclaration(_names.MakeTypeName(def).Identifier) - .AddModifiers(Public) - .AddBaseListTypes(SimpleBaseType(_names.Type(Nullability.NonNullable))); + var topDecl = ClassDeclaration(_names.MakeTypeName(def).Identifier) + .AddModifiers(_names.TypeVisibilityModifier); + + if (_names.EmitDomainClassesAndInterfaces) + { + topDecl = topDecl.AddBaseListTypes(SimpleBaseType(_names.Type(Nullability.NonNullable))); + } + else + { + topDecl = topDecl.AddModifiers(Static); + } + if (def.GenericParameters.Count > 0) { @@ -80,9 +89,14 @@ topDecl = topDecl.AddMembers(_commonGen.MakeUnionSelectorEnum(def)); } - topDecl = topDecl.AddMembers(_domClassGen.MakeDomainClassMembers(def)); - topDecl = topDecl.AddMembers(_readerGen.MakeReaderStruct(def)); - topDecl = topDecl.AddMembers(_writerGen.MakeWriterStruct(def)); + if (_names.EmitDomainClassesAndInterfaces) + { + topDecl = topDecl.AddMembers(_domClassGen.MakeDomainClassMembers(def)); + } + + topDecl = topDecl.AddMembers( + _readerGen.MakeReaderStruct(def), + _writerGen.MakeWriterStruct(def)); foreach (var nestedGroup in def.NestedGroups) { @@ -99,6 +113,9 @@ IEnumerable TransformInterface(TypeDefinition def) { + if (!_names.EmitDomainClassesAndInterfaces) + yield break; + yield return _interfaceGen.MakeInterface(def); yield return _interfaceGen.MakeProxy(def); yield return _interfaceGen.MakeSkeleton(def); @@ -168,7 +185,7 @@ if (classDecl == null) { classDecl = ClassDeclaration(_names.MakePipeliningSupportExtensionClassName(file).Identifier) - .AddModifiers(Public, Static, Partial); + .AddModifiers(_names.TypeVisibilityModifier, Static, Partial); } classDecl = classDecl.AddMembers(members); @@ -187,6 +204,8 @@ internal string Transform(GenFile file) { _names.NullableEnable = file.NullableEnable ?? _options.NullableEnableDefault; + _names.EmitDomainClassesAndInterfaces = file.EmitDomainClassesAndInterfaces; + _names.TypeVisibility = file.TypeVisibility; NameSyntax topNamespace = GenNames.NamespaceName(file.Namespace) ?? _names.TopNamespace; @@ -211,11 +230,14 @@ ns = ns.AddMembers(Transform(def).ToArray()); } - var psc = TransformForPipeliningSupport(file); - - if (psc != null) + if (_names.EmitDomainClassesAndInterfaces) { - ns = ns.AddMembers(psc); + var psc = TransformForPipeliningSupport(file); + + if (psc != null) + { + ns = ns.AddMembers(psc); + } } var cu = CompilationUnit().AddUsings( diff --git a/CapnpC.CSharp.Generator/CodeGen/CommonSnippetGen.cs b/CapnpC.CSharp.Generator/CodeGen/CommonSnippetGen.cs index 72de969..549a272 100644 --- a/CapnpC.CSharp.Generator/CodeGen/CommonSnippetGen.cs +++ b/CapnpC.CSharp.Generator/CodeGen/CommonSnippetGen.cs @@ -51,7 +51,7 @@ namespace CapnpC.CSharp.Generator.CodeGen { var decl = EnumDeclaration(_names.GetCodeIdentifier(def)) .WithAttributeLists(MakeTypeIdAttributeLists(def.Id)) - .AddModifiers(Public) + .AddModifiers(_names.TypeVisibilityModifier) .AddBaseListTypes(SimpleBaseType(_names.Type(Nullability.NonNullable))); foreach (var enumerant in def.Enumerants.OrderBy(e => e.CodeOrder)) diff --git a/CapnpC.CSharp.Generator/CodeGen/GenNames.cs b/CapnpC.CSharp.Generator/CodeGen/GenNames.cs index 95f1889..688f49a 100644 --- a/CapnpC.CSharp.Generator/CodeGen/GenNames.cs +++ b/CapnpC.CSharp.Generator/CodeGen/GenNames.cs @@ -1,4 +1,5 @@ using CapnpC.CSharp.Generator.Model; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; @@ -74,6 +75,8 @@ namespace CapnpC.CSharp.Generator.CodeGen public string SkeletonClassFormat { get; } public Name AwaitProxy { get; } public bool NullableEnable { get; set; } + public bool EmitDomainClassesAndInterfaces { get; set; } + public SupportedAnnotations.TypeVisibility TypeVisibility { get; set; } public GenNames(GeneratorOptions options) { TopNamespace = new Name(options.TopNamespaceName).IdentifierName; @@ -708,5 +711,23 @@ namespace CapnpC.CSharp.Generator.CodeGen PostfixUnaryExpression(SyntaxKind.SuppressNullableWarningExpression, expression) : expression; } + + public SyntaxToken TypeVisibilityModifier + { + get + { + switch (TypeVisibility) + { + case SupportedAnnotations.TypeVisibility.Public: + return Token(SyntaxKind.PublicKeyword); + + case SupportedAnnotations.TypeVisibility.Internal: + return Token(SyntaxKind.InternalKeyword); + + default: + throw new NotImplementedException(); + } + } + } } } diff --git a/CapnpC.CSharp.Generator/Model/GenFile.cs b/CapnpC.CSharp.Generator/Model/GenFile.cs index 098c8a6..ac0c35d 100644 --- a/CapnpC.CSharp.Generator/Model/GenFile.cs +++ b/CapnpC.CSharp.Generator/Model/GenFile.cs @@ -13,6 +13,8 @@ namespace CapnpC.CSharp.Generator.Model public string[] Namespace { get; set; } public bool? NullableEnable { get; set; } public bool EmitNullableDirective { get; set; } + public bool EmitDomainClassesAndInterfaces { get; set; } + public SupportedAnnotations.TypeVisibility TypeVisibility { get; set; } public IEnumerable NestedTypes { get => this.GetNestedTypes(); } public ICollection NestedDefinitions { get; } = new List(); diff --git a/CapnpC.CSharp.Generator/Model/SchemaModel.cs b/CapnpC.CSharp.Generator/Model/SchemaModel.cs index 8c0e9d6..8213ffd 100644 --- a/CapnpC.CSharp.Generator/Model/SchemaModel.cs +++ b/CapnpC.CSharp.Generator/Model/SchemaModel.cs @@ -94,6 +94,8 @@ namespace CapnpC.CSharp.Generator.Model file.Name = name; file.NullableEnable = GetNullableEnable(node); file.EmitNullableDirective = GetEmitNullableDirective(node) ?? false; + file.EmitDomainClassesAndInterfaces = GetEmitDomainClassesAndInterfaces(node) ?? true; + file.TypeVisibility = GetTypeVisibility(node) ?? TypeVisibility.Public; return ProcessNodePass1(id, name, state) as GenFile; } diff --git a/CapnpC.CSharp.Generator/Model/SupportedAnnotations.cs b/CapnpC.CSharp.Generator/Model/SupportedAnnotations.cs index 5951f16..cd78387 100644 --- a/CapnpC.CSharp.Generator/Model/SupportedAnnotations.cs +++ b/CapnpC.CSharp.Generator/Model/SupportedAnnotations.cs @@ -19,9 +19,17 @@ namespace CapnpC.CSharp.Generator.Model public const ulong NullableEnable = 0xeb0d831668c6eda1; public const ulong Name = 0xeb0d831668c6eda2; public const ulong EmitNullableDirective = 0xeb0d831668c6eda3; + public const ulong EmitDomainClassesAndInterfaces = 0xeb0d831668c6eda4; + public const ulong TypeVisibility = 0xeb0d831668c6eda6; } } + public enum TypeVisibility + { + Public = 0, + Internal = 1 + } + public static string[] GetNamespaceAnnotation(Schema.Node.Reader fileNode) { foreach (var annotation in fileNode.Annotations) @@ -98,5 +106,29 @@ namespace CapnpC.CSharp.Generator.Model } return null; } + + public static bool? GetEmitDomainClassesAndInterfaces(Schema.Node.Reader node) + { + foreach (var annotation in node.Annotations) + { + if (annotation.Id == AnnotationIds.Cs.EmitDomainClassesAndInterfaces && annotation.Value.IsBool) + { + return annotation.Value.Bool; + } + } + return null; + } + + public static TypeVisibility? GetTypeVisibility(Schema.Node.Reader node) + { + foreach (var annotation in node.Annotations) + { + if (annotation.Id == AnnotationIds.Cs.TypeVisibility && annotation.Value.IsEnum) + { + return (TypeVisibility)annotation.Value.Enum; + } + } + return null; + } } } diff --git a/include/csharp.capnp b/include/csharp.capnp index 19aabd2..879d533 100644 --- a/include/csharp.capnp +++ b/include/csharp.capnp @@ -1,6 +1,11 @@ @0xeb0d831668c6edab; $namespace("Capnp.Annotations"); +enum TypeVisibility @0xeb0d831668c6eda5 { + public @0; + internal @1; +} + annotation namespace @0xeb0d831668c6eda0 (file) : Text; # C# namespace for code generation @@ -8,7 +13,13 @@ annotation nullableEnable @0xeb0d831668c6eda1 (file) : Bool; # Whether to generate C# nullable reference types annotation emitNullableDirective @0xeb0d831668c6eda3 (file) : Bool; -# Whether to surround the generated with with #nullable enable/disable ... #nullable restore +# Whether to surround the generated code with #nullable enable/disable ... #nullable restore + +annotation emitDomainClassesAndInterfaces @0xeb0d831668c6eda4 (file) : Bool; +# Whether generate domain classes and interfaces (default is 'true' if annotation is missing) + +annotation typeVisibility @0xeb0d831668c6eda6 (file) : TypeVisibility; +# Visibility of generated types annotation name @0xeb0d831668c6eda2 (field, enumerant, struct, enum, interface, method, param, group, union) : Text; # C# member name for code generation diff --git a/scripts/measure-coverage.ps1 b/scripts/measure-coverage.ps1 index a438829..c16ed26 100644 --- a/scripts/measure-coverage.ps1 +++ b/scripts/measure-coverage.ps1 @@ -4,12 +4,10 @@ $coverageDir = "$rootDir\coverage" $coverageReportDir = "$rootDir\coverage\report" $openCover = "$env:LOCALAPPDATA\Apps\OpenCover\OpenCover.Console.exe" $vsTestConsole = where.exe vstest.console +$coverageOutput = "$coverageDir\coverage.xml" $runtimeTests = "$rootDir\Capnp.Net.Runtime.Tests\bin\Release\netcoreapp2.1\Capnp.Net.Runtime.Tests.dll" -$coverageOutputRuntime = "$coverageDir\cov-Capnp.Net.Runtime.xml" - $generatorTests = "$rootDir\CapnpC.CSharp.Generator.Tests\bin\Release\netcoreapp3.0\CapnpC.CSharp.Generator.Tests.dll" -$coverageOutputGenerator = "$coverageDir\cov-CapnpC.CSharp.Generator.xml" If(!(test-path $coverageDir)) { @@ -25,14 +23,15 @@ If(!(test-path $coverageReportDir)) -targetArgs:"/inIsolation $runtimeTests /TestCaseFilter:`"TestCategory=Coverage`"" ` -filter:"+[Capnp.Net.Runtime]Capnp.*" ` -excludebyattribute:"System.CodeDom.Compiler.GeneratedCodeAttribute" ` - -output:"$coverageOutputRuntime" ` + -output:"$coverageOutput" ` -mergebyhash -register:user -oldStyle & $openCover -target:"$vsTestConsole" ` -targetArgs:"/inIsolation $generatorTests" ` -filter:"+[CapnpC.CSharp.Generator]CapnpC.CSharp.Generator.* -[CapnpC.CSharp.Generator]CapnpC.CSharp.Generator.Schema.*" ` -excludebyattribute:"System.CodeDom.Compiler.GeneratedCodeAttribute" ` - -output:"$coverageOutputGenerator" ` + -output:"$coverageOutput" ` + -mergeoutput ` -mergebyhash -register:user -oldStyle -ReportGenerator.exe -reports:"$coverageOutputRuntime;$coverageOutputGenerator" -targetdir:"$coverageReportDir" -reportTypes:"Html;Xml" +ReportGenerator.exe -reports:"$coverageOutput" -targetdir:"$coverageReportDir" -reportTypes:"Html"