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