From d5d07a12035adce06ccc75b2b9a33afd84961446 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Tue, 1 Nov 2022 11:54:01 +0100 Subject: [PATCH] Flesh out state updates --- auth.capnp | 4 +-- claim.capnp | 17 ++++++++++++- main.capnp | 30 ++++++++++------------ notify.capnp | 18 ++++++++++--- resource.capnp | 36 ++++++++++++++++---------- resources.capnp | 6 ++--- state.capnp | 11 +++++++- users.capnp | 2 +- utils.capnp | 68 ++++++++++++++++++++++--------------------------- 9 files changed, 113 insertions(+), 79 deletions(-) diff --git a/auth.capnp b/auth.capnp index e4b2faa..c26168d 100644 --- a/auth.capnp +++ b/auth.capnp @@ -3,8 +3,8 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -using L10NString = import "utils.capnp".L10NString; -using Session = import "main.capnp".Session; +using import "utils.capnp".L10NString; +using import "main.capnp".Session; struct Response { enum Reason { diff --git a/claim.capnp b/claim.capnp index e2c182e..db56c2b 100644 --- a/claim.capnp +++ b/claim.capnp @@ -7,6 +7,7 @@ using import "/capnp/rpc.capnp".SturdyRef; using import "persistent.capnp".Persistent; using import "state.capnp".State; +using import "state.capnp".Update; interface Claimable extends (Persistent) { restore @0 ( sturdy :SturdyRef ) -> ( claim :Claim ); @@ -18,5 +19,19 @@ interface Claimable extends (Persistent) { } interface Claim extends (Persistent) { - update @0 ( state :State ) -> (); + update @0 ( update :Update ) -> ( error :Error ); + # Transactionally update a resource via a claim. + # + # The parameter `update` is a list of `UpdateValue` that specify a list of behaviours to be + # updated with the associated data. The format of this data depends on and is defined by the + # behaviour. + # An update call is atomic and transactional, if any one of the UpdateValue can not be applied + # the entire update call fails and is not applied. A client may send multiple update calls in + # parallel to opt out of the transactional behaviour of update. The ordering in which multiple + # update calls are applied is not specified, a client MUST NOT rely on updates happening in the + # order they are sent. + # The returned `error` is NULL if the update call succeeded. + interface Error { + + } } diff --git a/main.capnp b/main.capnp index a908427..e3d00b1 100644 --- a/main.capnp +++ b/main.capnp @@ -3,9 +3,9 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -using Authentication = import "auth.capnp".Authentication; -using Resources = import "resources.capnp".Resources; -using Users = import "users.capnp".Users; +using import "auth.capnp".Authentication; +using import "resources.capnp".Resources; +using import "users.capnp".Users; struct Version { @@ -18,9 +18,8 @@ interface Bootstrap getAPIVersion @0 () -> Version; getServerRelease @1 () -> ( name :Text, release :Text ); - # Returns the server implementation name and version/build number - # Designed only for human-facing debugging output so should be informative - # over machine-readable. + # Returns the server implementation name and version/build number Designed only for human-facing + # debugging output so should be informative over machine-readable. # Example: ("bffhd", "0.3.1-f397e1e [rustc 1.57.0 (f1edd0429 2021-11-29)]") mechanisms @2 () -> ( mechs :List(Text) ); @@ -28,22 +27,21 @@ interface Bootstrap createSession @3 ( mechanism :Text, initialData :Data ) -> ( authentication :Authentication ); - # Create a new session with the server that you wish to authenticate using - # `mechanism`. If the mechanism is a client-first mechanism you MAY set - # `initialData` to contain the data you want to send. If the mechanism is - # server-first or you do not wish to send initial data, make initialData a - # NULL-pointer. + # Create a new session with the server that you wish to authenticate using `mechanism`. If the + # mechanism is a client-first mechanism you MAY set `initialData` to contain the data you want + # to send. If the mechanism is server-first or you do not wish to send initial data, make + # initialData a NULL-pointer. } struct Session { - # An API session with the server. The below capabilities are set to NULL if - # the authenticated user doesn't have permission to access the system in - # question, or if the server does not implement it. + # An API session with the server. The below capabilities are set to NULL if the authenticated + # user doesn't have permission to access the system in question, or if the server does not + # implement it. resources @0 :Resources; # Access to the resources configured. users @1 :Users; - # User administration. This includes both modifying other users and - # self-modification, so this is allowed for most sessions + # User administration. This includes both modifying other users and self-modification, so this + # is allowed for most sessions } diff --git a/notify.capnp b/notify.capnp index 81457ea..346d9e2 100644 --- a/notify.capnp +++ b/notify.capnp @@ -3,14 +3,26 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -using State = import "state.capnp".State; +using import "state.capnp".State; +using import "state.capnp".Update; interface Notifyable { - subscribe @0 ( subscriber :Subscriber ) -> ( subscription :Subscription ); + state @0 () -> ( state :State ); + # Returns the current state of a resource. + + subscribe @1 ( subscriber :Subscriber ) -> ( subscription :Subscription ); + # Subscribe to state updates. The passed in `subscriber` is an interface implemented on the + # client side that a server calls to send update notifications. } interface Subscriber { - newState @0 ( state :State ) -> (); + update @0 ( update :Update ) -> (); + # Called by a server when a new state was produced for this resource. This method MAY not be + # called when a resource was updated but did not change its state. A server will only ever have + # one running update call per client session, so a client can limit the rate of updates by not + # resolving this call immediately. A server will coalesce multiple updates into a single update + # notification, meaning a client is not guaranteed to receive every intermediary state for a + # resource. } interface Subscription { } diff --git a/resource.capnp b/resource.capnp index 569067b..b31cf6e 100644 --- a/resource.capnp +++ b/resource.capnp @@ -11,23 +11,31 @@ using import "claim.capnp".Claimable; using import "utils.capnp".OID; interface Resource extends (Persistent) { - # 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". + # 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". type @0 () -> ( types :List(OID) ); - # The 'type' of Resource. Each OID in the list specifies certain behaviours - # that this Resource follows. + # The 'type' of Resource. Each OID in the list specifies certain behaviours that this Resource + # follows. - notify @1 () -> ( notify :Notifyable ); - # NULL if the user does not have permission to read this resource, or if - # this resource is not notifiable + describe @1 () -> Description; + # Return information about this resource. This information is usually rather static, but may + # change between calls. - interest @2 () -> ( interest :Interestable ); - # NULL if this resource is not interestable or the user does not have - # permission to set interests for this resource. + notify @2 () -> ( notify :Notifyable ); + # NULL if the user does not have permission to read this resource, or if this resource is not + # notifiable + + interest @3 () -> ( interest :Interestable ); + # NULL if this resource is not interestable or the user does not have permission to set + # interests for this resource. + + claim @4 () -> ( claim :Claimable ); + # NULL if the user does not have permission to write to this resource, or if this resource is + # not (ever!) claimable +} + +struct Description { - claim @3 () -> ( claim :Claimable ); - # NULL if the user does not have permission to write to this resource, or if - # this resource is not (ever!) claimable } diff --git a/resources.capnp b/resources.capnp index 5e71557..b71874e 100644 --- a/resources.capnp +++ b/resources.capnp @@ -5,7 +5,7 @@ $CSharp.namespace("FabAccessAPI.Schema"); using import "/capnp/rpc.capnp".SturdyRef; -using Resource = import "resource.capnp".Resource; +using import "resource.capnp".Resource; interface Resources { restore @0 ( sturdy :SturdyRef ) -> ( resources :Resource ); @@ -14,8 +14,8 @@ interface Resources { list @1 () -> ( resources :List(Resource) ); getByUrn @2 ( urn :Text ) -> ( resource :Resource ); - # Returns a NULL capability if the resource doesn't exist or an user - # doesn't have disclose permission for that resource. + # Returns a NULL capability if the resource doesn't exist or an user doesn't have disclose + # permission for that resource. getByName @3 ( name :Text ) -> ( resource :Resource ); } diff --git a/state.capnp b/state.capnp index 2820d95..b1a9a78 100644 --- a/state.capnp +++ b/state.capnp @@ -3,6 +3,15 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -interface State { +using import "utils.capnp".OID; +interface State { + get @0 ( oid :OID ) -> ( val :AnyPointer ); } + +struct UpdateValue { + oid @0 :OID; + val @1 :AnyPointer; +} + +using Update = List(UpdateValue); diff --git a/users.capnp b/users.capnp index e324cfb..213ca41 100644 --- a/users.capnp +++ b/users.capnp @@ -3,7 +3,7 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -using User = import "user.capnp".User; +using import "user.capnp".User; interface Users { whoami @0 () -> ( user :User ); diff --git a/utils.capnp b/utils.capnp index 01b40b5..217117b 100644 --- a/utils.capnp +++ b/utils.capnp @@ -1,41 +1,35 @@ @0xed0c02f41fea6b5a; interface L10NString { - # Any string type that is intended to be displayed to an user that is more - # than an identifier to be used as-is must be able to be localized into the - # users preferred language. This includes description, help messages, etc. - # but of course does not extend to usernames. - # TODO: Potentially make generic over the localized content (e.g. dates)? - # Can be done after the fact without braking protocol, so no big issue. + # Any string type that is intended to be displayed to an user that is more than an identifier to + # be used as-is must be able to be localized into the users preferred language. This includes + # description, help messages, etc. but of course does not extend to usernames. + # TODO: Potentially make generic over the localized content (e.g. dates)? Can be done after the + # fact without braking protocol, so no big issue. get @0 ( lang :Text ) -> ( lang :Text, content :Text ); - # Retrieve the string in the given locale. The input parameter MUST be a - # RFC5646-formatted locale identifier (e.g: "en-US", "de-DE", "az-Arab-IR"). + # Retrieve the string in the given locale. The input parameter MUST be a RFC5646-formatted + # locale identifier (e.g: "en-US", "de-DE", "az-Arab-IR"). # - # If a server can't find a localized version matching exactly it MUST try to - # substitute it. Substitution MUST always return more specific matches for - # general queries. e.g. if "it" is requested and the server has "it-CH" - # available it returns this string. + # If a server can't find a localized version matching exactly it MUST try to substitute it. + # Substitution MUST always return more specific matches for general queries. e.g. if "it" is + # requested and the server has "it-CH" available it returns this string. # - # Substitution SHOULD NOT cross language barriers, e.g. returning "en-GB" - # for a string requested in "cy-GB". Substitution MUST NOT return a - # localization in a different language unless server has a priori knowledge - # that the user can read and understand said language. + # Substitution SHOULD NOT cross language barriers, e.g. returning "en-GB" for a string requested + # in "cy-GB". Substitution MUST NOT return a localization in a different language unless server + # has a priori knowledge that the user can read and understand said language. # - # Substitution SHOULD prefer unspecified subtags over wrong subtags. If - # "es-AR" is requested and a server has "es", and "es-VE" available, "es" - # should be selected. + # Substitution SHOULD prefer unspecified subtags over wrong subtags. If "es-AR" is requested and + # a server has "es", and "es-VE" available, "es" should be selected. # - # A server MUST set the output `lang` field to the exact tag that the - # content it sends was written in and `content` to the localized string. - # e.g. If a string is requested for "sr" and the server has found a string - # that was configured as "sr-Cyrl-BA" the server sets lang to "sr-Cyrl-BA". + # A server MUST set the output `lang` field to the exact tag that the content it sends was + # written in and `content` to the localized string. e.g. If a string is requested for "sr" and + # the server has found a string that was configured as "sr-Cyrl-BA" the server sets lang to + # "sr-Cyrl-BA". # - # If a server can't find a suitable substitute it MUST set the output - # `content` to a NULL pointer and set the output `lang` to the input `lang` - # it was passed. - # If a server can't parse a given `lang` tag it MUST set the output `lang` - # to NULL. + # If a server can't find a suitable substitute it MUST set the output `content` to a NULL + # pointer and set the output `lang` to the input `lang` it was passed. + # If a server can't parse a given `lang` tag it MUST set the output `lang` to NULL. available @1 () -> ( langs :List(Text) ); # Returns the list of locales this content is available in. @@ -59,13 +53,11 @@ struct UUID { } using OID = Data; -# An OID is encoded as a sequence of varints. In this encoding the lower 7 bits -# of each octet contain data bits while the MSB indicates if the *following* -# octet is still part of this edge. It is the same encoding UTF-8 uses. To -# decode you simply collect octets until you find an octet <128 and then concat -# the data bits of all the octets you've accumulated, including the current one. -# This gives you the value of one node. Continue until you've exhausted the -# available data. This is a rather efficient encoding since almost all edges of -# the OID tree are smaller than 128 and thus encode into one byte. -# X.208 does *not* limit the size of nodes! However, a reasonable size limit is -# 128 bit per node, which is the size of the UUID nodes in the `2.25` subtree. +# An OID is encoded as a sequence of varints. In this encoding the lower 7 bits of each octet +# contain data bits while the MSB indicates if the *following* octet is still part of this edge. It +# is the same encoding UTF-8 uses. To decode you simply collect octets until you find an octet <128 +# and then concat the data bits of all the octets you've accumulated, including the current one. +# This gives you the value of one node. Continue until you've exhausted the available data. This is +# a rather efficient encoding since almost all edges of the OID tree are smaller than 128 and thus +# encode into one byte. X.208 does *not* limit the size of nodes! However, a reasonable size limit +# is 128 bit per node, which is the size of the UUID nodes in the `2.25` subtree.