Skip to content

Commit

Permalink
Merge pull request #204 from matrix-org/markjh/oh_hai_reliability
Browse files Browse the repository at this point in the history
Send a 'm.new_device' when we get a message for an unknown group session
  • Loading branch information
NegativeMjark authored Sep 15, 2016
2 parents 6ca917f + 2fbef86 commit 49a7475
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 9 deletions.
10 changes: 6 additions & 4 deletions lib/crypto/OlmDevice.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,9 @@ OlmDevice.prototype._saveInboundGroupSession = function(
* @param {string} senderKey
* @param {string} sessionId
* @param {function} func
* @return {object} result of func
* @return {object} Object with two keys "result": result of func, "exists"
* whether the session exists. if the session doesn't exist then the function
* isn't called and the "result" is undefined.
* @private
*/
OlmDevice.prototype._getInboundGroupSession = function(
Expand All @@ -550,7 +552,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
);

if (r === null) {
throw new Error("Unknown inbound group session id");
return {sessionExists: false};
}

r = JSON.parse(r);
Expand All @@ -567,7 +569,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
var session = new Olm.InboundGroupSession();
try {
session.unpickle(this._pickleKey, r.session);
return func(session);
return {sessionExists: true, result: func(session)};
} finally {
session.free();
}
Expand Down Expand Up @@ -603,7 +605,7 @@ OlmDevice.prototype.addInboundGroupSession = function(
* @param {string} sessionId session identifier
* @param {string} body base64-encoded body of the encrypted message
*
* @return {string} plaintext
* @return {object} {result: "plaintext"|undefined, sessionExists: Boolean}
*/
OlmDevice.prototype.decryptGroupMessage = function(
roomId, senderKey, sessionId, body
Expand Down
12 changes: 10 additions & 2 deletions lib/crypto/algorithms/megolm.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
sender_key: self._olmDevice.deviceCurve25519Key,
ciphertext: ciphertext,
session_id: session_id,
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
device_id: self._deviceId,
};

return encryptedContent;
Expand Down Expand Up @@ -356,7 +359,8 @@ utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
*
* @param {object} event raw event
*
* @return {object} decrypted payload (with properties 'type', 'content')
* @return {object} object with 'result' key with decrypted payload (with
* properties 'type', 'content') and a 'sessionKey' key.
*
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
* problem decrypting the event
Expand All @@ -377,7 +381,11 @@ MegolmDecryption.prototype.decryptEvent = function(event) {
var res = this._olmDevice.decryptGroupMessage(
event.room_id, content.sender_key, content.session_id, content.ciphertext
);
return JSON.parse(res);
if (res.sessionExists) {
return {result: JSON.parse(res.result), sessionExists: true};
} else {
return {sessionExists: false};
}
} catch (e) {
throw new base.DecryptionError(e);
}
Expand Down
6 changes: 4 additions & 2 deletions lib/crypto/algorithms/olm.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
*
* @param {object} event raw event
*
* @return {object} decrypted payload (with properties 'type', 'content')
* @return {object} result object with result property with the decrypted
* payload (with properties 'type', 'content'), and a "sessionExists" key
* always set to true.
*
* @throws {module:crypto/algorithms/base.DecryptionError} if there is a
* problem decrypting the event
Expand Down Expand Up @@ -198,7 +200,7 @@ OlmDecryption.prototype.decryptEvent = function(event) {
// TODO: Check the sender user id matches the sender key.
// TODO: check the room_id and fingerprint
if (payloadString !== null) {
return JSON.parse(payloadString);
return {result: JSON.parse(payloadString), sessionExists: true};
} else {
throw new base.DecryptionError("Bad Encrypted Message");
}
Expand Down
60 changes: 59 additions & 1 deletion lib/crypto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId) {
);

_registerEventHandlers(this, eventEmitter);

this._lastNewDeviceMessageTsByUserDeviceRoom = {};
}

function _registerEventHandlers(crypto, eventEmitter) {
Expand Down Expand Up @@ -820,7 +822,63 @@ Crypto.prototype.decryptEvent = function(event) {
var alg = new AlgClass({
olmDevice: this._olmDevice,
});
return alg.decryptEvent(event);
var r = alg.decryptEvent(event);
if (r.sessionExists) {
return r.result;
} else {
// We've got a message for a session we don't have.
// Maybe the sender forgot to tell us about the session.
// Remind the sender that we exist so that they might
// tell us about the sender.
if (event.getRoomId !== undefined && event.getSender !== undefined) {
this._sendPingToDevice(
event.getSender(), event.content.device, event.getRoomId
);
}

throw new algorithms.DecryptionError("Unknown inbound session id");
}
};

/**
* Send a "m.new_device" message to remind it that we exist and are a member
* of a room.
*
* This is rate limited to send a message at most once an hour per desination.
*
* @param {string} userId The ID of the user to ping.
* @param {string} deviceId The ID of the device to ping.
* @param {string} roomId The ID of the room we want to remind them about.
*/
Crypto.prototype._sendPingToDevice = function(userId, deviceId, roomId) {
if (deviceId === undefined) {
deviceId = "*";
}

var lastMessageTsMap = this._lastNewDeviceMessageTsByUserDeviceRoom;
var lastTsByDevice = lastMessageTsMap[userId] || {};
var lastTsByRoom = lastTsByDevice[deviceId] || {};
var lastTs = lastTsByRoom[roomId];
var timeNowMs = Date.now();
var oneHourMs = 1000 * 60 * 60;

if (lastTs === undefined || lastTs + oneHourMs < timeNowMs) {
var content = {
userId: {
deviceId: {
device_id: this._deviceId,
rooms: [roomId],
}
}
};

lastTsByRoom[roomId] = timeNowMs;

this._baseApis.sendToDevice(
"m.new_device", // OH HAI!
content
).done(function() {});
}
};

/**
Expand Down

0 comments on commit 49a7475

Please sign in to comment.