2021-10-01 20:06:06 +02:00
|
|
|
|
@0x8c2f829df1930cd5;
|
|
|
|
|
|
|
|
|
|
using CSharp = import "programming_language/csharp.capnp";
|
|
|
|
|
$CSharp.namespace("FabAccessAPI.Schema");
|
|
|
|
|
|
|
|
|
|
using Persistent = import "/capnp/persistent.capnp".Persistent;
|
|
|
|
|
using Value = import "/capnp/schema.capnp".Value;
|
|
|
|
|
|
|
|
|
|
using User = import "user.capnp".User;
|
2021-10-02 12:02:38 +02:00
|
|
|
|
using L10NString = import "utils.capnp".L10NString;
|
|
|
|
|
using UUID = import "utils.capnp".UUID;
|
2021-11-26 02:25:47 +01:00
|
|
|
|
using OID = import "utils.capnp".OID;
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
struct Resource {
|
|
|
|
|
# BFFH's smallest unit of a physical or abstract "thing".
|
|
|
|
|
# A resource can be as simple and physical as a table, as complex as a PCB production line or as
|
|
|
|
|
# abstract as "people with specific know-how are present".
|
|
|
|
|
|
|
|
|
|
uuid @0 :UUID;
|
|
|
|
|
# An stable, globally unique descriptor for a resource. Two resources with the same UUID are
|
|
|
|
|
# (almost¹) guaranteed to be the same instance, and the UUID of a resource will survive through
|
|
|
|
|
# server restarts, renaming, reconfiguration etc.
|
|
|
|
|
#
|
|
|
|
|
# [¹]: UUID are 128-bit integer. A collision is *possible*, just *very* unlikely. If you
|
|
|
|
|
# generate 1 billion UUID every second for the next 100 years you have a chance for a collision
|
|
|
|
|
# of about 50%.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
id @1 :Text;
|
|
|
|
|
# Every resource in BFFH has a human-readable "name" that is locally unique, but not persistent.
|
|
|
|
|
# That is a resource called "hello" today may be called "bye" tomorrow and a resource called
|
|
|
|
|
# "hi~~" may not be the same resource as the resource called "hi~~" yesterday. This name is
|
|
|
|
|
# canonical and thus identifying. There is exactly *one* valid representation of this name at
|
|
|
|
|
# any given point in time. Thus this name can also not be translated.
|
|
|
|
|
|
|
|
|
|
name @2 :L10NString;
|
|
|
|
|
# A resource may also have a human-meaningful name that is designed to be shown to users. This
|
|
|
|
|
# name does not have to be unique or identifiable or canonical, its main use is to be
|
|
|
|
|
# human-meaningful. For example a "name" could be the translations:
|
|
|
|
|
# - (en, "Prusa SL1 SLA-Printer")
|
|
|
|
|
# - (de, "Prusa SL1 SLA-Drucker")
|
|
|
|
|
# - (es, "Impresora 3D de SLA Prusa SL1")
|
|
|
|
|
|
|
|
|
|
description @3 :L10NString;
|
|
|
|
|
# A resource may have a description attached to tell an user some more information on a resource
|
|
|
|
|
# in a free-form format.
|
|
|
|
|
# Similar to the human-meaningful name this description can be translated.
|
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
grants @4 :ResourceCaps;
|
|
|
|
|
|
|
|
|
|
grant :union {
|
|
|
|
|
# If the current session has already been given a grant this field will contain a reference
|
|
|
|
|
# to it. Since stronger grants extend weaker grants only one of these needs to be set at any
|
|
|
|
|
# given point.
|
|
|
|
|
# This is mostly useful for session resumption.
|
|
|
|
|
|
|
|
|
|
none @5 :Void;
|
|
|
|
|
# No previous grant for this resource exists for the current user
|
|
|
|
|
|
|
|
|
|
notify @6 :Notify;
|
|
|
|
|
interest @7 :Interest;
|
|
|
|
|
claim @8 :Claim;
|
|
|
|
|
# The user has a respective grant on the resource
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ResourceCaps {
|
|
|
|
|
# Capabilities transfered for a resource. Users will have some or all of these set to non-null
|
|
|
|
|
# depending on their permission level.
|
|
|
|
|
|
|
|
|
|
getState @0 () -> State;
|
2021-10-02 12:02:38 +02:00
|
|
|
|
# Readonly access to the state of a resource.
|
|
|
|
|
# A resource can have "state". State are values attached to a resource that describe a specific
|
|
|
|
|
# state that users or administrators want this resource to be in. Usually this state consists of
|
|
|
|
|
# a number of primitive values encoding for example "turned on" or "turned off".
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
setNotify @1 ( callback :Callback ) -> ( notify :Notify );
|
|
|
|
|
# Notify allows clients to be informed about state changes asyncronously. A client can register
|
|
|
|
|
# a callback that is called every time state changes happen to the resource in question.
|
|
|
|
|
# Notify callbacks are ephermal. If the connection to the server is lost any callbacks from that
|
|
|
|
|
# client for any resource are unregistered.
|
|
|
|
|
|
|
|
|
|
claim @2 () -> ClaimResponse;
|
|
|
|
|
# Request writeable access to the state of a resource.
|
2021-10-02 12:02:38 +02:00
|
|
|
|
# Resources are semaphores. They allow writeable access for n ∈ ℕ\{0} clients, depending on the
|
|
|
|
|
# exact resource in question. In some cases n ≔ 1, and the only write access is exclusive.
|
|
|
|
|
# "Claims" model this by requiring a client to first assert a claim, thus reserving a semaphore
|
|
|
|
|
# slot or failing if no more are available, and then using this claim to write to a resources
|
|
|
|
|
# state.
|
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
interest @3 () -> ( interest :Interest );
|
|
|
|
|
# Register an "Interest" on this resource.
|
2021-10-02 12:02:38 +02:00
|
|
|
|
# Sometimes clients are not just interested in the state of a resource but rather want a
|
|
|
|
|
# resource to stay in a specific state. e.g. somebody working in a makerspace wants the space to
|
|
|
|
|
# stay open, even though they themselves may not have permission to keep the makerspace open.
|
|
|
|
|
# "Interest" represents this. Specifically right now it tells BFFH that the client wants at
|
|
|
|
|
# least one `Claim` to remain.
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
override @4 () -> ( claim :Claim );
|
|
|
|
|
# Override forces a claim to a resource, even if it is already exhausted. This is
|
|
|
|
|
# primarely useful for administrative overrides.
|
2021-11-26 02:25:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
struct ClaimResponse {
|
|
|
|
|
enum Error {
|
|
|
|
|
# Error describing why a claim failed.
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
exhausted @0;
|
|
|
|
|
# There are no more free Claim slots
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
locked @1;
|
|
|
|
|
# The resource was locked
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
precondition @2;
|
|
|
|
|
# Some precondition was not met
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
dependencies @3;
|
|
|
|
|
# Resource failed to secure dependencies
|
|
|
|
|
}
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
union {
|
|
|
|
|
failed :group {
|
|
|
|
|
error @0 :Error;
|
|
|
|
|
reason @1 :L10NString;
|
2021-11-26 02:25:47 +01:00
|
|
|
|
}
|
2021-11-26 03:18:33 +01:00
|
|
|
|
success @2 :Claim;
|
2021-11-26 02:25:47 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
struct Map(Key, Value) {
|
|
|
|
|
# A serialized key-value map represented as a list of (k,v) tuples.
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
2021-10-02 12:18:28 +02:00
|
|
|
|
entries @0 :List(Entry);
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
struct Entry {
|
|
|
|
|
key @0 :Key;
|
|
|
|
|
val @1 :Value;
|
2021-10-01 20:06:06 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-26 02:25:47 +01:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
using State = Map(OID, Value);
|
2021-10-02 12:02:38 +02:00
|
|
|
|
# Update state provided to a resource via a claim is represented as a Map of human-readable
|
|
|
|
|
# identifiers to Cap'n Proto Values. These Values can be either primitive types such as Uint8,
|
|
|
|
|
# Float64 or more complex types such as structs, lists, or enums.
|
|
|
|
|
# The resulting state of a resource, which is the output of whatever internal logic the resource
|
|
|
|
|
# implements, is also represented in this form, but the keys and also values may be different.
|
|
|
|
|
#
|
|
|
|
|
# Later on very common cases (use, register, return, etc.) can get shortcut functions in the Claim
|
|
|
|
|
# interface that pre-emptively check permissions and ability (so you get the respective cap iff the
|
|
|
|
|
# resource supports that update and if you're allowed to do that) but these functions only serve to
|
|
|
|
|
# make the update more efficient than calling `update` with the string identifier and dynamic typed
|
|
|
|
|
# value but do the exact same serverside as an `update` call would. This way we can make future
|
|
|
|
|
# versions of the API more efficient and easier to use while not breaking compatibility with old
|
|
|
|
|
# clients.
|
|
|
|
|
#
|
|
|
|
|
# TODO: This has the potential problem that a newer client can not distinguish between a server
|
|
|
|
|
# using an old version of the API and a client simply not being allowed to call a specific shortcut
|
|
|
|
|
# method because in both cases that cap will be a nullptr. Could be solved by making `Claim` a
|
|
|
|
|
# struct and indicating which shortcut methods it knows of.
|
|
|
|
|
# Not sure if this is a big problem, we optimize for old clients and up-to-date servers.
|
|
|
|
|
#
|
|
|
|
|
# TODO: We should provide a number of sensible implementations for common complex `Value` types such
|
|
|
|
|
# as "colour", "temperature", etc. and define identifiers for common values.
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
|
|
|
|
|
2021-11-26 02:25:47 +01:00
|
|
|
|
interface Notify {
|
2021-11-26 03:18:33 +01:00
|
|
|
|
# If an user has a notify callback registered it can use this capability to remove it again
|
|
|
|
|
|
|
|
|
|
remove @0 ();
|
|
|
|
|
# Remove any notify callbacks from this user for this resource.
|
|
|
|
|
|
|
|
|
|
install @1 ( callback :Callback );
|
|
|
|
|
# Install a notify callback, replacing any existing one. This method is useful when getting this
|
|
|
|
|
# interface implicitly via Interest or Claim.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Callback {
|
|
|
|
|
# This callback interface needs to be implemented on the client
|
|
|
|
|
|
|
|
|
|
newState @0 State;
|
|
|
|
|
# A server will call newState() with the updated output state. However a server will only
|
|
|
|
|
# allow one in-flight call, so as long as the previous call to newState() hasn't completed
|
|
|
|
|
# the server will drop intermediary updates as to not overload a client.
|
|
|
|
|
# Specifically, example timeline:
|
|
|
|
|
# 1. Update A
|
|
|
|
|
# 2. Server calls newState(A)
|
|
|
|
|
# 3. Update B
|
|
|
|
|
# 4. Update C
|
|
|
|
|
# 5. Call to newState(A) completes
|
|
|
|
|
# 6. Server calls newState(C)
|
|
|
|
|
# So Update B was never sent to the client but the client will eventually always end up with
|
|
|
|
|
# the latest state.
|
|
|
|
|
|
|
|
|
|
# TODO: There should probably be a more efficient approach here too, something along the
|
|
|
|
|
# lines of server-side filtering.
|
2021-10-01 20:06:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 02:25:47 +01:00
|
|
|
|
interface Interest extends(Notify) {
|
2021-11-26 03:18:33 +01:00
|
|
|
|
# "Interest" right now tells BFFH that the client wants at least one `Claim` to remain.
|
|
|
|
|
# However, more generally an Interest allows hooking into state changes and block or modify
|
|
|
|
|
# them.
|
2021-10-02 12:02:38 +02:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
dropInterest @0 ();
|
|
|
|
|
# Remove this interest from a resource.
|
2021-10-02 12:02:38 +02:00
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
lock @1 ();
|
|
|
|
|
# Lock a resource, making all future state changes from any user but the current one fail until
|
|
|
|
|
# the lock is released.
|
|
|
|
|
|
|
|
|
|
unlock @2 ();
|
|
|
|
|
# Unlock the resource again, allowing other users to change state again.
|
2021-10-01 20:06:06 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
interface Claim extends(Interest) {
|
2021-10-01 20:06:06 +02:00
|
|
|
|
# TODO: extend Persistance. Claims and Interests need to be able to survive a connection loss,
|
|
|
|
|
# which is exactly what `SturdyRef`/Persistance are designed to provide. The Persistance
|
|
|
|
|
# interface only provides one method, `save`, returning a `SturdyRef`. A SturdyRef is a generic
|
|
|
|
|
# and generally speaking opaque type that can be restored to a live capability using some sort
|
|
|
|
|
# of `Restorer` service.
|
|
|
|
|
# In this case the `Restorer` service could be `Claimable` / `Interestable` providing a
|
|
|
|
|
# `restore( ref: SturdyRef )` method.
|
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
readInput @0 () -> State;
|
|
|
|
|
# Get the current *input* state. This is not the output state that `Notify` or Actors get access
|
|
|
|
|
# to but instead the currently stored input state of a resource.
|
|
|
|
|
|
|
|
|
|
update @1 State -> UpdateResult;
|
2021-10-01 20:06:06 +02:00
|
|
|
|
# Update the State of the claimed resource with the given one
|
|
|
|
|
|
2021-11-26 03:18:33 +01:00
|
|
|
|
dropClaim @2 ();
|
|
|
|
|
# Drop this claim
|
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
struct UpdateResult {
|
|
|
|
|
enum Error {
|
|
|
|
|
# Reason why the update failed
|
2021-10-01 20:06:06 +02:00
|
|
|
|
|
2021-10-02 12:02:38 +02:00
|
|
|
|
denied @0;
|
|
|
|
|
# Update was denied beause user is missing an required permission
|
|
|
|
|
|
|
|
|
|
precondition @1;
|
|
|
|
|
# Some other precondition failed, e.g. because a required field is not set
|
|
|
|
|
|
|
|
|
|
invalid @2;
|
|
|
|
|
# The update is invalid, e.g. because an unknown field was set.
|
|
|
|
|
|
|
|
|
|
typeError @3;
|
|
|
|
|
# A field in the update has a known identifier but a bad type for that identifier
|
2021-11-26 03:18:33 +01:00
|
|
|
|
|
|
|
|
|
locked @4;
|
|
|
|
|
# The state is currently locked and can not be modified by anybody but the user that
|
|
|
|
|
# issued the lock.
|
2021-10-02 12:02:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
union {
|
|
|
|
|
failed :group {
|
|
|
|
|
error @0 :Error;
|
2021-11-26 03:18:33 +01:00
|
|
|
|
field @1 :OID;
|
2021-10-02 12:02:38 +02:00
|
|
|
|
reason @2 :L10NString;
|
|
|
|
|
}
|
|
|
|
|
success @3 :Void;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-01 20:06:06 +02:00
|
|
|
|
}
|