-
Notifications
You must be signed in to change notification settings - Fork 1
2 of 5
It is helpful and effectiv to think of software as layered architecture. Each implementation is a layer. Each implementation bridges a gap. There is an API on one side. It provides services to layer above. That API describes features. It was created to implement our use cases. This API will be used e.g. by a (graphical) user interface.
On the other side, each implementation is based on other software the layer below. That is the other side. In our case, that software below is ASAPJava and SharkPKI.
Our implementation has to transform API calls into ASAP calls and vica versa. Our implementation must transform application data structures into ASAP structures and vice versa.
We defined our message datstructure with an interface: SharkMessage.
ASAP allows sending messages: sendASAPMessage. This methode requires three parameter: An application format an optional URI and a byte array that contains the actual content.
InMemoSharkMessage is the class that bridges that gap.
There is a static methode serializeMessage which produces a byte array which becomes the content of an ASAP message.
public static byte[] serializeMessage(byte[] content, CharSequence sender, Set<CharSequence> receiver,
boolean sign, boolean encrypt, ASAPKeyStore asapKeyStore)
throws IOException, ASAPException {
...
}
It takes all parameter which can be set with our API. It also provides a reference to an ASAPKeyStorage. This interface is provided by our SharkPKI component. Next section describes how we set up our and related components and from what source we have got that object reference.
The following code serializes all meta data of our Shark message.
// create a ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// serialize shark message content
ASAPSerialization.writeByteArray(content, baos);
// serialize shark message sender
ASAPSerialization.writeCharSequenceParameter(sender, baos);
// serialize shark message receiver
ASAPSerialization.writeCharSequenceSetParameter(receiver, baos);
// add a timestamp
Timestamp creationTime = new Timestamp(System.currentTimeMillis());
String timestampString = creationTime.toString();
ASAPSerialization.writeCharSequenceParameter(timestampString, baos);
// produce a byte array
content = baos.toByteArray();
We use our own serialization class. We propose to do the same. Variable content
contains the serialized Shark message. This content could be delivered as ASAP message.
User may want that message to be signed.
byte flags = 0;
if(sign) {
byte[] signature = ASAPCryptoAlgorithms.sign(content, asapKeyStore);
baos = new ByteArrayOutputStream();
ASAPSerialization.writeByteArray(content, baos); // message has three parts: content, sender, receiver
// append signature
ASAPSerialization.writeByteArray(signature, baos);
// attach signature to message
content = baos.toByteArray();
flags += SIGNED_MASK;
}
We use a byte variable to indicate if a message is e.g. signed. We add the signed flag in that case: flags += SIGNED_MASK
.
We sign the content. ASAPCryptoAlgorithms is already implemented in the core ASAPJava library. It provides a [signing method](ASAPCryptoAlgorithms.sign(content, ASAPKeyStore). There is video that gives an introducation in basic encryption algorithms including message signing and verifying.
Again we use a ByteArrayOutputStream to compile several parts into a single byte array. First, we write the content that was produced a few lines earlier. Second, we add the signature. Finally, we have got a new content
It now contains the serialized Shark message with its signature.
User may want that message to be encrypted.
if(encrypt) {
content = ASAPCryptoAlgorithms.produceEncryptedMessagePackage(
content, // complied Shark message (maybe with signature)
receiver.iterator().next(), // receiver
asapKeyStore);
flags += ENCRYPTED_MASK;
}
ASAPCryptoAlgorithms provides a method that creates a symmetric encyption key. That key is used to encrypt the content. This key is encrypted with receivers public key, see a video for details.
We set a flag to remember that our content
variable contains an encrypted message.
Finally, we produce the content of our ASAP message by adding the flags to the content.
// serialize Shark message
baos = new ByteArrayOutputStream();
ASAPSerialization.writeByteParameter(flags, baos);
ASAPSerialization.writeByteArray(content, baos);
return baos.toByteArray();
We write our flags and the content into a byte array. That array is returned and will serve as content of an ASAP message. Deserialization follows the same steps in reverse order, see parseMessage.
Sending a message is pretty simple thanks to our InMemoSharkMessage class.
The complete implementation can be found in SharkMessengerComponentImpl
public void sendSharkMessage(byte[] content, CharSequence uri,
Set<CharSequence> selectedRecipients, boolean sign,
boolean encrypt)
throws SharkMessengerException, IOException {
this.checkComponentRunning(); // throws an exception if component not yet running
//....
// serialize Shark message and send it as ASAP message
this.asapPeer.sendASAPMessage(SHARK_MESSENGER_FORMAT, uri,
InMemoSharkMessage.serializeMessage(
content,
this.asapPeer.getPeerID(),
selectedRecipients,
sign, encrypt,
this.sharkPKIComponent));
}
} catch (ASAPException e) {
throw new SharkMessengerException("...");
}
}
This methods throws an exception if this component is not yet initialized (see checkComponentRunning()
).
We call serializeMessage()
. Our Shark message is optionally signed and encrypted and finally compiled into a byte array. That byte array is used as content of our ASAP message. Our application format is a constant value (SHARK_MESSENGER_FORMAT
). Uri is parameter of this method.
this.asapPeer.sendASAPMessage
sends delegates message sending to our ASAP peer. Message sent.
There is no extra code that deals with an incoming messages. That might sound strange at first but it is not that weird.
ASAP peers store received messages. ASAP peer offer access to that ASAPStorage. It gives an easy way to access sent and received messages with a given URI.
That is what we need. We did not implement our own message storage. We use peer’s storage instead, namely the ASAPChannel.
How does it work? We can find the following method in our API.
SharkMessengerChannel getChannel(CharSequence uri) throws SharkMessengerException, IOException;
It gives access to a SharkMessengerChannel which in turn provides a SharkMessageList. Finally, this interface allows accessing Shark message by calling
SharkMessage getSharkMessage(int position, boolean chronologically)
Apparently, we had an Android Recycler View in mind when we defined and implemented that method. This class provides a convenient way to produce a list of – in that case – messages.
How do we use an ASAP storage for our messenger app. We start with the API method (full source code)
public SharkMessengerChannel getChannel(CharSequence uri) throws SharkMessengerException, IOException {
try {
ASAPStorage asapStorage =
this.asapPeer.getASAPStorage(SharkMessengerComponent.SHARK_MESSENGER_FORMAT);
ASAPChannel channel = asapStorage.getChannel(uri);
return new SharkMessengerChannelImpl(this.asapPeer, this.sharkPKIComponent, channel);
}
catch(ASAPException asapException) {
throw new SharkMessengerException(asapException);
}
}
First, we get access to peer's storage by calling getASAPStorage()
. That storage object allows us to produce an channel object by calling
getChannel(uri)
. Uri is parameter in our method. This method will be called if we are about to produce a list of messages of a given chat. A chat can be identified by a uri. That uri is known when we call this method.
Note: We can get a list of all uris within an ASAPStorage.
Now, we have got a channel - an object that provides a (sorted if we like) list of sent and received ASAP messages. Content of each ASAP message is a serialized Shark message. We know that because we produced it that way, see above. We use this knowledge in SharkMessengerChannelImpl.
public SharkMessengerChannelImpl(ASAPPeer asapPeer, SharkPKIComponent pkiComponent, ASAPChannel asapChannel) {
this.asapPeer = asapPeer;
this.pkiComponent = pkiComponent;
this.asapChannel = asapChannel;
}
Object of this class have access to our peer, our SharkPKI and to the specific ASAPChannel.
public SharkMessageList getMessages(boolean sentMessagesOnly, boolean ordered)
throws SharkMessengerException, IOException {
try {
return new SharkMessageListImpl(this.pkiComponent, this.asapChannel, sentMessagesOnly, ordered);
}
catch(ASAPException e) {
throw new SharkMessengerException(e.getLocalizedMessage(), e);
}
}
This method produces a SharkMessageList that wraps our ASAPChannel.
...
// a (sorted) list of sent (and received) asap message
private final ASAPMessages asapMessages;
...
public SharkMessageListImpl(SharkPKIComponent pkiComponent, ASAPChannel asapChannel,
boolean sentMessagesOnly, boolean ordered) throws IOException, ASAPException {
//...
this.asapMessages = asapChannel.getMessages(false);
}
That constructor has a few more lines. Please have a look in the sources.
We just want to point out that single line of code. We produce a list of asap messages. This list contains sent and received messages. Now, we need only a single step to finally get access to our Shark messages. Our SharkMessageListImpl provides that method:
public SharkMessage getSharkMessage(int position, boolean chronologically) throws SharkMessengerException {
try {
// get hop list - if your are interested in it
List<ASAPHop> hopsList = this.asapMessages.getChunk(position, chronologically).getASAPHopList();
// most important - get access to serialized Shark messenger message
byte[] content = this.asapMessages.getMessage(position, chronologically);
// produce a Shark Message object from serialized data.
return InMemoSharkMessage.parseMessage(content, hopsList, this.pkiComponent);
}
catch(ASAPException | IOException asapException) {
throw new SharkMessengerException(asapException);
}
}
The circle has closed again. We take the content of an ASAP message. That is our serialized Shark messenger message. Our InMemoSharkMessage provides a method to create a SharkMessage from a that byte array. The PKI is required to encrypt the message and verify a signature of necessary and provided.
- Project goals
- Step 0: Concept
- Step 1: API
- Step 2: Implementation
- Step 3: Shark Component
- Step 4: Testing
- Step 5: GUI
- Javadoc
- Shark Messenger User Guide
- How to use
- Command Page