Fetch bundles on-demand, encrypt in background
Bundles are now fetched on demand when a session needs to be established. This should lessen the chance of changes to the bundles occuring before they're used, as well as lessen the load of fetching bundles. Also, the message encryption is now done in a background thread, as this can be somewhat costly if many sessions are present. This is probably not going to be an issue in real use, but it's good practice anyway.
This commit is contained in:
parent
cb7980c65e
commit
3815d4efa3
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.crypto.axolotl;
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
@ -42,13 +43,16 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.parser.IqParser;
|
import eu.siacs.conversations.parser.IqParser;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
|
|
||||||
public class AxolotlService {
|
public class AxolotlService {
|
||||||
|
|
||||||
|
@ -62,8 +66,9 @@ public class AxolotlService {
|
||||||
private final XmppConnectionService mXmppConnectionService;
|
private final XmppConnectionService mXmppConnectionService;
|
||||||
private final SQLiteAxolotlStore axolotlStore;
|
private final SQLiteAxolotlStore axolotlStore;
|
||||||
private final SessionMap sessions;
|
private final SessionMap sessions;
|
||||||
private final BundleMap bundleCache;
|
|
||||||
private final Map<Jid, Set<Integer>> deviceIds;
|
private final Map<Jid, Set<Integer>> deviceIds;
|
||||||
|
private final FetchStatusMap fetchStatusMap;
|
||||||
|
private final SerialSingleThreadExecutor executor;
|
||||||
private int ownDeviceId;
|
private int ownDeviceId;
|
||||||
|
|
||||||
public static class SQLiteAxolotlStore implements AxolotlStore {
|
public static class SQLiteAxolotlStore implements AxolotlStore {
|
||||||
|
@ -560,7 +565,13 @@ public class AxolotlService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BundleMap extends AxolotlAddressMap<PreKeyBundle> {
|
private static enum FetchStatus {
|
||||||
|
PENDING,
|
||||||
|
SUCCESS,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +581,8 @@ public class AxolotlService {
|
||||||
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
|
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
|
||||||
this.deviceIds = new HashMap<>();
|
this.deviceIds = new HashMap<>();
|
||||||
this.sessions = new SessionMap(axolotlStore, account);
|
this.sessions = new SessionMap(axolotlStore, account);
|
||||||
this.bundleCache = new BundleMap();
|
this.fetchStatusMap = new FetchStatusMap();
|
||||||
|
this.executor = new SerialSingleThreadExecutor();
|
||||||
this.ownDeviceId = axolotlStore.getLocalRegistrationId();
|
this.ownDeviceId = axolotlStore.getLocalRegistrationId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,56 +805,93 @@ public class AxolotlService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException {
|
private boolean createSessionsIfNeeded(Conversation conversation) {
|
||||||
|
boolean newSessions = false;
|
||||||
Log.d(Config.LOGTAG, "Creating axolotl sessions if needed...");
|
Log.d(Config.LOGTAG, "Creating axolotl sessions if needed...");
|
||||||
AxolotlAddress address = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0);
|
Jid contactJid = conversation.getContact().getJid().toBareJid();
|
||||||
for(Integer deviceId: bundleCache.getAll(address).keySet()) {
|
Set<AxolotlAddress> addresses = new HashSet<>();
|
||||||
Log.d(Config.LOGTAG, "Processing device ID: " + deviceId);
|
if(deviceIds.get(contactJid) != null) {
|
||||||
AxolotlAddress remoteAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), deviceId);
|
for(Integer foreignId:this.deviceIds.get(contactJid)) {
|
||||||
if(sessions.get(remoteAddress) == null) {
|
Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+foreignId);
|
||||||
Log.d(Config.LOGTAG, "Building new sesstion for " + deviceId);
|
addresses.add(new AxolotlAddress(contactJid.toString(), foreignId));
|
||||||
SessionBuilder builder = new SessionBuilder(this.axolotlStore, remoteAddress);
|
}
|
||||||
try {
|
} else {
|
||||||
builder.process(bundleCache.get(remoteAddress));
|
Log.e(Config.LOGTAG, "Have no target devices in PEP!");
|
||||||
XmppAxolotlSession session = new XmppAxolotlSession(this.axolotlStore, remoteAddress);
|
}
|
||||||
sessions.put(remoteAddress, session);
|
Log.d(Config.LOGTAG, "Checking own account "+account.getJid().toBareJid());
|
||||||
} catch (InvalidKeyException e) {
|
if(deviceIds.get(account.getJid().toBareJid()) != null) {
|
||||||
Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": InvalidKeyException, " +e.getMessage());
|
for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) {
|
||||||
} catch (UntrustedIdentityException e) {
|
Log.d(Config.LOGTAG, "Found device "+account.getJid().toBareJid()+":"+ownId);
|
||||||
Log.d(Config.LOGTAG, "Error building session for " + deviceId+ ": UntrustedIdentityException, " +e.getMessage());
|
addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId));
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, "Already have session for " + deviceId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!this.hasAny(contact)) {
|
for (AxolotlAddress address : addresses) {
|
||||||
Log.e(Config.LOGTAG, "No Axolotl sessions available!");
|
Log.d(Config.LOGTAG, "Processing device: " + address.toString());
|
||||||
throw new NoSessionsCreatedException(); // FIXME: proper error handling
|
FetchStatus status = fetchStatusMap.get(address);
|
||||||
|
XmppAxolotlSession session = sessions.get(address);
|
||||||
|
if ( session == null && ( status == null || status == FetchStatus.ERROR) ) {
|
||||||
|
fetchStatusMap.put(address, FetchStatus.PENDING);
|
||||||
|
this.buildSessionFromPEP(conversation, address);
|
||||||
|
newSessions = true;
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "Already have session for " + address.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return newSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException {
|
@Nullable
|
||||||
XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage);
|
public XmppAxolotlMessage encrypt(Message message ){
|
||||||
createSessionsIfNeeded(contact);
|
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact(),
|
||||||
Log.d(Config.LOGTAG, "Building axolotl foreign headers...");
|
ownDeviceId, message.getBody());
|
||||||
|
|
||||||
for(XmppAxolotlSession session : findSessionsforContact(contact)) {
|
if(findSessionsforContact(axolotlMessage.getContact()).isEmpty()) {
|
||||||
// if(!session.isTrusted()) {
|
return null;
|
||||||
// TODO: handle this properly
|
}
|
||||||
// continue;
|
Log.d(Config.LOGTAG, "Building axolotl foreign headers...");
|
||||||
// }
|
for (XmppAxolotlSession session : findSessionsforContact(axolotlMessage.getContact())) {
|
||||||
message.addHeader(session.processSending(message.getInnerKey()));
|
Log.d(Config.LOGTAG, session.remoteAddress.toString());
|
||||||
|
//if(!session.isTrusted()) {
|
||||||
|
// TODO: handle this properly
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "Building axolotl own headers...");
|
Log.d(Config.LOGTAG, "Building axolotl own headers...");
|
||||||
for(XmppAxolotlSession session : findOwnSessions()) {
|
for (XmppAxolotlSession session : findOwnSessions()) {
|
||||||
// if(!session.isTrusted()) {
|
Log.d(Config.LOGTAG, session.remoteAddress.toString());
|
||||||
// TODO: handle this properly
|
// if(!session.isTrusted()) {
|
||||||
// continue;
|
// TODO: handle this properly
|
||||||
// }
|
// continue;
|
||||||
message.addHeader(session.processSending(message.getInnerKey()));
|
// }
|
||||||
|
axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return axolotlMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSending(final Message message) {
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
MessagePacket packet = mXmppConnectionService.getMessageGenerator()
|
||||||
|
.generateAxolotlChat(message);
|
||||||
|
if (packet == null) {
|
||||||
|
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
|
||||||
|
} else {
|
||||||
|
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
|
||||||
|
mXmppConnectionService.sendMessagePacket(account, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(Message message) {
|
||||||
|
boolean newSessions = createSessionsIfNeeded(message.getConversation());
|
||||||
|
|
||||||
|
if (!newSessions) {
|
||||||
|
this.processSending(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
|
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
|
||||||
|
|
|
@ -179,13 +179,13 @@ public class Conversation extends AbstractEntity implements Blockable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) {
|
public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (Message message : this.messages) {
|
for (Message message : this.messages) {
|
||||||
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
||||||
&& (message.getEncryption() == Message.ENCRYPTION_OTR)) {
|
&& (message.getEncryption() == encryptionType)) {
|
||||||
onMessageFound.onMessageFound(message);
|
onMessageFound.onMessageFound(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,16 +66,18 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
delay.setAttribute("stamp", mDateFormat.format(date));
|
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateAxolotlChat(Message message) throws NoSessionsCreatedException{
|
public MessagePacket generateAxolotlChat(Message message) {
|
||||||
return generateAxolotlChat(message, false);
|
return generateAxolotlChat(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateAxolotlChat(Message message, boolean addDelay) throws NoSessionsCreatedException{
|
public MessagePacket generateAxolotlChat(Message message, boolean addDelay) {
|
||||||
MessagePacket packet = preparePacket(message, addDelay);
|
MessagePacket packet = preparePacket(message, addDelay);
|
||||||
AxolotlService service = message.getConversation().getAccount().getAxolotlService();
|
AxolotlService service = message.getConversation().getAccount().getAxolotlService();
|
||||||
Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing...");
|
Log.d(Config.LOGTAG, "Submitting message to axolotl service for send processing...");
|
||||||
XmppAxolotlMessage axolotlMessage = service.processSending(message.getContact(),
|
XmppAxolotlMessage axolotlMessage = service.encrypt(message);
|
||||||
message.getBody());
|
if (axolotlMessage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
packet.setAxolotlMessage(axolotlMessage.toXml());
|
packet.setAxolotlMessage(axolotlMessage.toXml());
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import net.java.otr4j.session.Session;
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
@ -105,6 +106,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
|
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
|
||||||
if(plaintextMessage != null) {
|
if(plaintextMessage != null) {
|
||||||
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED);
|
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, Message.STATUS_RECEIVED);
|
||||||
|
finishedMessage.setAxolotlSession(plaintextMessage.getSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
return finishedMessage;
|
return finishedMessage;
|
||||||
|
@ -189,15 +191,9 @@ public class MessageParser extends AbstractParser implements
|
||||||
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
||||||
Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing...");
|
Log.d(Config.LOGTAG, "Received PEP device list update from "+ from + ", processing...");
|
||||||
Element item = items.findChild("item");
|
Element item = items.findChild("item");
|
||||||
List<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
||||||
AxolotlService axolotlService = account.getAxolotlService();
|
AxolotlService axolotlService = account.getAxolotlService();
|
||||||
if(account.getJid().toBareJid().equals(from)) {
|
axolotlService.registerDevices(from, deviceIds);
|
||||||
} else {
|
|
||||||
Contact contact = account.getRoster().getContact(from);
|
|
||||||
for (Integer deviceId : deviceIds) {
|
|
||||||
axolotlService.fetchBundleIfNeeded(contact, deviceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -703,7 +703,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
|
|
||||||
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||||
message.getConversation().endOtrIfNeeded();
|
message.getConversation().endOtrIfNeeded();
|
||||||
message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
|
||||||
|
new Conversation.OnMessageFound() {
|
||||||
@Override
|
@Override
|
||||||
public void onMessageFound(Message message) {
|
public void onMessageFound(Message message) {
|
||||||
markMessage(message,Message.STATUS_SEND_FAILED);
|
markMessage(message,Message.STATUS_SEND_FAILED);
|
||||||
|
@ -758,12 +759,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
try {
|
message.setStatus(Message.STATUS_WAITING);
|
||||||
packet = mMessageGenerator.generateAxolotlChat(message);
|
account.getAxolotlService().sendMessage(message);
|
||||||
Log.d(Config.LOGTAG, "Succeeded generating axolotl chat message!");
|
|
||||||
} catch (NoSessionsCreatedException e) {
|
|
||||||
message.setStatus(Message.STATUS_WAITING);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1797,7 +1794,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
account.getJid().toBareJid() + " otr session established with "
|
account.getJid().toBareJid() + " otr session established with "
|
||||||
+ conversation.getJid() + "/"
|
+ conversation.getJid() + "/"
|
||||||
+ otrSession.getSessionID().getUserID());
|
+ otrSession.getSessionID().getUserID());
|
||||||
conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageFound(Message message) {
|
public void onMessageFound(Message message) {
|
||||||
|
|
Loading…
Reference in a new issue