diff --git a/authenticationsystem.capnp b/auth.capnp similarity index 98% rename from authenticationsystem.capnp rename to auth.capnp index e576ef0..1f7145a 100644 --- a/authenticationsystem.capnp +++ b/auth.capnp @@ -8,7 +8,7 @@ using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); using L10NString = import "utils.capnp".L10NString; -using Session = import "connection.capnp".Session; +using Session = import "main.capnp".Session; struct Response { enum Error { @@ -100,7 +100,7 @@ struct Response { } interface Authentication { - step @0 ( data: Data ) -> ( response: Response ); + step @0 (data :Data) -> Response; # Respond to a challenge with more data. A client MUST NOT call this after having received an # "successful" response. diff --git a/machine.capnp b/machine.capnp deleted file mode 100644 index be0ac7e..0000000 --- a/machine.capnp +++ /dev/null @@ -1,98 +0,0 @@ -@0x8c2f829df1930cd5; - -using Rust = import "programming_language/rust.capnp"; -$Rust.parentModule("schema"); - -using CSharp = import "programming_language/csharp.capnp"; -$CSharp.namespace("FabAccessAPI.Schema"); - -using General = import "general.capnp"; -using User = import "user.capnp".User; -using Space = import "space.capnp".Space; - -struct Machine { - enum MachineState { - free @0; - inUse @1; - toCheck @2; - blocked @3; - disabled @4; - reserved @5; - } - struct MachineInfoExtended { - currentUser @0 :User; - transferUser @1 :User; - } - - struct Reservation { - user @0 :User; - start @1: UInt64; - end @2: UInt64; - } - - id @0 :General.UUID; - space @1 :Space; - name @2 :Text; - description @3 :Text; - state @4 :MachineState; - manager @5:User; - - info @6 :Info; - interface Info $CSharp.name("InfoInterface") { - getMachineInfoExtended @0 () -> ( machineInfoExtended :MachineInfoExtended, dummy :UInt8 = 0); - - getPropertyList @1 () -> ( propertyList :List(General.KeyValuePair) ); - - getReservationList @2 () -> ( reservationList :List(Reservation) ); - } - - use @7 :Use; - interface Use $CSharp.name("UseInterface") { - use @0 (); - - reserve @1 (); - reserveto @2 (start :UInt64, end :UInt64); - } - - inuse @8 :InUse; - interface InUse $CSharp.name("InUseInterface") { - giveBack @0 (); - sendRawData @1 (data :Data); - } - - transfer @9 :Transfer; - interface Transfer $CSharp.name("TransferInterface") { - accept @0 (); - reject @1 (); - } - - check @10 :Check; - interface Check $CSharp.name("CheckInterface") { - check @0 (); - reject @1 (); - } - - manage @11 :Manage; - interface Manage $CSharp.name("ManageInterface") { - setProperty @0 (property :General.KeyValuePair); - removeProperty @1 (property :General.KeyValuePair); - - forceUse @2 (); - forceFree @3 (); - - forceTransfer @4 (user :User); - - block @5 (); - disabled @6 (); - } - - admin @12 :Admin; - interface Admin $CSharp.name("AdminInterface") { - forceSetState @0 ( state :MachineState ); - forceSetUser @1 ( user :User ); - - getAdminPropertyList @2 () -> (propertyList :List(General.KeyValuePair)); - setAdminProperty @3 (property :General.KeyValuePair); - removeAdminProperty @4 (property :General.KeyValuePair); - } -} diff --git a/connection.capnp b/main.capnp similarity index 56% rename from connection.capnp rename to main.capnp index 667809c..7970447 100644 --- a/connection.capnp +++ b/main.capnp @@ -6,17 +6,16 @@ $Rust.parentModule("schema"); using CSharp = import "programming_language/csharp.capnp"; $CSharp.namespace("FabAccessAPI.Schema"); -using Authentication = import "authenticationsystem.capnp".Authentication; -using MachineSystem = import "machinesystem.capnp".MachineSystem; -using UserSystem = import "usersystem.capnp".UserSystem; -using PermissionSystem = import "permissionsystem.capnp".PermissionSystem; +using Authentication = import "auth.capnp".Authentication; +using Ressources = import "ressources.capnp".Ressources; +using Users = import "users.capnp".Users; interface Bootstrap { mechanisms @0 () -> ( mechs: List(Text) ); # Get a list of Mechanisms this server allows in this context. - createSession @1 ( mechanism :Text, initialData :Data ) -> ( authentication :Authentication); + createSession @1 ( 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 @@ -24,7 +23,13 @@ interface Bootstrap } struct Session { - machineSystem @0 : MachineSystem; - userSystem @1 : UserSystem; - permissionSystem @2 : PermissionSystem; + # 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. + + ressources @0 : Ressources; + # Access to the ressources configured. + + users @1 : Users; + # User administration. This includes both modifying other users and self-modification, so this + # is allowed for most sessions } diff --git a/permissionsystem.capnp b/permissionsystem.capnp deleted file mode 100644 index fbca4be..0000000 --- a/permissionsystem.capnp +++ /dev/null @@ -1,22 +0,0 @@ -@0xd0568a21cf11488e; - -using Rust = import "programming_language/rust.capnp"; -$Rust.parentModule("schema"); - -using CSharp = import "programming_language/csharp.capnp"; -$CSharp.namespace("FabAccessAPI.Schema"); - -using Role = import "role.capnp".Role; - -interface PermissionSystem -{ - info @0 () -> ( info : Info ); - interface Info $CSharp.name("InfoInterface") { - getRoleList @0 () -> ( role_list :List(Role) ); - } - - manage @1 () -> ( manage : Manage ); - interface Manage $CSharp.name("ManageInterface") { - - } -} \ No newline at end of file diff --git a/resource.capnp b/resource.capnp new file mode 100644 index 0000000..badf634 --- /dev/null +++ b/resource.capnp @@ -0,0 +1,173 @@ +@0x8c2f829df1930cd5; + +using Rust = import "programming_language/rust.capnp"; +$Rust.parentModule("schema"); + +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 General = import "general.capnp"; +using User = import "user.capnp".User; +using Space = import "space.capnp".Space; + +struct Node { + # A node in the state tree. If it's the root note this struct "contains" the whole tree. + + # TODO: I'm not happy with this representation. While it's about as generic as we can get it's + # unhandly because all clients and servers have to always manually check every leaf of the + # state tree, relying on convention instead of static type checking. But I'm not sure how else + # to represent the state extensibly in a way that lets us evolve the protocol by stabilizing + # extensions. One option could be to use OID or UUID as "tag bits" and "stabilize" them by + # defining those as `const` values, but that wouldn't give us proper type checking either. + + part @0 :Text; + # Name of the node, making up a path to this node (e.g. "set/colour/red") + + union { + # Content of a node. A node has either children *or* a value, not both. + + children @1 :List(Node); + # Node is not a leaf node ⇒ it has a list of children + + value @2 :Value; + # Node is a leaf node ⇒ it contains a (typed) Cap'n Proto value. + # The type `Value` comes from the Cap'n Proto schema definition file (usually + # /usr/include/capnp/schema.capnp) and can be any basic capnp type, including lists and + # structs (as :AnyPointer which a client has to cast) + } +} + +struct Applied { + # Encodes if a specific actor has applied/verified a state change + + name @0 :Text; + # Name of the actor + + state @1 :State; + # State of the state change in the actor + enum State { + unapplied @0; + applied @1; + verified @2; + } +} + +interface Access { + # Allow syncronous read access to a resource's state. You're not given this capability directly + # but instead Notify, Interest and Claim all extend it, allowing you to call these methods from + # any of those. + + readState @0 Node; + readApplied @1 Applied; + + # TODO: There should probably be a more efficient approach for reading state than "read *all* + # state". +} + +interface Notify extends(Access) { + # The Notify interface allows clients to be informed about state changes asyncronously. + # It is mainly designed around the `register` function which allows a client to register a + # Callback on the client that is called every time state changes happen to the resource in + # question. + # Notify are ephermal. If the connection to the server is lost all `Notify` are unregistered + + register @0 ( cb: Callback ); + # Register a given callback to be called on every state update. If this client already has a + # callback registered for this resource the old callback is replaced. + # The two fields `state` and `applied` indicate interest for `state` and `applied`. If they are + # unset the respective method on the callback will not be called. + + unregister @1 (); + # Unregister the current callback, if any. + + interface Callback { + # This callback interface is implemented on the client + + newState @0 Node; + # A server will call newState() with the updated set state tree if `state` was set to `true` + # in `register`, however unless the last call to newState() didn't complete yet, as to not + # overload a client. + + # TODO: There should probably be a more efficient approach here too, something along the + # lines of server-side filtering. + + # TODO: Add newApplied? + } +} + +interface Interestable { + interest @0 () -> ( interest: Interest ); +} + +interface Interest extends(Access) { + register @0 ( cb: Callback ); + unregister @1 (); + + blocking @2 (); + # As an alternative to the `register`/`Callback` system you can also call `blocking` which will + # — as the name suggests — block until the last claim was dropped. + + interface Callback { + drop @0 (); + # The last claim on the resource this Interest is registered on was dropped, invalidating + # the Interest. + } +} + +interface Claimable { + # Having this capability set (i.e. not be a `nullptr`) means the user has at least writeable + # access to a resource and the resource is claimable (n > 0). + + claim @0 () -> ClaimResponse; + # Assert a claim on a resource. +} + +interface Lockable { + # Having this capability set means the user has managerial access to a resource. + + lock @0 () -> ( lock: Claim ); +} + +struct ClaimResponse { + enum Error { + # Error describing why a claim failed. + + exhausted @0; + # There are no more free Claim slots + + locked @1; + # The resource was locked + } + + union { + error @0 :Error; + success @1 :Claim; + } +} + +interface Claim extends(Access) { + # 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. + + update @0 Node; + # Update the State of the claimed resource with the given one +} + +struct Resource { + name @0 :Text; + description @1 :Text; + typeid @2 :Text; + + notify @3 :Notify; + interest @4 :Interest; + claimable @5 :Claimable; + lockable @6 :Lockable; +} diff --git a/machinesystem.capnp b/resources.capnp similarity index 54% rename from machinesystem.capnp rename to resources.capnp index 2697d3f..04118d5 100644 --- a/machinesystem.capnp +++ b/resources.capnp @@ -9,12 +9,8 @@ $CSharp.namespace("FabAccessAPI.Schema"); using General = import "general.capnp"; using Machine = import "machine.capnp".Machine; -interface MachineSystem +interface Ressources { - info @0 () -> ( info : Info ); - interface Info $CSharp.name("InfoInterface") { - getMachineList @0 () -> ( machine_list :List(Machine) ); - - getMachine @1 ( name :Text ) -> ( machine :Machine, dummy :UInt8 = 0 ); - } + listAll @0 () -> ( machine_list :List(Machine) ); + get @1 ( name :Text ) -> Machine; } diff --git a/users.capnp b/users.capnp new file mode 100644 index 0000000..96e3793 --- /dev/null +++ b/users.capnp @@ -0,0 +1,24 @@ +@0x9a05e95f65f2edda; + +using Rust = import "programming_language/rust.capnp"; +$Rust.parentModule("schema"); + +using CSharp = import "programming_language/csharp.capnp"; +$CSharp.namespace("FabAccessAPI.Schema"); + +using General = import "general.capnp"; +using User = import "user.capnp".User; + +interface Users +{ + whoami @0 () -> User; + + manage @1 () -> ( manage :Manage ); + interface Manage $CSharp.name("ManageInterface") { + list @0 () -> ( users :List(User) ); + + addUser @1 ( username: Text, password: Text ) -> User; + + removeUser @2 User; + } +} diff --git a/usersystem.capnp b/usersystem.capnp deleted file mode 100644 index 955514b..0000000 --- a/usersystem.capnp +++ /dev/null @@ -1,26 +0,0 @@ -@0x9a05e95f65f2edda; - -using Rust = import "programming_language/rust.capnp"; -$Rust.parentModule("schema"); - -using CSharp = import "programming_language/csharp.capnp"; -$CSharp.namespace("FabAccessAPI.Schema"); - -using General = import "general.capnp"; -using User = import "user.capnp".User; - -interface UserSystem -{ - info @0 () -> ( info : Info ); - interface Info $CSharp.name("InfoInterface") { - getUserSelf @0 ( ) -> ( user :User, dummy :UInt8 = 0 ); - } - - manage @1 () -> ( manage : Manage ); - interface Manage $CSharp.name("ManageInterface") { - getUserList @0 () -> ( user_list :List(User) ); - - addUser @1 (username :Text, password: Text) -> ( user :User ); - removeUser @2 (user :User, dummy :UInt8 = 0); - } -} \ No newline at end of file