1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Send a cancellation for room key requests (#456)

* Send a cancellation for room key requests

When we receive a room key, cancel any pending requests we have open for that
key.
This commit is contained in:
Richard van der Hoff
2017-06-07 14:00:47 +01:00
committed by GitHub
parent de257b34c0
commit 0371265fea
3 changed files with 161 additions and 1 deletions

View File

@@ -32,6 +32,24 @@ import utils from '../utils';
const SEND_KEY_REQUESTS_DELAY_MS = 500; const SEND_KEY_REQUESTS_DELAY_MS = 500;
/** possible states for a room key request /** possible states for a room key request
*
* The state machine looks like:
*
* |
* V (cancellation requested)
* UNSENT -----------------------------+
* | |
* | (send successful) |
* V |
* SENT |
* | |
* | (cancellation requested) |
* V |
* CANCELLATION_PENDING |
* | |
* | (cancellation sent) |
* V |
* (deleted) <---------------------------+
* *
* @enum {number} * @enum {number}
*/ */
@@ -41,6 +59,9 @@ const ROOM_KEY_REQUEST_STATES = {
/** request sent, awaiting reply */ /** request sent, awaiting reply */
SENT: 1, SENT: 1,
/** reply received, cancellation not yet sent */
CANCELLATION_PENDING: 2,
}; };
export default class OutgoingRoomKeyRequestManager { export default class OutgoingRoomKeyRequestManager {
@@ -107,6 +128,92 @@ export default class OutgoingRoomKeyRequestManager {
}); });
} }
/**
* Cancel room key requests, if any match the given details
*
* @param {module:crypto~RoomKeyRequestBody} requestBody
*
* @returns {Promise} resolves when the request has been updated in our
* pending list.
*/
cancelRoomKeyRequest(requestBody) {
return this._cryptoStore.getOutgoingRoomKeyRequest(
requestBody,
).then((req) => {
if (!req) {
// no request was made for this key
return;
}
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING:
// nothing to do here
return;
case ROOM_KEY_REQUEST_STATES.UNSENT:
// just delete it
// FIXME: ghahah we may have attempted to send it, and
// not yet got a successful response. So the server
// may have seen it, so we still need to send a cancellation
// in that case :/
console.log(
'deleting unnecessary room key request for ' +
stringifyRequestBody(requestBody),
);
return this._cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT,
);
case ROOM_KEY_REQUEST_STATES.SENT:
// send a cancellation.
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
state: ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
cancellationTxnId: this._baseApis.makeTxnId(),
},
).then((updatedReq) => {
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the
// request in state ROOM_KEY_REQUEST_STATES.SENT,
// so we must have raced with another tab to mark
// the request cancelled. There is no point in
// sending another cancellation since the other tab
// will do it.
console.log(
'Tried to cancel room key request for ' +
stringifyRequestBody(requestBody) +
' but it was already cancelled in another tab',
);
return;
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
this._sendOutgoingRoomKeyRequestCancellation(
updatedReq,
).catch((e) => {
console.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
this._startTimer();
}).done();
});
default:
throw new Error('unhandled state: ' + req.state);
}
});
}
// start the background timer to send queued requests, if the timer isn't // start the background timer to send queued requests, if the timer isn't
// already running // already running
_startTimer() { _startTimer() {
@@ -143,6 +250,7 @@ export default class OutgoingRoomKeyRequestManager {
console.log("Looking for queued outgoing room key requests"); console.log("Looking for queued outgoing room key requests");
return this._cryptoStore.getOutgoingRoomKeyRequestByState([ return this._cryptoStore.getOutgoingRoomKeyRequestByState([
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
ROOM_KEY_REQUEST_STATES.UNSENT, ROOM_KEY_REQUEST_STATES.UNSENT,
]).then((req) => { ]).then((req) => {
if (!req) { if (!req) {
@@ -151,7 +259,14 @@ export default class OutgoingRoomKeyRequestManager {
return; return;
} }
return this._sendOutgoingRoomKeyRequest(req).then(() => { let prom;
if (req.state === ROOM_KEY_REQUEST_STATES.UNSENT) {
prom = this._sendOutgoingRoomKeyRequest(req);
} else { // must be a cancellation
prom = this._sendOutgoingRoomKeyRequestCancellation(req);
}
return prom.then(() => {
// go around the loop again // go around the loop again
return this._sendOutgoingRoomKeyRequests(); return this._sendOutgoingRoomKeyRequests();
}).catch((e) => { }).catch((e) => {
@@ -187,6 +302,30 @@ export default class OutgoingRoomKeyRequestManager {
}); });
} }
// given a RoomKeyRequest, cancel it and delete the request record
_sendOutgoingRoomKeyRequestCancellation(req) {
console.log(
`Sending cancellation for key request for ` +
`${stringifyRequestBody(req.requestBody)} to ` +
`${stringifyRecipientList(req.recipients)} ` +
`(cancellation id ${req.cancellationTxnId})`,
);
const requestMessage = {
action: "request_cancellation",
requesting_device_id: this._deviceId,
request_id: req.requestId,
};
return this._sendMessageToDevices(
requestMessage, req.recipients, req.cancellationTxnId,
).then(() => {
return this._cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
);
});
}
// send a RoomKeyRequest to a list of recipients // send a RoomKeyRequest to a list of recipients
_sendMessageToDevices(message, recipients, txnId) { _sendMessageToDevices(message, recipients, txnId) {
const contentMap = {}; const contentMap = {};

View File

@@ -658,6 +658,14 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
content.session_key, event.getKeysClaimed(), content.session_key, event.getKeysClaimed(),
); );
// cancel any outstanding room key requests for this session
this._crypto.cancelRoomKeyRequest({
algorithm: content.algorithm,
room_id: content.room_id,
session_id: content.session_id,
sender_key: senderKey,
});
// have another go at decrypting events sent with this session. // have another go at decrypting events sent with this session.
this._retryDecryption(senderKey, sessionId); this._retryDecryption(senderKey, sessionId);
}; };

View File

@@ -822,6 +822,19 @@ Crypto.prototype.requestRoomKey = function(requestBody, recipients) {
}).done(); }).done();
}; };
/**
* Cancel any earlier room key request
*
* @param {module:crypto~RoomKeyRequestBody} requestBody
* parameters to match for cancellation
*/
Crypto.prototype.cancelRoomKeyRequest = function(requestBody) {
this._outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
.catch((e) => {
console.warn("Error clearing pending room key requests", e);
}).done();
};
/** /**
* handle an m.room.encryption event * handle an m.room.encryption event
* *