-
Notifications
You must be signed in to change notification settings - Fork 1
Developing Data Vault Applications
This page outlines a possible process for developing a data vault social-network-like application called Fairnet, through which users can share their list of posts. At this stage we're only interested in exchanging structured data (not files) and we're not concerned about multi-device synchronisation.
The development process would start by defining a model of our application using a domain-specific language. At this stage, I'm repurposing Ecore/Emfatic for this as it offers all the features I seem to need until now. Every application model needs to contain a User class which describes the data in the user vault and the services offered by each user of the system. In our case (Fairnet), a user has a name, a list of friends and a list of posts. Public posts can be retrieved by any Fairnet user.
package fairnet;
// Annotation indicating the vault entity in the model
@datavault
class User {
// The contents of the vault
attr String name;
val Friend[*] friends;
val Post[*] posts;
// The services offered by the each vault node
op addFriend() : Boolean; // We don't need to model user2
@public // might be cached to improve performance
op getPosts() : Post[*] // Sends posts without bodies
op getPost(String id) : Post // Sends the full post
}
// Other classes represent data structures (no services/ops allowed)
class Friend {
attr String id;
attr String name;
attr String publicKey;
}
class Post {
attr String id;
attr String title;
attr String body;
attr boolean public;
}From this model we will generate (most of) the Fairnet API in Java which developers will need to complement with the behaviour of the services of the User class. Before we go to the generated/handwritten code that comprises the API, below is an overview of how it would be used.
// The name Fairnet is derived by upper-casing the name of the model (package "fairnet")
Fairnet fairnet = new Fairnet("localhost:7788" /*The address of the ActiveMQ broker*/);
// user1.db is a file/database containing all the user's friends and posts
// as well as the user's name (as per the model above), id (public key)
// and private key (which are not modelled as they are common in all applications).
// It can be an XMI document, a change-based (CBP) document etc.
User user1 = new User(new File("user1.db"));
user1.register(fairnet); // Announces user1 to fairnet and starts listening to the message broker
User user2 = new User("user2.db");
user2.register(fairnet);
// Emulates a remote user, who is in fact just our local user2
// and who we will be querying on behalf of user1
RemoteUser remoteUser = new RemoteUser(fairnet, "user2-public-key", user1);
user1.setAddFriendResponseHandler(new AddFriendResponseHandler() {
public void run(User me, RemoteUser other, boolean result) {
// Runs when user2 has responded to user1's friend request (positively or negatively)
if (result == true) { // Positive response, we can now get the other user's posts
other.getPosts();
}
}
});
user1.setGetPostsResponseHandler(new GetPostsResponseHandler() {
public void run(User me, RemoteUser other, List<Post> results) {
// Runs when user2 has responded with their list of posts to user1's request
}
});
remoteUser.addFriend();We now go into the different classes that comprise the API demonstrated above. Of these classes, only User needs to be hand-written to specify the behaviour of the 3 service methods (addFriend, getPosts, getPost).
public class User extends UserBase { // UserBase is generated
@Override
public boolean addFriend(User requester) throws Exception {
if (requester.getName().equals("Tom")) {
friends.add(new Friend(requester.getId(),
requester.getName(),
requester.getPublicKey()));
return true;
}
else {
return false;
}
}
@Override
public List<Post> getPosts() throws Exception {
return posts;
}
@Override
public Post getPost(User requester, String id) throws Exception {
Post post = posts.selectOne(p|p.id = id); // Switching to EOL for convenience
if (isFriend(requester)) {
if (post == null) throw new NotFoundException();
else return post;
}
else {
if (post != null && post.isPublic()) return post;
else throw new PermissionDeniedException();
}
}
public void isFriend(User other) {
return friends.exists(f|f.id == other.id);
}
}public abstract class UserBase {
// + setters and getters
protected AddFriendResponseHandler addFriendResponseHandler;
protected GetPostsResponseHandler getPostsResponseHandler;
protected GetPostResponseHandler getPostResponseHandler;
// Method with generated body
public void register(Fairnet fairnet) {
// Listen to the message broker
// When a request e.g. addFriend message is received, call the addFriend
// method, get its result, encrypt it using the public key of the
// requester and send a response message to the message broker
// When a response message is received, decrypt it using the user's own
// private key and call the respective response handler (e.g. addFriendResponseHandler)
}
// These are left abstract for the developer to fill in with
// behaviour in a subclass (i.e. User.java above)
public abstract boolean addFriend(User requester) throws Exception;
public abstract List<Post> getPosts() throws Exception;
public abstract Post getPost(User requester, String id) throws Exception;
}public class RemoteUser {
protected User requester;
protected Fairnet fairnet;
protected String publicKey;
public void addFriend() {
// set-up an addFriend message
// encrypt it using the public key
// and send it to the message broker
}
public void getPosts() {
// ... as above
}
public void getPost(String id) {
// ... as above
}
}- A malicious user can flood the message broker with requests for the posts of a user, pretending to be one of their friends (which is possible since the public keys of all users are known). Although the malicious user cannot decrypt the responses, this can inundate the user's device and delay responses to legitimate requests (a form of DoS).
- To avoid this we could add username/password authentication to the system at the level of the message broker
- All request/response messages having to go through the message broker can be quite demanding resource-wise for the broker (we wish to keep the load to the broker as low as possible)
- The broker can be just used as a directory of IP addresses (?) for data-vault-hosting devices and communication can then happen through sockets (?) in a peer-to-peer manner
- None of these issues invalidate the model-based development process; we just need to generate more complex code in UserBase and RemoteUser.
- Alice receives a request R1 from Bob. To verify that R1 comes from Bob, Alice can send Bob a message to confirm that R1 originates from him. Only when Bob responds affirmatively (see below on how we can ensure the authenticity of responses) will Alice consider the original request.
- Alice makes a request R1 to Bob and a few moments later she receives a response R2 from Bob. To ensure that R2 originates from Bob, in the request message Alice can include a random request ID R1ID (which will be encrypted with the rest of the message), which Bob will need to include in his response. Another possible option is that Bob first encrypts R2 with his own private key, before encrypting it with Alice's public key. That way, Alice would know that Bob created the message, as (ideally) he is the only owner of his private key.