blob: 780bda8bd79f64a4990cf92f381f64ae8bb4baef [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library fuchsia.ledger;
using fuchsia.mem;
using zx;
// This file contains definitions of interfaces and data structures to access
// the Fuchsia Ledger.
/// Type of the keys in a ledger Page.
using Key = bytes:256;
/// Size in bytes of Page IDs.
const uint32 PAGE_ID_SIZE = 16;
/// Type of the identifier of a ledger Page.
struct PageId {
array<uint8>:PAGE_ID_SIZE id;
};
/// Base interface for all services offered by the Ledger.
///
/// In case of an unexpected error on one of the service offered by the Ledger,
/// the service will close the connected channel, sending a `zx.status` as an epitaph.
///
/// If a client needs to ensure a sequence of operation has been processed by
/// the service, it can issue a `Sync` command. The response will be send once
/// all request started before the `Sync` request has been processed by the
/// service.
[FragileBase]
protocol Syncable {
Sync() -> ();
};
[Discoverable]
protocol Ledger {
compose Syncable;
/// Retrieves the page with the given identifier, creating it if needed, and binds
/// `page_request` to it. A `null` identifier can be passed to create a new page with a random
/// unique identifier. It is allowed to connect to the same page concurrently multiple times.
///
/// Parameters:
/// `id` the identifier of the page, or `null` to create a new page with a random identifier.
GetPage(PageId? id, request<Page> page_request);
/// Gets the page with identifier
/// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].
/// This is a convenience method equivalent to:
/// GetPage([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], page_request).
GetRootPage(request<Page> page_request);
/// Sets the `ConflictResolverFactory` to use for resolving conflicts on pages.
/// If this method has never been called, a last-one-wins policy will be used
/// for every page. If this method is called multiple times, the factories are
/// kept and conflict resolver requests are sent to one of the connected
/// factories. If all factories are later disconnected, pages that already have
/// a conflict resolution strategy (because they were opened before the factory
/// disconnected) will continue using their current strategy. The pages for
/// which no conflict resolution is set up will not get their conflicts
/// resolved until this method is called again.
SetConflictResolverFactory(ConflictResolverFactory factory);
};
/// A reference to a value.
struct Reference {
bytes opaque_id;
};
/// A continuation token for paginated requests.
struct Token {
bytes opaque_id;
};
/// The result of a wait for conflict resolution. See
/// `Page.WaitForConflictResolution` for details.
enum ConflictResolutionWaitStatus {
/// No conflict was observed when the callback was registered.
NO_CONFLICTS = 0;
/// Some conflicts were observed when the callback was registered, and all
/// have been resolved.
CONFLICTS_RESOLVED = 1;
};
/// A page is the smallest unit of syncable data.
protocol Page {
compose Syncable;
/// Returns the identifier for the page.
GetId() -> (PageId id);
/// Creates a snapshot of the page, allowing the client app to read a
/// consistent view of the content of the page. If `key_prefix` is provided,
/// the resulting snapshot includes only the entries with matching keys.
///
/// If `watcher` is provided, it will receive notifications for changes of the
/// page state on this page connection newer than the resulting snapshot.
/// Change notifications will only contain the entries matching `key_prefix`.
/// To receive all changes, use an empty `key_prefix`.
GetSnapshot(request<PageSnapshot> snapshot_request,
Key key_prefix, PageWatcher? watcher);
// Mutation operations.
// Key level operations.
/// Mutations are bundled together into atomic commits. If a transaction is in
/// progress, the list of mutations bundled together is tied to the current
/// transaction. If no transaction is in progress, mutations will be bundled
/// with the following rules:
/// - A call to either `GetSnapshot()` or `StartTransaction()` will
/// commit any pending mutations.
/// - All pending mutations will regularly be bundled together and committed.
/// They are guaranteed to be persisted as soon as the client receives a
/// successful status.
/// `Put()` and `PutWithPriority()` can be used for small values that
/// fit inside a FIDL message. If the value is bigger, a reference must be
/// first created using `CreateReferenceFromSocket()` or
/// `CreateReferenceFromBuffer()` and then `PutReference()` can be used.
/// `PutWithPriority()` and `PutReference()` have an additional `priority`
/// parameter managing the synchronization policy for this value. `Put()` uses
/// a default priority of `Priority.EAGER`. For the list of available
/// priorities and their definition, see `Priority`.
Put(Key key, bytes value);
PutWithPriority(Key key, bytes value,
Priority priority);
PutReference(Key key, Reference reference, Priority priority);
Delete(Key key);
// Page level operations.
/// Deletes all entries in the page.
/// Outside of a transaction, this operation is equivalent to deleting every
/// key currently present on the page in a single transaction.
/// In a transaction, this operation is equivalent to deleting every key
/// present in the page before the transaction, as well as any new key added
/// since the transaction started.
Clear();
// References.
/// Creates a new reference. The object is not part of any commit. It must be
/// associated with a key using `PutReference()`. The content of the reference
/// will be the content of the socket. The content size must be equal to
/// `size`, otherwise the call will fail.
CreateReferenceFromSocket(uint64 size, handle<socket> data)
-> (Reference reference) error zx.status;
/// Creates a new reference. The object is not part of any commit. It must be
/// associated with a key using `PutReference()`. The content of the reference
/// will be the content of the buffer.
CreateReferenceFromBuffer(fuchsia.mem.Buffer buffer)
-> (Reference reference) error zx.status;
// Transactions.
/// Transactions allow the client to ensures changes are seen atomically by
/// observers of this page. Once a transaction is started with
/// `StartTransaction()`, every call to `Put(...)` and `Delete(...)` will not
/// be visible until either `Commit()` is called, and all changes are applied
/// in a single commit, or `Rollback()` is called and all changes are
/// discarded.
///
/// Parallel transactions on the same *page connection* are not allowed, and
/// calling `StartTransaction()` when a transaction is already in progress
/// returns an error. However, a client is free to connect to the same page
/// multiple times, and run parallel transactions on the same page using
/// separate connections. In this case, committing each transaction creates
/// divergent commits, which are later subject to conflict resolution.
///
/// When a transaction is in progress, the page content visible *on this page
/// connection* is pinned to the state from when `StartTransaction()` was
/// called. In particular, no watch notifications are delivered, and the
/// conflict resolution is not invoked while the transaction is in progress. If
/// conflicting changes are made or synced while the transaction is in
/// progress, conflict resolution is invoked after the transaction is
/// committed.
///
/// Starting a transaction will block until all watchers registered on this
/// page connection have received the current page state, ie. the one that
/// will be used as the base of the transaction. Put (with all its variants)
/// and Delete calls may be pipelined while StartTransaction() is pending and
/// will be taken into account in the transaction while it is pending.
StartTransaction();
Commit();
Rollback();
/// Sets a watcher to track the synchronisation state of this page. The
/// current state is immediately sent to the watcher when this method is
/// called.
SetSyncStateWatcher(SyncWatcher watcher);
// Conflict resolution.
/// Waits until all conflicts are resolved before calling the callback.
/// The client can call this method multiple times, even before the previous
/// calls are completed. Callbacks will be executed in the order they were
/// added and indicate whether a merge happened between the callback
/// registration and its execution.
/// If there are no pending conflicts at the time this is called, the callback
/// gets executed right away.
WaitForConflictResolution() -> (ConflictResolutionWaitStatus wait_status);
};
/// The synchronization priority of a reference.
enum Priority {
/// EAGER values will be downloaded with the commit and have the same
/// availability.
EAGER = 0;
/// LAZY values will not be downloaded with their commit, but only on demand.
/// A LAZY value thus may not be available when requested, for example if the
/// device has no internet connection at request time.
LAZY = 1;
};
/// A pair of key and value.
struct Entry {
Key key;
/// `value` is null if the value requested has the LAZY priority and is not
/// present on the device. Clients must use a Fetch call to retrieve the
/// contents.
fuchsia.mem.Buffer? value;
Priority priority;
};
/// A value inlined in a message.
struct InlinedValue {
bytes value;
};
/// A pair of key and an inlined value.
struct InlinedEntry {
Key key;
/// `value` is null if the value requested has the LAZY priority and is not
/// present on the device. Clients must use a Fetch call to retrieve the
/// contents.
InlinedValue? inlined_value;
Priority priority;
};
/// Error returned by the different PageSnapshot methods.
enum Error {
KEY_NOT_FOUND = 1;
NEEDS_FETCH = 2;
NETWORK_ERROR = 3;
};
/// The content of a page at a given time. Closing the connection to a `Page`
/// interface closes all `PageSnapshot` interfaces it created. The contents
/// provided by this interface are limited to the prefix provided to the
/// Page.GetSnapshot() call.
protocol PageSnapshot {
compose Syncable;
/// Returns the entries in the page with keys greater or equal to `key_start`
/// using the lexicographic order. If `key_start` is empty, all entries are
/// returned. If the result fits in a single fidl message, `next_token` will
/// be equal to NULL. Otherwise, `next_token` will have a non-NULL value. To
/// retrieve the remaining results, another call to `GetEntries` should be
/// made, initializing the optional `token` argument with the value of
/// `next_token` returned in the previous call. `next_token` will not be
/// NULL as long as there are more results and NULL once finished.
/// Only `EAGER` values are guaranteed to be returned inside `entries`.
/// Missing `LAZY` values can be retrieved over the network using Fetch().
/// The returned `entries` are sorted by `key`.
GetEntries(Key key_start, Token? token)
-> (vector<Entry>:MAX entries, Token? next_token);
/// Same as `GetEntries()`. The connection will be closed with a
/// `ZX_ERR_BAD_STATE` epitaph if a value does not fit in a FIDL message.
[Deprecated]
GetEntriesInline(Key key_start, Token? token)
-> (vector<InlinedEntry>:MAX entries, Token? next_token);
/// Returns the keys of all entries in the page which are greater or equal to
/// `key_start` using the lexicographic order. If `key_start` is empty, all
/// keys are returned. The returned `keys` are sorted. If the result fits in
/// a single fidl message, `next_token` will be equal to NULL. Otherwise,
/// `next_token` will have a non-NULL value. To retrieve the remaining
/// results, another call to `GetKeys` should be made, initializing the
/// optional `token` argument with the value of `next_token` returned in the
/// previous call. `next_token` will not be NULL as long as there are more
/// results and NULL once finished.
GetKeys(Key key_start, Token? token)
-> (vector<Key>:MAX keys, Token? next_token);
/// Returns the value of a given key.
/// Only `EAGER` values are guaranteed to be returned. Calls when the value is
/// `LAZY` and not available will return a `NEEDS_FETCH` status. The value can
/// be retrieved over the network using a Fetch() call.
Get(Key key) -> (fuchsia.mem.Buffer buffer) error Error;
/// Returns the value of a given key if it fits in a FIDL message.
/// The connection will be closed with a `ZX_ERR_BAD_STATE` epitaph if the
/// value does not fit in a FIDL message.
/// See `Get()` for additional information.
GetInline(Key key) -> (InlinedValue value) error Error;
/// Fetches the value of a given key, over the network if not already present
/// locally. `NETWORK_ERROR` is returned if the download fails (e.g.: network
/// is not available).
Fetch(Key key) -> (fuchsia.mem.Buffer buffer) error Error;
/// Fetches the value of a given key, over the network if not already present
/// locally, and returns a shared handle of a part of the value of a given
/// key, starting at the position that is specified by `offset`. If `offset`
/// is less than 0, starts at `-offset` from the end of the value.
/// Returns at most `max_size` bytes. If `max_size` is less than 0, returns
/// everything.
FetchPartial(Key key, int64 offset, int64 max_size)
-> (fuchsia.mem.Buffer buffer) error Error;
};
enum ResultState {
COMPLETED = 0;
PARTIAL_STARTED = 1;
PARTIAL_CONTINUED = 2;
PARTIAL_COMPLETED = 3;
};
struct PageChange {
/// The timestamp of this change. This represents the number of nanoseconds
/// since Unix epoch (i.e., since "1970-01-01 00:00 UTC", ignoring leap
/// seconds). This value is set by the device that created the change and is
/// not synchronized across devices. In particular, there is no guarantee that
/// the `timestamp` of a follow up change is greater than this one's.
zx.time timestamp;
/// List of new and modified entries. `changed_entries` are sorted by `key`.
vector<Entry>:MAX changed_entries;
/// List of deleted keys, in sorted order.
vector<Key>:MAX deleted_keys;
};
/// Interface to watch changes to a page. The client will receive changes made by
/// itself, as well as other clients or synced from other devices. The contents
/// of a transaction will never be split across multiple OnChange() calls, but
/// the contents of multiple transactions may be merged into one OnChange() call.
protocol PageWatcher {
/// Called for changes made on the page. If the result fits in a single fidl
/// message, `result_state` will be `COMPLETED`. Otherwise, OnChange will be
/// called multiple times and `result_state` will be `PARTIAL_STARTED` the
/// first time, `PARTIAL_CONTINUED` the following ones and finally
/// `PARTIAL_COMPLETED` on the last call. No new OnChange() call will be made
/// while the previous one is still active. If clients are interested in the
/// full content of the page at the time of the change, they can request a
/// PageSnapshot in the callback. This request is optional and can be requested
/// in any partial (started, continued or completed) and/or COMPLETED OnChange
/// call. In any case, all requests made on a sequence of OnChange calls for
/// the same page change, will always return the same snapshot: the one
/// including all changes.
///
/// Note that calls to Page.StartTransaction() on the page connection on which
/// the watcher was registered will block until all OnChange() calls have
/// finished.
OnChange(PageChange page_change, ResultState result_state)
-> (request<PageSnapshot>? snapshot);
};
/// This interface lets clients control the conflict resolution policy of the
/// ledger. It allows them to either use pre-defined policies, or provide their
/// own implementation. This can be decided on a page-by-page basis.
protocol ConflictResolverFactory {
/// Returns the conflict resolution policy for the given page.
GetPolicy(PageId page_id) -> (MergePolicy policy);
/// Returns a `ConflictResolver` to use for the given page. This will only be
/// called if `GetPolicy` for the same page returned `AUTOMATIC_WITH_FALLBACK`
/// or `CUSTOM`.
NewConflictResolver(PageId page_id, request<ConflictResolver> resolver);
};
/// Strategy to be used when resolving conflicts.
enum MergePolicy {
/// Last one wins. When 2 commits are merged, the resulting commit contains:
/// - all keys/values that do not conflict
/// - all keys/values of the commit with the biggest timestamp (or biggest
/// id, if the timestamps are the same)
LAST_ONE_WINS = 0;
/// Commits are automatically merged when no key has been modified on both
/// sides. When a key has been modified by both commits, conflict resolution is
/// delegated to a user-provided `ConflictResolver` that is created by calling
/// `ConflictResolverFactory.NewConflictResolver`. A single `ConflictResolver`
/// is created for each page. When the `ConflictResolver` is disconnected, a
/// new one is requested.
AUTOMATIC_WITH_FALLBACK = 1;
/// All merges are resolved by a user-provided `ConflictResolver` as described
/// above, even when commits to be merged change a disjoined set of keys.
CUSTOM = 2;
};
/// A value that is either small enough to be directly embedded in `bytes` or
/// that is referenced by `reference`.
union BytesOrReference {
1: bytes bytes;
2: Reference reference;
};
/// Source of the value used to resolve a conflict.
///
/// `DELETE` deletes the key; `NEW` creates a new value; `RIGHT`
/// selects the value from the right branch. If no value is sent, the left
/// branch is selected.
/// Used by `MergedValue`.
enum ValueSource {
RIGHT = 0;
NEW = 1;
DELETE = 2;
};
/// A change in the page. If `source` is set to `NEW`, `new_value` must be set
/// to the new value. If `source` is not `NEW`, `new_value` and `priority` are
/// ignored.
struct MergedValue {
Key key;
ValueSource source;
BytesOrReference? new_value;
Priority priority;
};
/// An entry in a diff, as returned by `MergeResultProvider`.
///
/// If `base`, `left` or `right` are NULL, this means that the corresponding key
/// was not present in the base, left or right (respectively) branch of the
/// page.
struct DiffEntry {
Key key;
Value? base;
Value? left;
Value? right;
};
/// A value in a DiffEntry.
///
/// If the value is LAZY and is not present locally, `value` will be NULL. The
/// value can be retrieved using a `Fetch()` call on a corresponding snapshot.
struct Value {
fuchsia.mem.Buffer? value;
Priority priority;
};
/// A merge result provider, obtained from `ConflictResolver.Resolve()`. Can be
/// used to retrieve data about the conflict, and provide the merge result. When
/// all changes have been sent, `Done()` should be called to mark the end of
/// incoming merge changes.
protocol MergeResultProvider {
compose Syncable;
/// `GetFullDiff` returns the set of all key/value pairs (entries) that
/// have been modified between the common ancestor (see
/// `ConflictResolver.Resolve()`) and the left and right branches.
///
/// Values of `LAZY` keys may not be present on the device. In that case, the
/// corresponding Value objects within DiffEntry will have a NULL `value`
/// field. If needed, `left` and `right`, provided by the
/// `ConflictResolver.Resolve()` method can be used by clients to Fetch these
/// values. If a key is not present at all in one of the branches, its
/// corresponding Value object will be NULL.
///
/// The first call to get the `DiffEntry`s should be done using a NULL
/// token. If the result does not fit in a single fidl message, `next_token`
/// will have a non-NULL value, which can be used to retrieve the rest of
/// the results by calling `GetFullDiff()` with that token.
GetFullDiff(Token? token)
-> (vector<DiffEntry>:MAX changes, Token? next_token);
/// `GetConflictingDiff` returns the set of all key/value pairs that were
/// modified on both sides to different values, or deleted on one side and
/// modified on the other.
///
/// It behaves like `GetFullDiff` otherwise.
[Deprecated]
GetConflictingDiff(Token? token)
-> (vector<DiffEntry>:MAX changes, Token? next_token);
/// Once the result of the merge has been computed `Merge()` can be called with
/// all changes that resolve this conflict. If the result does not fit in a
/// single fidl message, `Merge()` can be called multiple times. If any of the
/// `Merge()` calls fails, i.e. `status` is not `OK`, all following calls will
/// fail with the same error.
///
/// If a key/value pair is sent multiple times in one or several `Merge()`
/// call, only the last pair is taken into account.
///
/// For all keys for which no merged value has been set (either here or
/// through `MergeNonConflictingEntries()` below), the left value will be
/// used. It is thus not necessary to send a MergedValue with a `LEFT` value
/// source, unless to overwrite a previous MergedValue.
Merge(vector<MergedValue>:MAX merge_changes);
/// Automatically merges all non conflicting entries (entries that are
/// modified on one side only or identical on both sides). This is equivalent
/// to sending, through `Merge()`, a MergedValue with a `RIGHT` ValueSource
/// for all non-conflicting keys modified on the right side. Conflicting
/// entries can still be merged using the `Merge()` method.
MergeNonConflictingEntries();
/// Marks the end of merge changes to resolve this conflict. After `Done()` is
/// called `MergeResultProvider` interface cannot be used any more.
Done();
};
/// Custom conflict resolver. If a `ConflictResolverFactory` is registered, and
/// `ConflictResolverFactory.GetPolicy()` returns `AUTOMATIC_WITH_FALLBACK` or
/// `CUSTOM` when called for a given page, the `NewConflictResolver` method will
/// be called and will provide a `ConflictResolver`. Each time a custom conflict
/// resolution is needed according to the chosen policy, the method
/// `ConflictResolver.Resolve()` will be called, and the client will resolve the
/// conflict by returning the final value for all conflicting keys as well as
/// values for any other key that the client wants to change.
protocol ConflictResolver {
/// Method called when a conflict needs to be resolved. `left` and `right`
/// contain the snapshots of the two branches and `common_version` that of the
/// lowest common ancestor. `common_version` can be NULL if this version is no
/// longer available. The result of the merge can be given through the
/// `result_provider`, using the left branch as the base of the merge commit,
/// i.e. only key/value pairs that are different from the left version of the
/// page should be sent. `result_provider` can also be used to retrieve the set
/// of differences, i.e. conflicting keys, between the two versions.
Resolve(PageSnapshot left, PageSnapshot right, PageSnapshot? common_version,
MergeResultProvider new_result_provider);
};
/// Synchronization state.
enum SyncState {
/// There are no pending operations.
IDLE = 0;
/// There are pending operations, but there is no syncing in progress. This
/// could be because of (possibly a combination of):
/// - waiting for better connectivity
/// - waiting due to internal policies (e.g. batching network requests,
/// waiting for a merge to happen before uploading)
/// - waiting to determine if synchronization is needed (e.g. during initial
/// setup)
PENDING = 1;
/// Synchronization is in progress.
IN_PROGRESS = 2;
/// An internal error occurred while trying to sync.
ERROR = 3;
};
/// Watcher interface to be implemented by clients who wish to follow the
/// synchronization status of their ledger. SyncStateChanged callback must be
/// called for new state change calls to be sent.
protocol SyncWatcher {
SyncStateChanged(SyncState download_status, SyncState upload_status) -> ();
};