APIActor with data tree

Actor with data tree

A complete actor implementation suitable for most applications. It manages a data tree 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;
		ActorWithDataTree actor = new ActorWithDataTree(keyPair, storageStore, messagingStore, autoSaveDelay, new Delegate());

		class Delegate implements ActorWithDataTree.Delegate {
			...
		};
	
		var keyPair = cn.keyPairFromRecord(record);   // or cn.createKeyPair()
		var storageStore = cn.storeFromUrl('https://condensation.io');
		var messagingStore = cn.storeFromUrl('https://condensation.io');
		var autoSaveDelay = 200;
		var actor = new cn.ActorWithDataTree(keyPair, storageStore, messagingStore, entrustedAccounts, autoSaveDelay);

		actor.onReceiveMessage = function() { ... };
	
		my $keyPair = CN::KeyPair->fromRecord($record);   # or CN::KeyPair->generate()
		my $storageStore = cn.storeFromUrl('file:///path/to/local/store');
		my $messagingStore = cn.storeFromUrl('https://condensation.io');
		my $actor = CN::ActorWithDataTree->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 data tree
		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 data tree, 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 data tree.

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 data tree. Sub data trees 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 in-queue on the messaging store. This should be invoked regularly to process messages.

The second call reads the in-queues 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 in-queues 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

Data tree

The data tree 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