blob: 24454dd02082b7c036defaa032cad226924b0a5a [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;
// 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;
};
[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 |factory| is NULL or this method is not called, a global last one wins
// policy will be used. If a factory is set and later on disconnected, pages
// for which no conflict resolution is set up will not get their conflict
// 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.
int64 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;
};
// 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 {
// |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);
// |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);
// 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.
//
// 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);
// 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);
// 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);
};
// 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) -> ();
};