more fixes, more coverage

This commit is contained in:
Christian Köllner 2020-03-31 22:01:43 +02:00
parent 2369b4788a
commit 20c8523aae
15 changed files with 279 additions and 102 deletions

View File

@ -378,6 +378,10 @@ namespace Capnp.Net.Runtime.Tests
var mb = MessageBuilder.Create(); var mb = MessageBuilder.Create();
var root = mb.BuildRoot<TW>(); var root = mb.BuildRoot<TW>();
obj.Serialize(root); obj.Serialize(root);
using (var tr = new FrameTracing.RpcFrameTracer(Console.Out))
{
tr.TraceFrame(FrameTracing.FrameDirection.Tx, mb.Frame);
}
var d = (DeserializerState)root; var d = (DeserializerState)root;
var obj2 = new TD(); var obj2 = new TD();
obj2.Deserialize(d); obj2.Deserialize(d);

View File

@ -1,4 +1,5 @@
using Capnp.Net.Runtime.Tests.GenImpls; using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Rpc;
using Capnproto_test.Capnp.Test; using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; using System;
@ -229,7 +230,7 @@ namespace Capnp.Net.Runtime.Tests
} }
[TestMethod] [TestMethod]
public void ListOfStructs() public void ListOfStructs1()
{ {
var b = MessageBuilder.Create(); var b = MessageBuilder.Create();
var list = b.CreateObject<ListOfStructsSerializer<SomeStruct.WRITER>>(); var list = b.CreateObject<ListOfStructsSerializer<SomeStruct.WRITER>>();
@ -259,6 +260,19 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual("3", list3[3].SomeText); Assert.AreEqual("3", list3[3].SomeText);
} }
[TestMethod]
public void ListOfStructs2()
{
var b = MessageBuilder.Create();
var list = b.CreateObject<DynamicSerializerState>();
list.SetListOfStructs(3, 4, 5);
list.SetListOfStructs(3, 4, 5);
Assert.ThrowsException<InvalidOperationException>(() => list.SetListOfStructs(1, 4, 5));
Assert.ThrowsException<InvalidOperationException>(() => list.SetListOfStructs(3, 1, 5));
Assert.ThrowsException<InvalidOperationException>(() => list.SetListOfStructs(3, 4, 1));
Assert.ThrowsException<InvalidOperationException>(() => list.StructWriteData(0, 1, 1));
}
[TestMethod] [TestMethod]
public void ListOfText() public void ListOfText()
{ {
@ -741,5 +755,171 @@ namespace Capnp.Net.Runtime.Tests
Assert.ThrowsException<IndexOutOfRangeException>(() => { var _ = list[0]; }); Assert.ThrowsException<IndexOutOfRangeException>(() => { var _ = list[0]; });
Assert.AreEqual(0, list.ToArray().Length); Assert.AreEqual(0, list.ToArray().Length);
} }
class TestSerializerStateStruct11 : SerializerState
{
public TestSerializerStateStruct11()
{
SetStruct(1, 1);
}
}
class TestSerializerStateStruct20 : SerializerState
{
public TestSerializerStateStruct20()
{
SetStruct(2, 0);
}
}
class TestSerializerStateStruct11X : SerializerState
{
public TestSerializerStateStruct11X()
{
SetStruct(1, 1);
}
}
[TestMethod]
public void SerializerStateInvalidRewrap1()
{
var dss = new DynamicSerializerState(MessageBuilder.Create());
dss.SetListOfValues(8, 1);
Assert.ThrowsException<InvalidOperationException>(() => dss.Rewrap<TestSerializerStateStruct11>());
}
[TestMethod]
public void SerializerStateInvalidRewrap2()
{
var dss = new DynamicSerializerState();
dss.SetStruct(1, 0);
Assert.ThrowsException<InvalidOperationException>(() => dss.Rewrap<TestSerializerStateStruct11>());
}
[TestMethod]
public void SerializerStateInvalidRewrap3()
{
var dss = new DynamicSerializerState();
dss.SetStruct(0, 1);
Assert.ThrowsException<InvalidOperationException>(() => dss.Rewrap<TestSerializerStateStruct11>());
}
[TestMethod]
public void SerializerStateInvalidRewrap4()
{
var dss = new DynamicSerializerState();
dss.SetStruct(1, 0);
Assert.ThrowsException<InvalidOperationException>(() => dss.Rewrap<ListOfTextSerializer>());
}
[TestMethod]
public void UnboundSerializerState()
{
var dss = new DynamicSerializerState();
dss.SetStruct(1, 0);
Assert.ThrowsException<InvalidOperationException>(() => dss.WriteData(0, 0));
}
[TestMethod]
public void LinkBadUsage1()
{
var mb = MessageBuilder.Create();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetStruct(0, 1);
Assert.ThrowsException<ArgumentNullException>(() => dss.Link(0, null));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.Link(-1, dss));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.Link(1, dss));
dss.Link(0, dss);
Assert.ThrowsException<InvalidOperationException>(() => dss.Link(0, mb.CreateObject<DynamicSerializerState>()));
}
[TestMethod]
public void LinkBadUsage2()
{
var dss = new DynamicSerializerState(MessageBuilder.Create());
dss.SetListOfPointers(1);
Assert.ThrowsException<ArgumentNullException>(() => dss.Link(0, null));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.Link(-1, dss));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.Link(1, dss));
}
[TestMethod]
public void StructReadCapNoCapTable()
{
var dss = new DynamicSerializerState(MessageBuilder.Create());
dss.SetStruct(0, 1);
Assert.ThrowsException<InvalidOperationException>(() => dss.ReadCap(0));
}
[TestMethod]
public void StructReadCap()
{
var dss = DynamicSerializerState.CreateForRpc();
dss.SetStruct(0, 3);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.ReadCap(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => dss.ReadCap(100));
Assert.IsTrue(dss.ReadCap(0).IsNull);
dss.LinkToCapability(1, 99);
dss.Link(2, dss);
dss.Allocate();
Assert.IsTrue(dss.ReadCap(0).IsNull);
Assert.IsTrue(dss.ReadCap<ITestCallOrder>(0) is Proxy proxy && proxy.IsNull);
Assert.ThrowsException<Rpc.RpcException>(() => dss.ReadCap(1));
Assert.ThrowsException<Rpc.RpcException>(() => dss.ReadCap(2));
}
[TestMethod]
public void Rewrap()
{
var mb = MessageBuilder.Create();
var list1 = mb.CreateObject<ListOfStructsSerializer<TestSerializerStateStruct11>>();
list1.Init(3);
var list2 = list1.Rewrap<ListOfStructsSerializer<TestSerializerStateStruct11X>>();
list2[0].WriteData(0, 0);
Assert.ThrowsException<InvalidOperationException>(() => list2.Rewrap<TestSerializerStateStruct11>());
var obj = mb.CreateObject<TestSerializerStateStruct11>();
var obj2 = obj.Rewrap<TestSerializerStateStruct11X>();
Assert.ThrowsException<InvalidOperationException>(() => obj2.Rewrap<TestSerializerStateStruct20>());
}
[TestMethod]
public void AllocatedNil()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.Allocate();
Assert.ThrowsException<InvalidOperationException>(() => dss.SetCapability(0));
dss.SetCapability(null);
Assert.ThrowsException<InvalidOperationException>(() => dss.SetListOfPointers(1));
Assert.ThrowsException<InvalidOperationException>(() => dss.SetListOfStructs(1, 1, 1));
Assert.ThrowsException<InvalidOperationException>(() => dss.SetListOfValues(8, 1));
Assert.ThrowsException<InvalidOperationException>(() => dss.SetObject(mb.CreateObject<TestSerializerStateStruct11>()));
dss.SetObject(null);
Assert.ThrowsException<InvalidOperationException>(() => dss.SetStruct(1, 1));
}
[TestMethod]
public void SetCapability1()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetStruct(1, 1);
Assert.ThrowsException<InvalidOperationException>(() => dss.SetCapability(null));
Assert.ThrowsException<InvalidOperationException>(() => dss.SetCapability(0));
}
[TestMethod]
public void SetCapability2()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetCapability(7);
dss.SetCapability(7);
Assert.ThrowsException<InvalidOperationException>(() => dss.SetCapability(8));
Assert.ThrowsException<InvalidOperationException>(() => dss.SetCapability(null));
}
} }
} }

View File

@ -571,12 +571,12 @@ namespace Capnp.Net.Runtime.Tests
task1.Result.Dispose(); task1.Result.Dispose();
testbed.FlushCommunication(); testbed.FlushCommunication();
Assert.AreEqual(1, counters.HandleCount); Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, TestBase.ShortTimeout));
task2.Result.Dispose(); task2.Result.Dispose();
testbed.FlushCommunication(); testbed.FlushCommunication();
Assert.AreEqual(0, counters.HandleCount); Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, TestBase.ShortTimeout));
} }
} }

View File

@ -49,7 +49,7 @@ namespace Capnp
/// <summary> /// <summary>
/// The capabilities imported from the capability table. Only valid in RPC context. /// The capabilities imported from the capability table. Only valid in RPC context.
/// </summary> /// </summary>
public IList<Rpc.ConsumedCapability?>? Caps { get; set; } public IList<Rpc.ConsumedCapability>? Caps { get; set; }
/// <summary> /// <summary>
/// Current segment (essentially Segments[CurrentSegmentIndex]) /// Current segment (essentially Segments[CurrentSegmentIndex])
/// </summary> /// </summary>

View File

@ -79,7 +79,7 @@ namespace Capnp
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item> /// <item><description>This state does neither describe a struct, nor a list of pointers</description></item>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list> /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception> /// </exception>
public new void LinkToCapability(int slot, uint capabilityIndex) => base.LinkToCapability(slot, capabilityIndex); public new void LinkToCapability(int slot, uint? capabilityIndex) => base.LinkToCapability(slot, capabilityIndex);
/// <summary> /// <summary>
/// Determines the underlying object to be a struct. /// Determines the underlying object to be a struct.
@ -89,6 +89,8 @@ namespace Capnp
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception> /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
public new void SetStruct(ushort dataCount, ushort ptrCount) => base.SetStruct(dataCount, ptrCount); public new void SetStruct(ushort dataCount, ushort ptrCount) => base.SetStruct(dataCount, ptrCount);
public new void SetCapability(uint? capabilityIndex) => base.SetCapability(capabilityIndex);
/// <summary> /// <summary>
/// Determines the underlying object to be a list of (primitive) values. /// Determines the underlying object to be a list of (primitive) values.
/// </summary> /// </summary>

View File

@ -10,7 +10,7 @@ namespace Capnp
{ {
readonly ISegmentAllocator _allocator; readonly ISegmentAllocator _allocator;
readonly DynamicSerializerState _rootPtrBuilder; readonly DynamicSerializerState _rootPtrBuilder;
List<Rpc.ConsumedCapability?>? _capTable; List<Rpc.ConsumedCapability>? _capTable;
MessageBuilder(ISegmentAllocator allocator) MessageBuilder(ISegmentAllocator allocator)
{ {
@ -92,13 +92,13 @@ namespace Capnp
if (_capTable != null) if (_capTable != null)
throw new InvalidOperationException("Capability table was already initialized"); throw new InvalidOperationException("Capability table was already initialized");
_capTable = new List<Rpc.ConsumedCapability?>(); _capTable = new List<Rpc.ConsumedCapability>();
} }
/// <summary> /// <summary>
/// Returns this message builder's segment allocator. /// Returns this message builder's segment allocator.
/// </summary> /// </summary>
public ISegmentAllocator Allocator => _allocator; public ISegmentAllocator Allocator => _allocator;
internal List<Rpc.ConsumedCapability?>? Caps => _capTable; internal List<Rpc.ConsumedCapability>? Caps => _capTable;
} }
} }

View File

@ -180,7 +180,7 @@ namespace Capnp.Rpc
} }
} }
public static async Task<TInterface> Unwrap<TInterface>(this TInterface cap) where TInterface: class, IDisposable public static async Task<TInterface?> Unwrap<TInterface>(this TInterface cap) where TInterface: class, IDisposable
{ {
using var proxy = cap as Proxy; using var proxy = cap as Proxy;
@ -188,6 +188,9 @@ namespace Capnp.Rpc
return cap; return cap;
var unwrapped = await proxy.ConsumedCap.Unwrap(); var unwrapped = await proxy.ConsumedCap.Unwrap();
if (unwrapped == null)
return null;
return ((CapabilityReflection.CreateProxy<TInterface>(unwrapped)) as TInterface)!; return ((CapabilityReflection.CreateProxy<TInterface>(unwrapped)) as TInterface)!;
} }

View File

@ -16,8 +16,6 @@ namespace Capnp.Rpc
return new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token)); return new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
} }
public static LazyCapability Null => CreateBrokenCap("Null capability");
readonly Task<Proxy>? _proxyTask; readonly Task<Proxy>? _proxyTask;
public LazyCapability(Task<ConsumedCapability?> capabilityTask) public LazyCapability(Task<ConsumedCapability?> capabilityTask)

View File

@ -108,7 +108,7 @@ namespace Capnp.Rpc
try try
{ {
var cap = aorcq.Answer.Caps![(int)cur.CapabilityIndex]; var cap = aorcq.Answer.Caps![(int)cur.CapabilityIndex];
proxy = new Proxy(cap ?? LazyCapability.Null); proxy = new Proxy(cap);
} }
catch (ArgumentOutOfRangeException) catch (ArgumentOutOfRangeException)
{ {

View File

@ -197,16 +197,13 @@ namespace Capnp.Rpc
/// <param name="disposeThis">Whether to Dispose() this Proxy instance</param> /// <param name="disposeThis">Whether to Dispose() this Proxy instance</param>
/// <returns>Proxy for desired capability interface</returns> /// <returns>Proxy for desired capability interface</returns>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception> /// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception>
/// <exception cref="InvalidOperationException">This capability is broken, or mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception> /// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception> /// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception> /// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public T Cast<T>(bool disposeThis) where T: class public T Cast<T>(bool disposeThis) where T: class
{ {
if (IsNull)
throw new InvalidOperationException("Capability is broken");
using (disposeThis ? this : null) using (disposeThis ? this : null)
{ {
return (CapabilityReflection.CreateProxy<T>(ConsumedCap) as T)!; return (CapabilityReflection.CreateProxy<T>(ConsumedCap) as T)!;

View File

@ -5,13 +5,11 @@ namespace Capnp.Rpc
{ {
static class ResolvingCapabilityExtensions static class ResolvingCapabilityExtensions
{ {
public static async Task<ConsumedCapability> Unwrap(this ConsumedCapability? cap) public static async Task<ConsumedCapability?> Unwrap(this ConsumedCapability? cap)
{ {
cap ??= LazyCapability.Null;
while (cap is IResolvingCapability resolving) while (cap is IResolvingCapability resolving)
{ {
cap = await resolving.WhenResolved ?? LazyCapability.Null; cap = await resolving.WhenResolved;
} }
return cap; return cap;
@ -66,7 +64,7 @@ namespace Capnp.Rpc
switch (obj) switch (obj)
{ {
case Proxy proxy: return proxy; case Proxy proxy: return proxy;
case null: return new Proxy(LazyCapability.Null); case null: return new Proxy(null);
default: return BareProxy.FromImpl(obj); default: return BareProxy.FromImpl(obj);
} }
} }

View File

@ -1347,9 +1347,9 @@ namespace Capnp.Rpc
} }
} }
internal IList<ConsumedCapability?> ImportCapTable(Payload.READER payload) internal IList<ConsumedCapability> ImportCapTable(Payload.READER payload)
{ {
var list = new List<ConsumedCapability?>(); var list = new List<ConsumedCapability>();
if (payload.CapTable != null) if (payload.CapTable != null)
{ {
@ -1378,17 +1378,9 @@ namespace Capnp.Rpc
foreach (var cap in state.MsgBuilder.Caps) foreach (var cap in state.MsgBuilder.Caps)
{ {
var capDesc = payload.CapTable[i++]; var capDesc = payload.CapTable[i++];
if (cap == null)
{
LazyCapability.Null.Export(this, capDesc);
}
else
{
postAction += cap.Export(this, capDesc); postAction += cap.Export(this, capDesc);
cap.Release(); cap.Release();
} }
}
Tx(state.MsgBuilder.Frame); Tx(state.MsgBuilder.Frame);

View File

@ -7,7 +7,7 @@ namespace Capnp.Rpc
{ {
class Vine : Skeleton class Vine : Skeleton
{ {
public static Skeleton Create(ConsumedCapability cap) public static Skeleton Create(ConsumedCapability? cap)
{ {
if (cap is LocalCapability lcap) if (cap is LocalCapability lcap)
return lcap.ProvidedCap; return lcap.ProvidedCap;
@ -15,9 +15,9 @@ namespace Capnp.Rpc
return new Vine(cap); return new Vine(cap);
} }
Vine(ConsumedCapability consumedCap) Vine(ConsumedCapability? consumedCap)
{ {
Proxy = new Proxy(consumedCap ?? throw new ArgumentNullException(nameof(consumedCap))); Proxy = new Proxy(consumedCap);
#if DebugFinalizers #if DebugFinalizers
CreatorStackTrace = Environment.StackTrace; CreatorStackTrace = Environment.StackTrace;

View File

@ -30,7 +30,7 @@ namespace Capnp
internal MessageBuilder? MsgBuilder { get; set; } internal MessageBuilder? MsgBuilder { get; set; }
internal ISegmentAllocator? Allocator => MsgBuilder?.Allocator; internal ISegmentAllocator? Allocator => MsgBuilder?.Allocator;
internal List<Rpc.ConsumedCapability?>? Caps => MsgBuilder?.Caps; internal List<Rpc.ConsumedCapability>? Caps => MsgBuilder?.Caps;
internal SerializerState? Owner { get; set; } internal SerializerState? Owner { get; set; }
internal int OwnerSlot { get; set; } internal int OwnerSlot { get; set; }
internal uint SegmentIndex { get; set; } internal uint SegmentIndex { get; set; }
@ -156,7 +156,7 @@ namespace Capnp
/// Returns the allocated memory slice (given this state already is allocated). Note that this definition is somewhat /// Returns the allocated memory slice (given this state already is allocated). Note that this definition is somewhat
/// non-symmetric to <code>DeserializerState.RawData</code>. Never mind: You should not use it directly, anyway. /// non-symmetric to <code>DeserializerState.RawData</code>. Never mind: You should not use it directly, anyway.
/// </summary> /// </summary>
public Span<ulong> RawData => SegmentSpan.Slice(Offset, (int)WordsAllocated); public Span<ulong> RawData => IsAllocated ? SegmentSpan.Slice(Offset, (int)WordsAllocated) : Span<ulong>.Empty;
/// <summary> /// <summary>
/// The kind of object this state currently represents. /// The kind of object this state currently represents.
@ -170,12 +170,6 @@ namespace Capnp
SegmentIndex = 0; SegmentIndex = 0;
Offset = 0; Offset = 0;
} }
else if (Owner?.Kind == ObjectKind.ListOfStructs)
{
Owner.Allocate();
SegmentIndex = Owner.SegmentIndex;
Offset = Owner.Offset + OwnerSlot + 1;
}
else else
{ {
if (Allocator == null) if (Allocator == null)
@ -242,25 +236,43 @@ namespace Capnp
} }
} }
internal void EncodePointer(int offset, SerializerState target, bool allowCopy) internal Rpc.ConsumedCapability? DecodeCapPointer(int offset)
{ {
if (target == null) if (Caps == null)
throw new ArgumentNullException(nameof(target)); throw new InvalidOperationException("Capbility table not set");
if (!target.IsAllocated) if (!IsAllocated)
throw new InvalidOperationException("Target must be allocated before a pointer can be built"); {
return null;
}
if (MsgBuilder == null) WirePointer pointer = RawData[offset];
throw Unbound();
try if (pointer.IsNull)
{
return null;
}
if (pointer.Kind != PointerKind.Other)
{
throw new Rpc.RpcException(
"Expected a capability pointer, but got something different");
}
if (pointer.CapabilityIndex >= Caps.Count)
{
throw new Rpc.RpcException(
"Capability index out of range");
}
return Caps[(int)pointer.CapabilityIndex];
}
void EncodePointer(int offset, SerializerState target, bool allowCopy)
{ {
if (SegmentSpan[offset] != 0) if (SegmentSpan[offset] != 0)
throw new InvalidOperationException("Won't replace an already allocated pointer to prevent memory leaks and security flaws");
}
catch (IndexOutOfRangeException)
{ {
throw new ArgumentOutOfRangeException(nameof(offset)); throw new InvalidOperationException("Won't replace an already allocated pointer to prevent memory leaks and security flaws");
} }
if (target.Allocator != null && if (target.Allocator != null &&
@ -375,31 +387,6 @@ namespace Capnp
} }
} }
internal Rpc.ConsumedCapability? DecodeCapPointer(int offset)
{
if (offset < 0)
throw new IndexOutOfRangeException(nameof(offset));
if (Caps == null)
throw new InvalidOperationException("Capbility table not set");
WirePointer pointer = RawData[offset];
if (pointer.Kind != PointerKind.Other)
{
throw new Rpc.RpcException(
"Expected a capability pointer, but got something different");
}
if (pointer.CapabilityIndex >= Caps.Count)
{
throw new Rpc.RpcException(
"Capability index out of range");
}
return Caps[(int)pointer.CapabilityIndex];
}
/// <summary> /// <summary>
/// Links a sub-item (struct field or list element) of this state to another state. Usually, this operation is not necessary, since objects are constructed top-down. /// Links a sub-item (struct field or list element) of this state to another state. Usually, this operation is not necessary, since objects are constructed top-down.
/// However, there might be some advanced scenarios where you want to reference the same object twice (also interesting for designing amplification attacks). /// However, there might be some advanced scenarios where you want to reference the same object twice (also interesting for designing amplification attacks).
@ -471,7 +458,7 @@ namespace Capnp
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item> /// <item><description>This state does neither describe a struct, nor a list of pointers</description></item>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list> /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception> /// </exception>
protected void LinkToCapability(int slot, uint capabilityIndex) protected void LinkToCapability(int slot, uint? capabilityIndex)
{ {
var cstate = new SerializerState(); var cstate = new SerializerState();
cstate.SetCapability(capabilityIndex); cstate.SetCapability(capabilityIndex);
@ -511,14 +498,16 @@ namespace Capnp
} }
} }
public void SetCapability(uint capabilityIndex) protected void SetCapability(uint? capabilityIndex)
{
if (capabilityIndex.HasValue)
{ {
if (Kind == ObjectKind.Nil) if (Kind == ObjectKind.Nil)
{ {
VerifyNotYetAllocated(); VerifyNotYetAllocated();
Kind = ObjectKind.Capability; Kind = ObjectKind.Capability;
CapabilityIndex = capabilityIndex; CapabilityIndex = capabilityIndex.Value;
Allocate(); Allocate();
} }
else if (Kind != ObjectKind.Capability || CapabilityIndex != capabilityIndex) else if (Kind != ObjectKind.Capability || CapabilityIndex != capabilityIndex)
@ -526,6 +515,14 @@ namespace Capnp
throw AlreadySet(); throw AlreadySet();
} }
} }
else
{
if (Kind != ObjectKind.Nil)
{
throw AlreadySet();
}
}
}
/// <summary> /// <summary>
/// Determines the underlying object to be a list of (primitive) values. /// Determines the underlying object to be a list of (primitive) values.
@ -1225,10 +1222,13 @@ namespace Capnp
/// Adds an entry to the capability table if the provided capability does not yet exist. /// Adds an entry to the capability table if the provided capability does not yet exist.
/// </summary> /// </summary>
/// <param name="capability">The low-level capability object to provide.</param> /// <param name="capability">The low-level capability object to provide.</param>
/// <returns>Index of the given capability in the capability table</returns> /// <returns>Index of the given capability in the capability table, null if capability is null</returns>
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception> /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability(Rpc.ConsumedCapability? capability) public uint? ProvideCapability(Rpc.ConsumedCapability? capability)
{ {
if (capability == null)
return null;
if (Caps == null) if (Caps == null)
throw new InvalidOperationException("Underlying MessageBuilder was not enabled to support capabilities"); throw new InvalidOperationException("Underlying MessageBuilder was not enabled to support capabilities");
@ -1252,7 +1252,7 @@ namespace Capnp
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception> /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability(Rpc.Skeleton capability) public uint ProvideCapability(Rpc.Skeleton capability)
{ {
return ProvideCapability(Rpc.LocalCapability.Create(capability)); return ProvideCapability(Rpc.LocalCapability.Create(capability))!.Value;
} }
/// <summary> /// <summary>
@ -1267,12 +1267,12 @@ namespace Capnp
/// </list></param> /// </list></param>
/// <returns>Index of the given capability in the capability table</returns> /// <returns>Index of the given capability in the capability table</returns>
/// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception> /// <exception cref="InvalidOperationException">The underlying message builder was not configured for capability table support.</exception>
public uint ProvideCapability(object? obj) public uint? ProvideCapability(object? obj)
{ {
switch (obj) switch (obj)
{ {
case null: case null:
return ProvideCapability(default(Rpc.ConsumedCapability)); return null;
case Rpc.Proxy proxy: using (proxy) case Rpc.Proxy proxy: using (proxy)
return ProvideCapability(proxy.ConsumedCap); return ProvideCapability(proxy.ConsumedCap);
case Rpc.ConsumedCapability consumedCapability: case Rpc.ConsumedCapability consumedCapability:
@ -1348,8 +1348,8 @@ namespace Capnp
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil) if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
throw new InvalidOperationException("Allowed on structs only"); throw new InvalidOperationException("Allowed on structs only");
if (index >= StructPtrCount) if (index < 0 || index >= StructPtrCount)
return null; throw new ArgumentOutOfRangeException(nameof(index));
return DecodeCapPointer(index + StructDataCount); return DecodeCapPointer(index + StructDataCount);
} }

View File

@ -219,10 +219,13 @@ namespace Capnp
/// <summary> /// <summary>
/// Encodes a capability pointer. /// Encodes a capability pointer.
/// </summary> /// </summary>
/// <param name="index">capability index</param> /// <param name="index">capability index, 'null' means 'null pointer'</param>
public void SetCapability(uint index) public void SetCapability(uint? index)
{ {
if (index.HasValue)
_ptrData = ((ulong)index << 32) | (ulong)PointerKind.Other; _ptrData = ((ulong)index << 32) | (ulong)PointerKind.Other;
else
_ptrData = 0;
} }
} }
} }