From 7983300f4a90001ca3b7a882262f8efc634e30f7 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 9 Aug 2018 17:28:51 -0700 Subject: [PATCH] Use base64 strings for incoming message cache instead of binary --- js/background.js | 3 +- js/modules/data.js | 14 -------- js/modules/migrate_to_sql.js | 5 +++ js/signal_protocol_store.js | 7 ++-- libtextsecure/message_receiver.js | 50 ++++++++++++++++++++++++---- libtextsecure/storage/unprocessed.js | 7 ++-- test/storage_test.js | 4 +-- 7 files changed, 62 insertions(+), 28 deletions(-) diff --git a/js/background.js b/js/background.js index 7c45c25abb..8990e20df8 100644 --- a/js/background.js +++ b/js/background.js @@ -338,7 +338,8 @@ db, clearStores: Whisper.Database.clearStores, handleDOMException: Whisper.Database.handleDOMException, - arrayBufferToString: textsecure.MessageReceiver.arrayBufferToString, + arrayBufferToString: + textsecure.MessageReceiver.arrayBufferToStringBase64, countCallback: count => { window.log.info(`Migration: ${count} messages complete`); showMigrationStatus(count); diff --git a/js/modules/data.js b/js/modules/data.js index 357ad04e2b..b7a7c481f0 100644 --- a/js/modules/data.js +++ b/js/modules/data.js @@ -59,7 +59,6 @@ module.exports = { getUnprocessedById, saveUnprocessed, saveUnprocesseds, - updateUnprocessed, removeUnprocessed, removeAllUnprocessed, @@ -374,19 +373,6 @@ async function saveUnprocesseds(arrayOfUnprocessed, { forceSave } = {}) { }); } -async function updateUnprocessed(id, updates) { - const existing = await channels.getUnprocessedById(id); - if (!existing) { - throw new Error(`Unprocessed id ${id} does not exist in the database!`); - } - const toSave = { - ...existing, - ...updates, - }; - - await saveUnprocessed(toSave); -} - async function removeUnprocessed(id) { await channels.removeUnprocessed(id); } diff --git a/js/modules/migrate_to_sql.js b/js/modules/migrate_to_sql.js index fff080d786..51b897abd2 100644 --- a/js/modules/migrate_to_sql.js +++ b/js/modules/migrate_to_sql.js @@ -86,6 +86,11 @@ async function migrateToSQL({ forEach(array, item => { // In the new database, we can't store ArrayBuffers, so we turn these two fields // into strings like MessageReceiver now does before save. + + // Need to set it to version two, since we're using Base64 strings now + // eslint-disable-next-line no-param-reassign + item.version = 2; + if (item.envelope) { // eslint-disable-next-line no-param-reassign item.envelope = arrayBufferToString(item.envelope); diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js index 12ba9b1a7f..70dd0cdca6 100644 --- a/js/signal_protocol_store.js +++ b/js/signal_protocol_store.js @@ -942,6 +942,9 @@ getAllUnprocessed() { return window.Signal.Data.getAllUnprocessed(); }, + getUnprocessedById(id) { + return window.Signal.Data.getUnprocessedById(id, { Unprocessed }); + }, addUnprocessed(data) { // We need to pass forceSave because the data has an id already, which will cause // an update instead of an insert. @@ -950,8 +953,8 @@ Unprocessed, }); }, - updateUnprocessed(id, updates) { - return window.Signal.Data.updateUnprocessed(id, updates, { Unprocessed }); + saveUnprocessed(data) { + return window.Signal.Data.saveUnprocessed(data, { Unprocessed }); }, removeUnprocessed(id) { return window.Signal.Data.removeUnprocessed(id, { Unprocessed }); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index e8b684aa95..aa23fcecd7 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -35,6 +35,10 @@ MessageReceiver.stringToArrayBuffer = string => dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer(); MessageReceiver.arrayBufferToString = arrayBuffer => dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary'); +MessageReceiver.stringToArrayBufferBase64 = string => + dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer(); +MessageReceiver.arrayBufferToStringBase64 = arrayBuffer => + dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); MessageReceiver.prototype = new textsecure.EventTarget(); MessageReceiver.prototype.extend({ @@ -275,6 +279,12 @@ MessageReceiver.prototype.extend({ try { let envelopePlaintext = item.envelope; + if (item.version === 2) { + envelopePlaintext = MessageReceiver.stringToArrayBufferBase64( + envelopePlaintext + ); + } + if (typeof envelopePlaintext === 'string') { envelopePlaintext = MessageReceiver.stringToArrayBuffer( envelopePlaintext @@ -285,6 +295,13 @@ MessageReceiver.prototype.extend({ const { decrypted } = item; if (decrypted) { let payloadPlaintext = decrypted; + + if (item.version === 2) { + payloadPlaintext = MessageReceiver.stringToArrayBufferBase64( + payloadPlaintext + ); + } + if (typeof payloadPlaintext === 'string') { payloadPlaintext = MessageReceiver.stringToArrayBuffer( payloadPlaintext @@ -339,7 +356,7 @@ MessageReceiver.prototype.extend({ ); return textsecure.storage.unprocessed.remove(item.id); } - return textsecure.storage.unprocessed.update(item.id, { attempts }); + return textsecure.storage.unprocessed.save({ ...item, attempts }); }) ).then( () => items, @@ -357,18 +374,33 @@ MessageReceiver.prototype.extend({ const id = this.getEnvelopeId(envelope); const data = { id, - envelope: MessageReceiver.arrayBufferToString(plaintext), + version: 2, + envelope: MessageReceiver.arrayBufferToStringBase64(plaintext), timestamp: Date.now(), attempts: 1, }; return textsecure.storage.unprocessed.add(data); }, - updateCache(envelope, plaintext) { + async updateCache(envelope, plaintext) { const id = this.getEnvelopeId(envelope); - const data = { - decrypted: MessageReceiver.arrayBufferToString(plaintext), - }; - return textsecure.storage.unprocessed.update(id, data); + const item = await textsecure.storage.unprocessed.get(id); + if (!item) { + window.log.error( + `updateCache: Didn't find item ${id} in cache to update` + ); + return null; + } + + if (item.get('version') === 2) { + item.set( + 'decrypted', + MessageReceiver.arrayBufferToStringBase64(plaintext) + ); + } else { + item.set('decrypted', MessageReceiver.arrayBufferToString(plaintext)); + } + + return textsecure.storage.unprocessed.save(item.attributes); }, removeFromCache(envelope) { const id = this.getEnvelopeId(envelope); @@ -1189,3 +1221,7 @@ textsecure.MessageReceiver.stringToArrayBuffer = MessageReceiver.stringToArrayBuffer; textsecure.MessageReceiver.arrayBufferToString = MessageReceiver.arrayBufferToString; +textsecure.MessageReceiver.stringToArrayBufferBase64 = + MessageReceiver.stringToArrayBufferBase64; +textsecure.MessageReceiver.arrayBufferToStringBase64 = + MessageReceiver.arrayBufferToStringBase64; diff --git a/libtextsecure/storage/unprocessed.js b/libtextsecure/storage/unprocessed.js index 8793feeb2b..4990c8e54c 100644 --- a/libtextsecure/storage/unprocessed.js +++ b/libtextsecure/storage/unprocessed.js @@ -12,11 +12,14 @@ getAll() { return textsecure.storage.protocol.getAllUnprocessed(); }, + get(id) { + return textsecure.storage.protocol.getUnprocessedById(id); + }, add(data) { return textsecure.storage.protocol.addUnprocessed(data); }, - update(id, updates) { - return textsecure.storage.protocol.updateUnprocessed(id, updates); + save(data) { + return textsecure.storage.protocol.saveUnprocessed(data); }, remove(id) { return textsecure.storage.protocol.removeUnprocessed(id); diff --git a/test/storage_test.js b/test/storage_test.js index 596472da99..71ef6c4cd6 100644 --- a/test/storage_test.js +++ b/test/storage_test.js @@ -1241,12 +1241,12 @@ describe('SignalProtocolStore', function() { }); }); - it('updateUnprocessed successfully updates only part of itme', function() { + it('saveUnprocessed successfully updates item', function() { var id = 1; return store .addUnprocessed({ id: id, name: 'first', timestamp: 1 }) .then(function() { - return store.updateUnprocessed(id, { name: 'updated' }); + return store.saveUnprocessed({ id, name: 'updated', timestamp: 1 }); }) .then(function() { return store.getAllUnprocessed();