| // 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) -> (); |
| }; |