Files
Thomas Leonard 4f53cd9164 Restorer.Table.create now takes a switch
The table is now cleared when the switch finishes. Avoids having to
remember to set this up manually.

Also, no longer yield when returning a cached ref. Seems unnecessary,
and could cause a leak if cancelled.
2024-12-03 10:29:26 +00:00

151 lines
5.7 KiB
OCaml

(** This package adds networking support, including TLS.
Libraries should not need to link against this package (just use capnp-rpc
instead), since they generally shouldn't care whether services are local or
remote. *)
open Capnp_rpc.Std
module S = S
module Endpoint = Endpoint
module Two_party_network = Two_party_network
module Auth = Auth
module Tls_wrapper = Tls_wrapper
module Restorer : sig
module Id : sig
type t
(** The object ID passed in the Cap'n Proto Bootstrap message. *)
val generate : unit -> t
(** [generate ()] is a fresh unguessable service ID.
Note: you must initialise `Nocrypto`'s entropy before calling this
(you will get a runtime error if you forget). *)
val derived : secret:string -> string -> t
(** [derived ~secret name] is a service ID based on [secret] and [name].
It is calculated as [SHA256.hmac secret name].
[secret] could be the hash of a private key file, for example. *)
val public : string -> t
(** [public name] is the service ID [name].
This may be useful for interoperability with non-secure clients that expect
to use a plain-text service ID (e.g. "calculator"). It could also be
useful if [name] is some unguessable token you have generated yourself. *)
val digest : Auth.hash -> t -> string
(** [digest h id] is the digest [h id].
Since [id] is normally a secret token, we must be careful not to allow
timing attacks (taking a slightly different amount of time to return an
error depending on how much of the ID the caller guessed correctly).
Taking a secure hash of the value first is one way to avoid this, since
revealing the hash isn't helpful to the attacker. *)
val to_string : t -> string
(** [to_string t] is the raw bytes of [t]. *)
val pp : t Fmt.t
val equal : t -> t -> bool
end
(** {2 Resolutions} *)
type resolution
(** Internally, this is just [('a Capability.t, Capnp_rpc.Exception.t) result] but the
types work out better having it abstract. *)
val grant : 'a Capability.t -> resolution
(** [grant x] is [Ok x]. *)
val reject : Capnp_rpc.Exception.t -> resolution
(** [reject x] is [Error x]. *)
val unknown_service_id : resolution
(** [unknown_service_id] is a standard rejection message. *)
(** {2 Restorers} *)
type t
(** A restorer looks up live capabilities from service IDs. *)
val none : t
(** [none] is a restorer that rejects everything. *)
val single : Id.t -> 'a Capability.t -> t
(** [single id cap] is a restorer that responds to [id] with [cap] and
rejects everything else. *)
module type LOADER = sig
type t
(** A user-provided function to restore services from persistent storage. *)
val hash : t -> Auth.hash
(** [hash t] is the hash to apply to a [Restorer.Id.t] to get the storage key,
which is passed to [load].
You should use the [hash id] value to find the item. Note that [hash]
is purely a local security measure - remote peers only see the ID. *)
val make_sturdy : t -> Id.t -> Uri.t
(** [make_sturdy t id] converts an ID to a full URI, by adding the
hosting vat's address and fingerprint. *)
val load : t -> 'a Sturdy_ref.t -> string -> resolution
(** [load t sr digest] is called to restore the service with key [digest].
[sr] is a sturdy ref that refers to the service, which the service
might want to hand out to clients.
Note that connecting to [sr] will block until the loader has returned.
The result is cached until its ref-count reaches zero, so the table
will never allow two live capabilities for a single [Id.t] at once. It
will also not call [load] twice in parallel for the same digest. *)
end
module Table : sig
type t
(** A restorer that keeps a hashtable mapping IDs to capabilities in memory. *)
val create : sw:Eio.Switch.t -> (Id.t -> Uri.t) -> t
(** [create ~sw make_sturdy] is a new in-memory-only table.
[make_sturdy id] converts an ID to a full URI, by adding the
hosting vat's address and fingerprint.
@param sw {!clear} is called when [sw] is turned off. *)
val of_loader : sw:Eio.Switch.t -> (module LOADER with type t = 'loader) -> 'loader -> t
(** [of_loader ~sw (module Loader) l] is a new caching table that uses
[Loader.load l sr (Loader.hash id)] to restore services that aren't in the cache.
@param sw The [load] function runs in a new fiber in [sw].
{!clear} is called when [sw] is turned off. *)
val add : t -> Id.t -> 'a Capability.t -> unit
(** [add t id cap] adds a mapping to [t].
It takes ownership of [cap] (it will call [Capability.dec_ref cap] on [clear]). *)
val sturdy_ref : t -> Id.t -> 'a Sturdy_ref.t
(** [sturdy_ref t id] is a sturdy ref that can be used to restore service [id]. *)
val remove : t -> Id.t -> unit
(** [remove t id] removes [id] from [t].
It decrements the capability's ref count if it was added manually with [add]. *)
val clear : t -> unit
(** [clear t] removes all entries from the table. *)
end
val of_table : Table.t -> t
val restore : t -> Id.t -> ('a Capability.t, Capnp_rpc.Exception.t) result
(** [restore t id] restores [id] using [t].
You don't normally need to call this directly, as the Vat will do it automatically. *)
end
module type VAT_NETWORK = S.VAT_NETWORK with
type 'a capability := 'a Capability.t and
type restorer := Restorer.t and
type service_id := Restorer.Id.t and
type 'a sturdy_ref := 'a Sturdy_ref.t
module Networking (N : S.NETWORK) : VAT_NETWORK with
module Network = N
module Capnp_address = Capnp_address