APIActor with document

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:

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.

See also