Decentralized Instant Messaging (Java SDK)
Dependencies
build.gradle
allprojects {
repositories {
jcenter()
mavenCentral()
}
}
dependencies {
implementation group: 'chat.dim', name: 'SDK', version: '0.2.2'
}
pom.xml
<dependencies>
<dependency>
<groupId>chat.dim</groupId>
<artifactId>SDK</artifactId>
<version>0.2.2</version>
<type>pom</type>
</dependency>
</dependencies>
Account
User private key, ID, meta, and profile are generated in client,
and broadcast only meta
& profile
onto DIM station.
Register User Account
Step 1. generate private key (with asymmetric algorithm)
PrivateKey privateKey = PrivateKey.generate(PrivateKey.RSA);
NOTICE: After registered, the client should save the private key in secret storage.
Step 2. generate meta with private key (and meta seed)
String seed = "username";
Meta meta = Meta.generate(MetaType.Default, privateKey, seed);
Step 3. generate ID with meta (and network type)
ID identifier = meta.generateID(NetworkType.Main);
Create and upload User Profile
Step 4. create profile with ID and sign with private key
UserProfile profile = new UserProfile(identifier);
profile.setName("Albert Moky");
profile.setAvatar("https://secure.gravatar.com/avatar/34aa0026f924d017dcc7a771f495c086");
profile.sign(privateKey);
Step 5. send meta & profile to station
Messenger messenger = Messenger.getInstance();
Command cmd = new ProfileCommand(identifier, meta, profile);
messenger.sendCommand(cmd);
The profile should be sent to station after connected
and handshake accepted, details are provided in later chapters
Connect and Handshake
Step 1. connect to DIM station (TCP)
Step 2. prepare for receiving message data package
public void onReceive(byte[] responseData) {
byte[] response = messenger.onReceivePackage(responseData);
if (response != null && response.length > 0) {
send(response);
}
}
Step 3. send first handshake command
(1) create handshake command
String sessionKey = null;
Command cmd = new HandshakeCommand(sessionKey);
(2) pack, encrypt and sign
InstantMessage iMsg = new InstantMessage(cmd, userId, stationId);
SecureMessage sMsg = messenger.encryptMessage(iMsg);
ReliableMessage rMsg = messenger.signMessage(sMsg);
(3) Meta protocol
Attaching meta in the first message package is to make sure the station can find it,
particularly when it's first time the user connect to this station.
rMsg.setMeta(user.getMeta());
(4) send out serialized message data package
byte[] data = messenger.serializeMessage(rMsg);
send(data);
Step 4. waiting handshake response
The CPU (Command Processing Units) will catch the handshake command response from station, and CPU will process them automatically, so just wait untill handshake success or network error.
Message
Content
Content content = new TextContent("Hey, girl!");
Content content = new ImageContent(imageData, "image.png");
Content content = new AudioContent(voiceData, "voice.mp3");
NOTICE: file message content (Image, Audio, Video)
will be sent out only includes the filename and a URL
where the file data (encrypted with the same symmetric key) be stored.
Command
- Query meta with contact ID
Command cmd = new MetaCommand(identifier);
- Query profile with contact ID
Command cmd = new ProfileCommand(identifier);
Send command
public class Messenger extends chat.dim.Messenger {
public boolean sendCommand(Command cmd) {
assert server != null;
return sendContent(cmd, server.identifier);
}
}
MetaCommand or ProfileCommand with only ID means querying, and the CPUs will catch and process all the response automatically.
Command Processing Units
You can send a customized command (such as search command) and prepare a processor to handle the response.
Search command processor
public class SearchCommandProcessor extends CommandProcessor {
public static final String SearchUpdated = "SearchUpdated";
public SearchCommandProcessor(Messenger messenger) {
super(messenger);
}
private void parse(SearchCommand cmd) {
Map<String, Object> results = cmd.getResults();
}
@Override
public Content process(Content content, ID sender, InstantMessage iMsg) {
assert content instanceof SearchCommand;
parse((SearchCommand) content);
NotificationCenter nc = NotificationCenter.getInstance();
nc.postNotification(SearchUpdated, this, content);
return null;
}
}
Handshake command processor
public class HandshakeCommandProcessor extends CommandProcessor {
public HandshakeCommandProcessor(Messenger messenger) {
super(messenger);
}
private Content success() {
Log.info("handshake success!");
String sessionKey = (String) getContext("session_key");
Server server = (Server) getContext("server");
server.handshakeAccepted(sessionKey, true);
return null;
}
private Content ask(String sessionKey) {
Log.info("handshake again, session key: " + sessionKey);
setContext("session_key", sessionKey);
return new HandshakeCommand(sessionKey);
}
@Override
public Content process(Content content, ID sender, InstantMessage iMsg) {
assert content instanceof HandshakeCommand;
HandshakeCommand cmd = (HandshakeCommand) content;
String message = cmd.message;
if ("DIM!".equals(message)) {
return success();
} else if ("DIM?".equals(message)) {
return ask(cmd.sessionKey);
} else {
throw new IllegalStateException("handshake command error: " + content);
}
}
}
And don't forget to register them.
CommandProcessor.register(Command.HANDSHAKE, HandshakeCommandProcessor.class);
CommandProcessor.register(SearchCommand.SEARCH, SearchCommandProcessor.class);
Save instant message
Override interface saveMessage()
in Messenger to store instant message:
public class Messenger extends chat.dim.Messenger {
@Override
public boolean saveMessage(InstantMessage msg) {
Content content = msg.content;
if (content instanceof HandshakeCommand) {
return true;
}
if (content instanceof MetaCommand) {
return true;
}
if (content instanceof MuteCommand || content instanceof BlockCommand) {
return true;
}
if (content instanceof SearchCommand) {
return true;
}
Amanuensis clerk = Amanuensis.getInstance();
if (content instanceof ReceiptCommand) {
return clerk.saveReceipt(msg);
} else {
return clerk.saveMessage(msg);
}
}
}
Copyright © 2019 Albert Moky