Actor with document
A complete actor implementation suitable for most applications. It manages a document as private data and a list of actor group members, allows sending and receiving messages, and announces itself.
Creating an instance
KeyPair keyPair = KeyPair.from(record); // or KeyPair.generate() Store storageStore = new FolderStore("local:", "file:///path/to/local/store"); Store messagingStore = Store.fromUrl("https://condensation.io"); long autoSaveDelay = 200; ActorWithDocument actor = new ActorWithDocument(keyPair, storageStore, messagingStore, autoSaveDelay, new Delegate()); class Delegate implements ActorWithDocument.Delegate { ... };
var keyPair = cds.keyPairFromRecord(record); // or cds.createKeyPair() var storageStore = cds.storeFromUrl('https://condensation.io'); var messagingStore = cds.storeFromUrl('https://condensation.io'); var autoSaveDelay = 200; var actor = new cds.ActorWithDocument(keyPair, storageStore, messagingStore, entrustedAccounts, autoSaveDelay); actor.onReceiveMessage = function() { ... };
my $keyPair = CDS::KeyPair->fromRecord($record); # or CDS::KeyPair->generate() my $storageStore = cds.storeFromUrl('file:///path/to/local/store'); my $messagingStore = cds.storeFromUrl('https://condensation.io'); my $actor = CDS::ActorWithDocument->new($keyPair, $storageStore, $messagingStore, $entrustedAccounts, Delegate->new); package Delegate; sub onReceiveMessage { ... }
The key pair (public and private key) is used to sign and encrypt data. It should be stored safely on the device.
The storage store is used for storing private data. It may be a private store accessible to the actor only. Ideally, this should be a local (on-device) store with fast access.
The messaging store is used for sending and receiving messages, and must be publicly available.
Storage and messaging stores may be the same.
Entrusted accounts belong to trusted actors ("trustees"). All messages and private data can be decrypted by any entrusted actor. The list may be empty.
The auto save delay is used to auto-save the private data whenever it changes. A value of 0 disables automatic saving.
Actor identifier
Hash actorHash = actor.keyPair.publicKey.hash
var actorHash = actor.keyPair.publicKey.hash;
my $actorHash = $actor->keyPair->publicKey->hash;
The actor hash is a globally unique 32-byte identifier of this actor. It is derived from the public key.
In certain places, the identifier may be shortened to 16 bytes:
Bytes shortIdentifier = actorHash.bytes.slice(0, 16)
var shortIdentifier = actorHash.bytes.slice(0, 16);
my $shortIdentifier = substr($actorHash->bytes, 0, 16);
Identifiers shorter than 16 bytes (128 bits) are susceptible to spoofing, and should therefore not be used.
Actor group
The actor manages an actor group. Each actor is in one of the following states:
- An active actor intends to read messages every now and then, even if it is not available all the time.
- An idle actor still belongs to the group, but is not active any more.
- A revoked actor was part of the group in the past, but thrown out. Messages signed by this actor are not trusted any more.
The difference between active and idle actors is on their intention rather than their actual behavior. An active actor may become idle at any time, and vice versa.
Actors are revoked when their keys have been exposed (e.g. device loss), or potentially exposed. If it turns out that the key was not lost, a revoked actor may become active or idle again.
actor.setActive() actor.setIdle() actor.setRevoked() actor.isActive() actor.isIdle() actor.isRevoked()
TODO
TODO
ActorInGroup a = actor.actorInGroup.get(actorHash); if (a == null) { // Not part of the group return; } // Set the state a.setActive(); a.setIdle(); a.setRevoked(); // Obtain the state a.isActive() a.isIdle() a.isRevoked() // Messaging store a.store() // Selector in the document a.selector
TODO
TODO
To join a new actor to the group, the new actor and an active actor of the group may call:
ActorInGroup a = actor.join(accountWithKey);
TODO
TODO
boolean contains = actor.contains(actorHash);
var contains = actor.contains(actorHash);
my $contains = $actor->actor($actorHash);
Returns true if the actor belongs to this actor group, i.e. if there is an active or idle account with that hash.
Private data
Private data is stored as a document, and accessible through the root selector, e.g.:
Selector emailSelector = actor.root.child(BC.email);
var emailSelector = actor.root.childAsText('email');
my $emailSelector = actor->root->child('email');
See the selector documentation for details on how to read and modify data in a document.
actor.readPrivateData();
actor.readPrivateData();
$actor->readPrivateData();
Reads the private data by listing the private box, and merging any unmerged information.
Usually, this should be called once after actor creation. If multiple instances of the application are running at the same time, this may be called regularly to merge information stored by other instances.
actor.savePrivateData();
actor.savePrivateData();
$actor->savePrivateData();
Manually saves the private data if there are changes. This is not necessary when auto-saving is enabled.
Note that this only saves the main document. Sub documents must be saved independently.
Synchronization
Whenever the private data is saved, the actor sends (updates) a message to all active actors of the group.
Receiving messages
Message reading is invoked through one of the following calls:
actor.readInQueue(); actor.readActiveInQueues(); actor.readIdleInQueues();
actor.readInQueue(); actor.readActiveInQueues(); actor.readIdleInQueues();
$actor->readInQueue(); $actor->readActiveInQueues(); $actor->readIdleInQueues();
The first call reads the actor's own message box on the messaging store. This should be invoked regularly to process messages.
The second call reads the messages of all active actors of the group. It thereby "steals" messages of other actors that are addressed to the actor group rather than the actor itself.
The third call reads the messages of all idle actors of the group.
Whenever the actor receives a regular message, it calls onMessage:
// In the delegate void onMessage() { // Process the message }
actor.onMessage = function() { // Process the message }
# In the delegate sub onMessage { my ($) = @_; # Process the message }
Replies are stored in the private data, and not delivered through the delegate.
Sending messages
Messages are sent through message channels.
MessageChannel messageChannel = actor.createMessageChannel(recipient, validity, delegate); class Delegate implements MessageChannel.Delegate { public void onMessageChannelSent(@NonNull Source messageReference) { ... } public void onMessageChannelStoreError(@NonNull Store store, @NonNull String error) { ... } }
var messageChannel = actor.createMessageChannel(recipient, validity); messageChannel.onSent = function(messageReference) { ... }; messageChannel.onStoreError = function(store, error) { ... };
my $messageChannel = $actor->createMessageChannel($recipient, $validity);
Creates a message channel with a random label (16 bytes). This is primarily used to send single messages.
Recipient may be an actor group, or any other recipient instance.
Validity indicates how long the message is kept alive, i.e. how much time the recipient has to retrieve it.
MessageChannel messageChannel = actor.openMessageChannel(label, recipient, validity, delegate);
var messageChannel = actor.openMessageChannel(label, recipient, validity, delegate);
my $messageChannel = $actor->openMessageChannel($label, $recipient, $validity, $delegate);
Opens the message channel label.
When sending a message, the actor attaches the message content to the /sent messages subtree, and keeps it for a given amount of time.
To join a group, a new actor sends a message to an active actor of the group. The active actor then decides whether or not to accept and include the new actor. The application can define whether or not a
Document
The document managed by the actor has the following structure:
root actor group 94a683ee…5e // hash and store active // 'y' for active, unset for idle revoked // 'y' for revoked
Threading
Actor instances must always be accessed from the main thread.