blob: e393885d0b7d966b48b5be1b8927d6241087e0f7 [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.
/// Response code for ledger operations.
enum Status : int32 {
OK = 0;
PARTIAL_RESULT = 1;
INVALID_TOKEN = 2;
INVALID_ARGUMENT = 3;
PAGE_NOT_FOUND = 4;
KEY_NOT_FOUND = 5;
REFERENCE_NOT_FOUND = 6;
NEEDS_FETCH = 7;
IO_ERROR = 8;
NETWORK_ERROR = 9;
TRANSACTION_ALREADY_IN_PROGRESS = 10;
NO_TRANSACTION_IN_PROGRESS = 11;
INTERNAL_ERROR = 12;
VALUE_TOO_LARGE = 13;
ILLEGAL_STATE = 14;
UNKNOWN_ERROR = -1;
};
/// Size in bytes of Page IDs.
const uint32 kPageIdSize = 16;
struct PageId {
array<uint8>:kPageIdSize 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 |ledger.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]
interface ErrorNotifier {
1000: Sync() -> ();
};
[Discoverable]
interface Ledger {
/// Retrieves the page with the given identifier, creating it if needed. 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.
///
/// Returns OK and binds |page_request| to the page on success.
1: GetPage(PageId? id, request<Page> page_request) -> (Status status);
/// 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).
2: GetRootPage(request<Page> page_request) -> (Status status);
/// 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.
3: SetConflictResolverFactory(ConflictResolverFactory factory)
-> (Status status);
};
/// A reference to a value.
struct Reference {
vector<uint8> opaque_id;
};
/// A continuation token for paginated requests.
struct Token {
vector<uint8> 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.
interface Page {
/// Returns the identifier for the page.
1: 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|.
2: GetSnapshot(request<PageSnapshot> snapshot_request,
vector<uint8>:256 key_prefix, PageWatcher? watcher) -> (Status status);
// 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|.
3: Put(vector<uint8>:256 key, vector<uint8> value) -> (Status status);
4: PutWithPriority(vector<uint8>:256 key, vector<uint8> value,
Priority priority) -> (Status status);
5: PutReference(vector<uint8>:256 key, Reference reference, Priority priority)
-> (Status status);
6: Delete(vector<uint8>:256 key) -> (Status status);
// 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.
7: Clear() -> (Status status);
// 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.
8: CreateReferenceFromSocket(uint64 size, handle<socket> data)
-> (Status status, Reference? reference);
/// 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.
9: CreateReferenceFromBuffer(fuchsia.mem.Buffer buffer)
-> (Status status, Reference? reference);
// 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, commiting 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.
10: StartTransaction() -> (Status status);
11: Commit() -> (Status status);
12: Rollback() -> (Status status);
/// 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.
13: SetSyncStateWatcher(SyncWatcher watcher) -> (Status status);
// 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.
14: 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 {
vector<uint8>:256 key;
/// |value| is null and |size| is 0 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 {
vector<uint8> value;
};
/// A pair of key and an inlined value.
struct InlinedEntry {
vector<uint8>:256 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;
};
/// 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.
interface PageSnapshot {
/// 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, |status| will be
/// |OK| and |next_token| equal to NULL. Otherwise, |status| will be
/// |PARTIAL_RESULT| and |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. |status| will be |PARTIAL_RESULT| as long
/// as there are more results and |OK| 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|.
1: GetEntries(vector<uint8>:256 key_start, Token? token)
-> (Status status, vector<Entry> entries, Token? next_token);
/// Same as |GetEntries()|. |VALUE_TOO_LARGE| is returned if a value does not
/// fit in a FIDL message.
2: GetEntriesInline(vector<uint8>:256 key_start, Token? token)
-> (Status status, vector<InlinedEntry> 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. If the result fits in a single FIDL message, |status|
/// will be |OK| and |next_token| equal to NULL. Otherwise, |status| will be
/// |PARTIAL_RESULT| and |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.
/// The returned |keys| are sorted. |status| will be |PARTIAL_RESULT| as long
/// as there are more results and |OK| once finished.
3: GetKeys(vector<uint8>:256 key_start, Token? token)
-> (Status status, vector<vector<uint8>:256> 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.
4: Get(vector<uint8>:256 key) -> (Status status, fuchsia.mem.Buffer? buffer);
/// Returns the value of a given key if it fits in a FIDL message.
/// |VALUE_TOO_LARGE| is returned if the value does not fit in a FIDL message.
/// See |Get()| for additional information.
5: GetInline(vector<uint8>:256 key) -> (Status status, InlinedValue? value);
/// 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).
6: Fetch(vector<uint8>:256 key) -> (Status status, fuchsia.mem.Buffer? buffer);
/// 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.
7: FetchPartial(vector<uint8>:256 key, int64 offset, int64 max_size)
-> (Status status, fuchsia.mem.Buffer? buffer);
};
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> changed_entries;
/// List of deleted keys, in sorted order.
vector<vector<uint8>:256> 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.
interface 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.
1: 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.
interface ConflictResolverFactory {
/// Returns the conflict resolution policy for the given page.
1: 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|.
2: 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 {
vector<uint8> bytes;
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 {
vector<uint8>:256 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 {
vector<uint8>:256 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;
};
/// Status for methods that return a sequence of values.
enum IterationStatus : int8 {
/// The iteration has finished.
OK = 0;
/// The iteration has not finished. The method must be called again to
/// retrieve the next batch of responses.
PARTIAL_RESULT = 1;
};
/// 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.
interface MergeResultProvider : ErrorNotifier {
/// |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, |status| will
/// be |PARTIAL_RESULT| and |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.
1: GetFullDiff(Token? token)
-> (Status status, vector<DiffEntry> changes, Token? next_token);
1001: GetFullDiffNew(Token? token)
-> (IterationStatus status, vector<DiffEntry> 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.
2: GetConflictingDiff(Token? token)
-> (Status status, vector<DiffEntry> changes, Token? next_token);
1002: GetConflictingDiffNew(Token? token)
-> (IterationStatus status, vector<DiffEntry> 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.
3: Merge(vector<MergedValue> merge_changes) -> (Status status);
1003: MergeNew(vector<MergedValue> 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.
4: MergeNonConflictingEntries() -> (Status status);
1004: MergeNonConflictingEntriesNew();
/// Marks the end of merge changes to resolve this conflict. After |Done()| is
/// called |MergeResultProvider| interface cannot be used any more.
5: Done() -> (Status status);
1005: DoneNew();
};
/// 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.
interface 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.
1: 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.
interface SyncWatcher {
1: SyncStateChanged(SyncState download_status, SyncState upload_status) -> ();
};