From cd0fe7037b3c29890af449070ec1fad5321c8ca9 Mon Sep 17 00:00:00 2001 From: lilia Date: Wed, 15 Feb 2017 18:27:06 -0800 Subject: [PATCH] Add replayable error for signed key failure Disable message sending if signed key updates fail too many times, but allow the user to retry sending. // FREEBIE --- js/libtextsecure.js | 31 +++++++++++++++++++++++++++++++ js/models/messages.js | 15 +++++++++++++-- js/views/message_view.js | 7 ++----- libtextsecure/errors.js | 13 +++++++++++++ libtextsecure/sendmessage.js | 18 ++++++++++++++++++ 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/js/libtextsecure.js b/js/libtextsecure.js index fe8e65fba45..68273d5a3d3 100644 --- a/js/libtextsecure.js +++ b/js/libtextsecure.js @@ -11,6 +11,7 @@ INIT_SESSION: 2, TRANSMIT_MESSAGE: 3, REBUILD_MESSAGE: 4, + RETRY_SEND_MESSAGE_PROTO: 5 }; window.textsecure = window.textsecure || {}; window.textsecure.replay = { @@ -89,6 +90,17 @@ SendMessageNetworkError.prototype = new ReplayableError(); SendMessageNetworkError.prototype.constructor = SendMessageNetworkError; + function SignedPreKeyRotationError(numbers, message, timestamp) { + ReplayableError.call(this, { + functionCode : Type.RETRY_SEND_MESSAGE_PROTO, + args : [numbers, message, timestamp] + }); + this.name = 'SignedPreKeyRotationError'; + this.message = "Too many signed prekey rotation failures"; + } + SignedPreKeyRotationError.prototype = new ReplayableError(); + SignedPreKeyRotationError.prototype.constructor = SignedPreKeyRotationError; + function MessageError(message, httpError) { ReplayableError.call(this, { functionCode : Type.REBUILD_MESSAGE, @@ -119,6 +131,7 @@ window.textsecure.ReplayableError = ReplayableError; window.textsecure.OutgoingMessageError = OutgoingMessageError; window.textsecure.MessageError = MessageError; + window.textsecure.SignedPreKeyRotationError = SignedPreKeyRotationError; })(); @@ -39014,6 +39027,11 @@ MessageSender.prototype = { }.bind(this)); }, sendMessageProto: function(timestamp, numbers, message, callback) { + var rejections = textsecure.storage.get('signedKeyRotationRejected', 0); + if (rejections > 5) { + throw new textsecure.SignedPreKeyRotationError(numbers, message.toArrayBuffer(), timestamp); + } + var outgoing = new OutgoingMessage(this.server, timestamp, numbers, message, callback); numbers.forEach(function(number) { @@ -39023,6 +39041,18 @@ MessageSender.prototype = { }.bind(this)); }, + retrySendMessageProto: function(numbers, encodedMessage, timestamp) { + var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); + return new Promise(function(resolve, reject) { + this.sendMessageProto(timestamp, numbers, proto, function(res) { + if (res.errors.length > 0) + reject(res); + else + resolve(res); + }); + }.bind(this)); + }, + sendIndividualProto: function(number, proto, timestamp) { return new Promise(function(resolve, reject) { this.sendMessageProto(timestamp, [number], proto, function(res) { @@ -39330,6 +39360,7 @@ textsecure.MessageSender = function(url, ports, username, password, attachment_s textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE); textsecure.replay.registerFunction(sender.retransmitMessage.bind(sender), textsecure.replay.Type.TRANSMIT_MESSAGE); textsecure.replay.registerFunction(sender.sendMessage.bind(sender), textsecure.replay.Type.REBUILD_MESSAGE); + textsecure.replay.registerFunction(sender.retrySendMessageProto.bind(sender), textsecure.replay.Type.RETRY_SEND_MESSAGE_PROTO); this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(sender); this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup .bind(sender); diff --git a/js/models/messages.js b/js/models/messages.js index 512da9e96d0..58514110af0 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -211,6 +211,9 @@ if (result instanceof Error) { errors = [result]; this.saveErrors(errors); + if (result.name === 'SignedPreKeyRotationError') { + getAccountManager().rotateSignedPreKey(); + } } else { errors = result.errors; this.saveErrors(errors); @@ -283,7 +286,8 @@ var error = _.find(this.get('errors'), function(e) { return (e.name === 'MessageError' || e.name === 'OutgoingMessageError' || - e.name === 'SendMessageNetworkError'); + e.name === 'SendMessageNetworkError' || + e.name === 'SignedPreKeyRotationError'); }); return !!error; }, @@ -292,11 +296,18 @@ return e.number === number && (e.name === 'MessageError' || e.name === 'OutgoingMessageError' || - e.name === 'SendMessageNetworkError'); + e.name === 'SendMessageNetworkError' || + e.name === 'SignedPreKeyRotationError'); }); this.set({errors: errors[1]}); return errors[0][0]; }, + isReplayableError: function(e) { + return (e.name === 'MessageError' || + e.name === 'OutgoingMessageError' || + e.name === 'SendMessageNetworkError' || + e.name === 'SignedPreKeyRotationError'); + }, resend: function(number) { var error = this.removeOutgoingErrors(number); diff --git a/js/views/message_view.js b/js/views/message_view.js index 3ee0bd221e6..bd49b6d4569 100644 --- a/js/views/message_view.js +++ b/js/views/message_view.js @@ -131,11 +131,8 @@ 'click .error-message': 'select' }, retryMessage: function() { - var retrys = _.filter(this.model.get('errors'), function(e) { - return (e.name === 'MessageError' || - e.name === 'OutgoingMessageError' || - e.name === 'SendMessageNetworkError'); - }); + var retrys = _.filter(this.model.get('errors'), + this.model.isReplayableError.bind(this.model)); _.map(retrys, 'number').forEach(function(number) { this.model.resend(number); }.bind(this)); diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js index e30f1cadcc2..4e68279e77e 100644 --- a/libtextsecure/errors.js +++ b/libtextsecure/errors.js @@ -10,6 +10,7 @@ INIT_SESSION: 2, TRANSMIT_MESSAGE: 3, REBUILD_MESSAGE: 4, + RETRY_SEND_MESSAGE_PROTO: 5 }; window.textsecure = window.textsecure || {}; window.textsecure.replay = { @@ -88,6 +89,17 @@ SendMessageNetworkError.prototype = new ReplayableError(); SendMessageNetworkError.prototype.constructor = SendMessageNetworkError; + function SignedPreKeyRotationError(numbers, message, timestamp) { + ReplayableError.call(this, { + functionCode : Type.RETRY_SEND_MESSAGE_PROTO, + args : [numbers, message, timestamp] + }); + this.name = 'SignedPreKeyRotationError'; + this.message = "Too many signed prekey rotation failures"; + } + SignedPreKeyRotationError.prototype = new ReplayableError(); + SignedPreKeyRotationError.prototype.constructor = SignedPreKeyRotationError; + function MessageError(message, httpError) { ReplayableError.call(this, { functionCode : Type.REBUILD_MESSAGE, @@ -118,5 +130,6 @@ window.textsecure.ReplayableError = ReplayableError; window.textsecure.OutgoingMessageError = OutgoingMessageError; window.textsecure.MessageError = MessageError; + window.textsecure.SignedPreKeyRotationError = SignedPreKeyRotationError; })(); diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index b5027c70221..bc4a483f694 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -183,6 +183,11 @@ MessageSender.prototype = { }.bind(this)); }, sendMessageProto: function(timestamp, numbers, message, callback) { + var rejections = textsecure.storage.get('signedKeyRotationRejected', 0); + if (rejections > 5) { + throw new textsecure.SignedPreKeyRotationError(numbers, message.toArrayBuffer(), timestamp); + } + var outgoing = new OutgoingMessage(this.server, timestamp, numbers, message, callback); numbers.forEach(function(number) { @@ -192,6 +197,18 @@ MessageSender.prototype = { }.bind(this)); }, + retrySendMessageProto: function(numbers, encodedMessage, timestamp) { + var proto = textsecure.protobuf.DataMessage.decode(encodedMessage); + return new Promise(function(resolve, reject) { + this.sendMessageProto(timestamp, numbers, proto, function(res) { + if (res.errors.length > 0) + reject(res); + else + resolve(res); + }); + }.bind(this)); + }, + sendIndividualProto: function(number, proto, timestamp) { return new Promise(function(resolve, reject) { this.sendMessageProto(timestamp, [number], proto, function(res) { @@ -499,6 +516,7 @@ textsecure.MessageSender = function(url, ports, username, password, attachment_s textsecure.replay.registerFunction(sender.tryMessageAgain.bind(sender), textsecure.replay.Type.ENCRYPT_MESSAGE); textsecure.replay.registerFunction(sender.retransmitMessage.bind(sender), textsecure.replay.Type.TRANSMIT_MESSAGE); textsecure.replay.registerFunction(sender.sendMessage.bind(sender), textsecure.replay.Type.REBUILD_MESSAGE); + textsecure.replay.registerFunction(sender.retrySendMessageProto.bind(sender), textsecure.replay.Type.RETRY_SEND_MESSAGE_PROTO); this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(sender); this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup .bind(sender);