From 1023ea173287ba49ee0cea9856e97749aefc6ea4 Mon Sep 17 00:00:00 2001 From: lilia Date: Mon, 20 Oct 2014 01:21:23 -0700 Subject: [PATCH] Refactor textsecure.protos -> textsecure.protobuf DRY up protobuf declarations and move to a slightly briefer naming convention. Also dropped some ArrayBuffer -> string conversions as ProtoBuf.js handles ArrayBuffers just fine, and in fact, more efficiently than strings. Finally, dropped the btoa() wrappers, because that incurs an extra string -> string conversion before the protobuf's internal string -> array buffer conversion. In lieu of btoa, we can simply pass in the optional string encoding argument to the protobuf's decode method, which in these cases should be 'binary'. Related: #17 --- background.html | 1 + index.html | 1 + js/crypto.js | 26 ++++++++++----------- js/helpers.js | 59 +++++++---------------------------------------- js/protobufs.js | 21 +++++++++++++++++ js/sendmessage.js | 54 +++++++++++++++++++++---------------------- js/test.js | 11 +++++---- options.html | 1 + test.html | 1 + 9 files changed, 79 insertions(+), 96 deletions(-) create mode 100644 js/protobufs.js diff --git a/background.html b/background.html index 2d90e655..b0e186ba 100644 --- a/background.html +++ b/background.html @@ -27,6 +27,7 @@ + diff --git a/index.html b/index.html index da54265b..6b286fc6 100644 --- a/index.html +++ b/index.html @@ -129,6 +129,7 @@ + diff --git a/js/crypto.js b/js/crypto.js index 7672ecf0..445bcaa2 100644 --- a/js/crypto.js +++ b/js/crypto.js @@ -536,7 +536,7 @@ window.textsecure.crypto = function() { var initSessionFromPreKeyWhisperMessage; var decryptWhisperMessage; var handlePreKeyWhisperMessage = function(from, encodedMessage) { - var preKeyProto = textsecure.protos.decodePreKeyWhisperMessageProtobuf(encodedMessage); + var preKeyProto = textsecure.protobuf.PreKeyWhisperMessage.decode(encodedMessage, 'binary'); return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) { return decryptWhisperMessage(from, getString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) { if (sessions[1] !== undefined) @@ -662,7 +662,7 @@ window.textsecure.crypto = function() { var messageProto = messageBytes.substring(1, messageBytes.length - 8); var mac = messageBytes.substring(messageBytes.length - 8, messageBytes.length); - var message = textsecure.protos.decodeWhisperMessageProtobuf(messageProto); + var message = textsecure.protobuf.WhisperMessage.decode(messageProto, 'binary'); var remoteEphemeralKey = toArrayBuffer(message.ephemeralKey); if (session === undefined) { @@ -704,10 +704,10 @@ window.textsecure.crypto = function() { delete session['pendingPreKey']; - var finalMessage = textsecure.protos.decodePushMessageContentProtobuf(getString(plaintext)); + var finalMessage = textsecure.protobuf.PushMessageContent.decode(plaintext); - if ((finalMessage.flags & textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION) - == textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION) + if ((finalMessage.flags & textsecure.protobuf.PushMessageContent.Flags.END_SESSION) + == textsecure.protobuf.PushMessageContent.Flags.END_SESSION) closeSession(session, true); removeOldChains(session); @@ -778,17 +778,17 @@ window.textsecure.crypto = function() { self.handleIncomingPushMessageProto = function(proto) { switch(proto.type) { - case textsecure.protos.IncomingPushMessageProtobuf.Type.PLAINTEXT: - return Promise.resolve(textsecure.protos.decodePushMessageContentProtobuf(getString(proto.message))); - case textsecure.protos.IncomingPushMessageProtobuf.Type.CIPHERTEXT: + case textsecure.protobuf.IncomingPushMessageSignal.Type.PLAINTEXT: + return Promise.resolve(textsecure.protobuf.PushMessageContent.decode(proto.message)); + case textsecure.protobuf.IncomingPushMessageSignal.Type.CIPHERTEXT: var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); return decryptWhisperMessage(from, getString(proto.message)); - case textsecure.protos.IncomingPushMessageProtobuf.Type.PREKEY_BUNDLE: + case textsecure.protobuf.IncomingPushMessageSignal.Type.PREKEY_BUNDLE: if (proto.message.readUint8() != ((3 << 4) | 3)) throw new Error("Bad version byte"); var from = proto.source + "." + (proto.sourceDevice == null ? 0 : proto.sourceDevice); return handlePreKeyWhisperMessage(from, getString(proto.message)); - case textsecure.protos.IncomingPushMessageProtobuf.Type.RECEIPT: + case textsecure.protobuf.IncomingPushMessageSignal.Type.RECEIPT: return Promise.resolve(null); default: return new Promise(function(resolve, reject) { reject(new Error("Unknown message type")); }); @@ -800,7 +800,7 @@ window.textsecure.crypto = function() { var session = crypto_storage.getOpenSession(deviceObject.encodedNumber); var doEncryptPushMessageContent = function() { - var msg = new textsecure.protos.WhisperMessageProtobuf(); + var msg = new textsecure.protobuf.WhisperMessage(); var plaintext = toArrayBuffer(pushMessageContent.encode()); var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160); @@ -850,7 +850,7 @@ window.textsecure.crypto = function() { }); } - var preKeyMsg = new textsecure.protos.PreKeyWhisperMessageProtobuf(); + var preKeyMsg = new textsecure.protobuf.PreKeyWhisperMessage(); preKeyMsg.identityKey = toArrayBuffer(crypto_storage.getIdentityKey().pubKey); preKeyMsg.registrationId = textsecure.storage.getUnencrypted("registrationId"); @@ -957,7 +957,7 @@ window.textsecure.crypto = function() { return verifyMAC(ivAndCiphertext, ecRes[1], mac).then(function() { window.crypto.subtle.decrypt({name: "AES-CBC", iv: iv}, ecRes[0], ciphertext).then(function(plaintext) { - var identityKeyMsg = textsecure.protos.decodeIdentityKeyProtobuf(getString(plaintext)); + var identityKeyMsg = textsecure.protobuf.IdentityKey.decode(plaintext); privToPub(toArrayBuffer(identityKeyMsg.identityKey)).then(function(identityKeyPair) { crypto_storage.putKeyPair("identityKey", identityKeyPair); diff --git a/js/helpers.js b/js/helpers.js index f9ff31ee..b15e0481 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -167,49 +167,6 @@ function base64ToArrayBuffer(string) { return base64DecToArr(string); } -// Protobuf decoding -//TODO: throw on missing fields everywhere -window.textsecure.protos = function() { - var self = {}; - - self.IncomingPushMessageProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/IncomingPushMessageSignal.proto").build("textsecure.IncomingPushMessageSignal"); - self.decodeIncomingPushMessageProtobuf = function(string) { - return self.IncomingPushMessageProtobuf.decode(btoa(string)); - } - - self.PushMessageContentProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/IncomingPushMessageSignal.proto").build("textsecure.PushMessageContent"); - self.decodePushMessageContentProtobuf = function(string) { - return self.PushMessageContentProtobuf.decode(btoa(string)); - } - - self.WhisperMessageProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/WhisperTextProtocol.proto").build("textsecure.WhisperMessage"); - self.decodeWhisperMessageProtobuf = function(string) { - return self.WhisperMessageProtobuf.decode(btoa(string)); - } - - self.PreKeyWhisperMessageProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/WhisperTextProtocol.proto").build("textsecure.PreKeyWhisperMessage"); - self.decodePreKeyWhisperMessageProtobuf = function(string) { - return self.PreKeyWhisperMessageProtobuf.decode(btoa(string)); - } - - self.DeviceInitProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/DeviceMessages.proto").build("textsecure.DeviceInit"); - self.decodeDeviceInitProtobuf = function(string) { - return self.DeviceInitProtobuf.decode(btoa(string)); - } - - self.IdentityKeyProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/DeviceMessages.proto").build("textsecure.IdentityKey"); - self.decodeIdentityKeyProtobuf = function(string) { - return self.IdentityKeyProtobuf.decode(btoa(string)); - } - - self.DeviceControlProtobuf = dcodeIO.ProtoBuf.loadProtoFile("protos/DeviceMessages.proto").build("textsecure.DeviceControl"); - self.decodeDeviceControlProtobuf = function(string) { - return self.DeviceControlProtobuf.decode(btoa(string)); - } - - return self; -}(); - // Number formatting utils window.textsecure.utils = function() { var self = {}; @@ -611,7 +568,7 @@ window.textsecure.subscribeToPush = function(message_callback) { socket.onmessage = function(message) { textsecure.crypto.decryptWebsocketMessage(message.message).then(function(plaintext) { - var proto = textsecure.protos.decodeIncomingPushMessageProtobuf(getString(plaintext)); + var proto = textsecure.protobuf.IncomingPushMessageSignal.decode(plaintext); // After this point, a) decoding errors are not the server's fault, and // b) we should handle them gracefully and tell the user they received an invalid message console.log("Successfully decoded message with id: " + message.id); @@ -629,8 +586,8 @@ window.textsecure.subscribeToPush = function(message_callback) { if (decrypted.flags == null) decrypted.flags = 0; - if ((decrypted.flags & textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION) - == textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION) + if ((decrypted.flags & textsecure.protobuf.PushMessageContent.Flags.END_SESSION) + == textsecure.protobuf.PushMessageContent.Flags.END_SESSION) return; if (decrypted.flags != 0) throw new Error("Unknown flags in message"); @@ -649,7 +606,7 @@ window.textsecure.subscribeToPush = function(message_callback) { decrypted.group.id = getString(decrypted.group.id); var existingGroup = textsecure.storage.groups.getNumbers(decrypted.group.id); if (existingGroup === undefined) { - if (decrypted.group.type != textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE) + if (decrypted.group.type != textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE) throw new Error("Got message for unknown group"); textsecure.storage.groups.createNewGroup(decrypted.group.members, decrypted.group.id); } else { @@ -659,7 +616,7 @@ window.textsecure.subscribeToPush = function(message_callback) { throw new Error("Sender was not a member of the group they were sending from"); switch(decrypted.group.type) { - case textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE: + case textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE: if (decrypted.group.avatar !== null) promises.push(handleAttachment(decrypted.group.avatar)); @@ -682,12 +639,12 @@ window.textsecure.subscribeToPush = function(message_callback) { decrypted.attachments = []; break; - case textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.QUIT: + case textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT: textsecure.storage.groups.removeNumber(decrypted.group.id, proto.source); decrypted.body = null; decrypted.attachments = []; - case textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.DELIVER: + case textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER: decrypted.group.name = null; decrypted.group.members = []; decrypted.group.avatar = null; @@ -741,7 +698,7 @@ window.textsecure.registerSingleDevice = function(number, verificationCode, step } window.textsecure.registerSecondDevice = function(encodedDeviceInit, cryptoInfo, stepDone) { - var deviceInit = textsecure.protos.decodeDeviceInit(encodedDeviceInit); + var deviceInit = textsecure.protobuf.DeviceInit.decode(encodedDeviceInit, 'binary'); return cryptoInfo.decryptAndHandleDeviceInit(deviceInit).then(function(identityKey) { if (identityKey.server != textsecure.api.relay) throw new Error("Unknown relay used by master"); diff --git a/js/protobufs.js b/js/protobufs.js new file mode 100644 index 00000000..173e4208 --- /dev/null +++ b/js/protobufs.js @@ -0,0 +1,21 @@ +;(function() { + + function loadProtoBufs(filename) { + return dcodeIO.ProtoBuf.loadProtoFile({root: 'protos', file: filename}).build('textsecure'); + }; + + var pushMessages = loadProtoBufs('IncomingPushMessageSignal.proto'); + var protocolMessages = loadProtoBufs('WhisperTextProtocol.proto'); + var deviceMessages = loadProtoBufs('DeviceMessages.proto'); + + window.textsecure = window.textsecure || {}; + window.textsecure.protobuf = { + IncomingPushMessageSignal : pushMessages.IncomingPushMessageSignal, + PushMessageContent : pushMessages.PushMessageContent, + WhisperMessage : protocolMessages.WhisperMessage, + PreKeyWhisperMessage : protocolMessages.PreKeyWhisperMessage, + DeviceInit : deviceMessages.DeviceInit, + IdentityKey : deviceMessages.IdentityKey, + DeviceControl : deviceMessages.DeviceControl + }; +})(); diff --git a/js/sendmessage.js b/js/sendmessage.js index fb2354ff..5a1b8c33 100644 --- a/js/sendmessage.js +++ b/js/sendmessage.js @@ -79,11 +79,11 @@ window.textsecure.messaging = function() { for (var i in groups) { var group = textsecure.storage.groups.getGroup(groups[i]); - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(group.id); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.members = group.numbers; proto.group.name = group.name === undefined ? null : group.name; @@ -102,10 +102,10 @@ window.textsecure.messaging = function() { var tryMessageAgain = function(number, encodedMessage, callback) { //TODO: Wipe identity key! refreshGroups(number).then(function() { - var message = textsecure.protos.decodePushMessageContentProtobuf(encodedMessage); + var message = textsecure.protobuf.PushMessageContent.decode(encodedMessage, 'binary'); textsecure.sendMessage([number], message, callback); }); - } + }; textsecure.replay.registerReplayFunction(tryMessageAgain, textsecure.replay.SEND_MESSAGE); var sendMessageProto = function(numbers, message, callback) { @@ -194,7 +194,7 @@ window.textsecure.messaging = function() { } makeAttachmentPointer = function(attachment) { - var proto = new textsecure.protos.PushMessageContentProtobuf.AttachmentPointer(); + var proto = new textsecure.protobuf.PushMessageContent.AttachmentPointer(); proto.key = textsecure.crypto.getRandomBytes(64); var iv = textsecure.crypto.getRandomBytes(16); @@ -233,7 +233,7 @@ window.textsecure.messaging = function() { } self.sendMessageToNumber = function(number, messageText, attachments) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); + var proto = new textsecure.protobuf.PushMessageContent(); proto.body = messageText; var promises = []; @@ -246,9 +246,9 @@ window.textsecure.messaging = function() { } self.closeSession = function(number) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); + var proto = new textsecure.protobuf.PushMessageContent(); proto.body = "TERMINATE"; - proto.flags = textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION; + proto.flags = textsecure.protobuf.PushMessageContent.Flags.END_SESSION; return sendIndividualProto(number, proto).then(function(res) { var devices = textsecure.storage.devices.getDeviceObjectsForNumber(number); for (var i in devices) @@ -259,11 +259,11 @@ window.textsecure.messaging = function() { } self.sendMessageToGroup = function(groupId, messageText, attachments) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); + var proto = new textsecure.protobuf.PushMessageContent(); proto.body = messageText; - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.DELIVER; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.DELIVER; var numbers = textsecure.storage.groups.getNumbers(groupId); if (numbers === undefined) @@ -279,14 +279,14 @@ window.textsecure.messaging = function() { } self.createGroup = function(numbers, name, avatar) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); var group = textsecure.storage.groups.createNewGroup(numbers); proto.group.id = toArrayBuffer(group.id); var numbers = group.numbers; - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.members = numbers; proto.group.name = name; @@ -305,10 +305,10 @@ window.textsecure.messaging = function() { } self.addNumberToGroup = function(groupId, number) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; var numbers = textsecure.storage.groups.addNumbers(groupId, [number]); if (numbers === undefined) @@ -319,10 +319,10 @@ window.textsecure.messaging = function() { } self.setGroupName = function(groupId, name) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; proto.group.name = name; var numbers = textsecure.storage.groups.getNumbers(groupId); @@ -334,10 +334,10 @@ window.textsecure.messaging = function() { } self.setGroupAvatar = function(groupId, avatar) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.UPDATE; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.UPDATE; var numbers = textsecure.storage.groups.getNumbers(groupId); if (numbers === undefined) @@ -351,10 +351,10 @@ window.textsecure.messaging = function() { } self.leaveGroup = function(groupId) { - var proto = new textsecure.protos.PushMessageContentProtobuf(); - proto.group = new textsecure.protos.PushMessageContentProtobuf.GroupContext(); + var proto = new textsecure.protobuf.PushMessageContent(); + proto.group = new textsecure.protobuf.PushMessageContent.GroupContext(); proto.group.id = toArrayBuffer(groupId); - proto.group.type = textsecure.protos.PushMessageContentProtobuf.GroupContext.Type.QUIT; + proto.group.type = textsecure.protobuf.PushMessageContent.GroupContext.Type.QUIT; var numbers = textsecure.storage.groups.getNumbers(groupId); if (numbers === undefined) diff --git a/js/test.js b/js/test.js index 4056f66a..a3820637 100644 --- a/js/test.js +++ b/js/test.js @@ -291,15 +291,16 @@ describe("Axolotl", function() { if (data.newEphemeralKey !== undefined) privKeyQueue.push(data.newEphemeralKey); - var message = new textsecure.protos.IncomingPushMessageProtobuf(); + var message = new textsecure.protobuf.IncomingPushMessageSignal(); message.type = data.type; message.source = "SNOWDEN"; message.message = data.message; message.sourceDevice = 1; try { - return textsecure.crypto.handleIncomingPushMessageProto(textsecure.protos.decodeIncomingPushMessageProtobuf(getString(message.encode()))).then(function(res) { + var proto = textsecure.protobuf.IncomingPushMessageSignal.decode(message.encode()); + return textsecure.crypto.handleIncomingPushMessageProto(proto).then(function(res) { if (data.expectTerminateSession) - return res.flags == textsecure.protos.PushMessageContentProtobuf.Flags.END_SESSION; + return res.flags == textsecure.protobuf.PushMessageContent.Flags.END_SESSION; return res.body == data.expectedSmsText; }).catch(function(e) { if (data.expectException) @@ -345,11 +346,11 @@ describe("Axolotl", function() { //XXX: This should be all we do: isEqual(data.expectedCiphertext, msg.body, false); if (msg.type == 1) { var expected = getString(data.expectedCiphertext); - var decoded = textsecure.protos.decodeWhisperMessageProtobuf(expected.substring(1, expected.length - 8)); + var decoded = textsecure.protobuf.WhisperMessage.decode(expected.substring(1, expected.length - 8), 'binary'); var result = getString(msg.body); return getString(decoded.encode()) == result.substring(1, result.length - 8); } else { - var decoded = textsecure.protos.decodePreKeyWhisperMessageProtobuf(getString(data.expectedCiphertext).substr(1)); + var decoded = textsecure.protobuf.PreKeyWhisperMessage.decode(getString(data.expectedCiphertext).substr(1), 'binary'); var result = getString(msg.body).substring(1); return getString(decoded.encode()) == result; } diff --git a/options.html b/options.html index 98fb19fc..96118128 100644 --- a/options.html +++ b/options.html @@ -104,6 +104,7 @@ + diff --git a/test.html b/test.html index 2a14722f..bfe95511 100644 --- a/test.html +++ b/test.html @@ -45,6 +45,7 @@ +