From a92f4ab8daba02d3c1ed5b116fd74330f744cb56 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 5 Jun 2020 08:55:21 +1000 Subject: [PATCH 1/9] address reviews --- ts/session/protocols/SessionProtocol.ts | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index eae8fcbad..92479de7a 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -1,5 +1,6 @@ import { SessionResetMessage } from '../messages/outgoing'; // import { MessageSender } from '../sending'; +import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; interface StringToNumberMap { [key: string]: number; @@ -74,11 +75,11 @@ export async function sendSessionRequestIfNeeded( timestamp: Date.now(), }); - return sendSessionRequests(sessionReset, device); + return sendSessionRequest(sessionReset, device); } /** */ -export async function sendSessionRequests( +export async function sendSessionRequest( message: SessionResetMessage, device: string ): Promise { @@ -90,14 +91,14 @@ export async function sendSessionRequests( // const rawMessage = toRawMessage(message); // // TODO: Send out the request via MessageSender - // return MessageSender.send(rawMessage) - // .then(async () => { - // await _updateSentSessionTimestamp(device, timestamp); + // try { + // await MessageSender.send(rawMessage); + // await _updateSentSessionTimestamp(device, timestamp); + // } catch (e) { + // window.console.log('Failed to send session request to', device); + // } finally { // pendingSendSessionsTimestamp.delete(device); - // }) - // .catch(() => { - // pendingSendSessionsTimestamp.delete(device); - // }); + // } } /** @@ -133,7 +134,7 @@ export async function onSessionRequestProcessed(device: string) { */ async function _fetchFromDBIfNeeded(): Promise { if (!sentSessionsTimestamp) { - const sentItem = await window.Signal.Data.getItemById( + const sentItem = await getItemById( 'sentSessionsTimestamp' ); if (sentItem) { @@ -142,7 +143,7 @@ async function _fetchFromDBIfNeeded(): Promise { sentSessionsTimestamp = {}; } - const processedItem = await window.Signal.Data.getItemById( + const processedItem = await getItemById( 'processedSessionsTimestamp' ); if (processedItem) { @@ -159,7 +160,7 @@ async function _writeToDBSentSessions(): Promise { value: JSON.stringify(sentSessionsTimestamp), }; - await window.Signal.Data.createOrUpdateItem(data); + await createOrUpdateItem(data); } async function _writeToDBProcessedSessions(): Promise { @@ -168,7 +169,7 @@ async function _writeToDBProcessedSessions(): Promise { value: JSON.stringify(processedSessionsTimestamp), }; - await window.Signal.Data.createOrUpdateItem(data); + await createOrUpdateItem(data); } /** From 15f71cb9c8fc43eeba0d8a670ff0332ea478992a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 5 Jun 2020 10:36:33 +1000 Subject: [PATCH 2/9] move sessionprotocol to a full static class --- ts/session/protocols/SessionProtocol.ts | 437 ++++++++++++------------ ts/session/protocols/index.ts | 2 +- 2 files changed, 225 insertions(+), 214 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index 92479de7a..e357e46a3 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -5,248 +5,259 @@ import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; interface StringToNumberMap { [key: string]: number; } +// tslint:disable: function-name +// tslint:disable: no-unnecessary-class +export class SessionProtocol { + + private static dbLoaded: Boolean = false; + /** + * This map olds the sent session timestamps, i.e. session requests message effectively sent to the recipient. + * It is backed by a database entry so it's loaded from db on startup. + * This map should not be used directly, but instead through + * `updateSendSessionTimestamp()`, `getSendSessionRequest()` or `hasSendSessionRequest()` + */ + private static sentSessionsTimestamp: StringToNumberMap; + + + /** + * This map olds the processed session timestamps, i.e. when we received a session request and handled it. + * It is backed by a database entry so it's loaded from db on startup. + * This map should not be used directly, but instead through + * `updateProcessedSessionTimestamp()`, `getProcessedSessionRequest()` or `hasProcessedSessionRequest()` + */ + private static processedSessionsTimestamp: StringToNumberMap; + + /** + * This map olds the timestamp on which a sent session reset is triggered for a specific device. + * Once the message is sent or failed to sent, this device is removed from here. + * This is a memory only map. Which means that on app restart it's starts empty. + */ + private static readonly pendingSendSessionsTimestamp: Set = new Set(); + + + /** Returns true if we already have a session with that device */ + public static async hasSession(device: string): Promise { + // Session does not use the concept of a deviceId, thus it's always 1 + const address = new window.libsignal.SignalProtocolAddress(device, 1); + const sessionCipher = new window.libsignal.SessionCipher( + window.textsecure.storage.protocol, + address + ); -/** - * This map olds the sent session timestamps, i.e. session requests message effectively sent to the recipient. - * It is backed by a database entry so it's loaded from db on startup. - * This map should not be used directly, but instead through - * `_updateSendSessionTimestamp()`, `_getSendSessionRequest()` or `_hasSendSessionRequest()` - */ -let sentSessionsTimestamp: StringToNumberMap; - -/** - * This map olds the processed session timestamps, i.e. when we received a session request and handled it. - * It is backed by a database entry so it's loaded from db on startup. - * This map should not be used directly, but instead through - * `_updateProcessedSessionTimestamp()`, `_getProcessedSessionRequest()` or `_hasProcessedSessionRequest()` - */ -let processedSessionsTimestamp: StringToNumberMap; - -/** - * This map olds the timestamp on which a sent session reset is triggered for a specific device. - * Once the message is sent or failed to sent, this device is removed from here. - * This is a memory only map. Which means that on app restart it's starts empty. - */ -const pendingSendSessionsTimestamp: Set = new Set(); - -/** ======= exported functions ======= */ - -/** Returns true if we already have a session with that device */ -export async function hasSession(device: string): Promise { - // Session does not use the concept of a deviceId, thus it's always 1 - const address = new window.libsignal.SignalProtocolAddress(device, 1); - const sessionCipher = new window.libsignal.SessionCipher( - window.textsecure.storage.protocol, - address - ); - - return sessionCipher.hasOpenSession(); -} - -/** - * Returns true if we sent a session request to that device already OR - * if a session request to that device is right now being sent. - */ -export async function hasSentSessionRequest(device: string): Promise { - const pendingSend = pendingSendSessionsTimestamp.has(device); - const hasSent = await _hasSentSessionRequest(device); + return sessionCipher.hasOpenSession(); + } - return pendingSend || hasSent; -} + /** + * Returns true if we sent a session request to that device already OR + * if a session request to that device is right now being sent. + */ + public static async hasSentSessionRequest(device: string): Promise { + const pendingSend = SessionProtocol.pendingSendSessionsTimestamp.has(device); + const hasSent = await SessionProtocol._hasSentSessionRequest(device); -/** - * Triggers a SessionResetMessage to be sent if: - * - we do not already have a session and - * - we did not sent a session request already to that device and - * - we do not have a session request currently being send to that device - */ -export async function sendSessionRequestIfNeeded( - device: string -): Promise { - if (hasSession(device) || hasSentSessionRequest(device)) { - return Promise.resolve(); - } - - const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact( - device - ); - const sessionReset = new SessionResetMessage({ - preKeyBundle, - timestamp: Date.now(), - }); - - return sendSessionRequest(sessionReset, device); -} + return pendingSend || hasSent; + } -/** */ -export async function sendSessionRequest( - message: SessionResetMessage, - device: string -): Promise { - const timestamp = Date.now(); - - // mark the session as being pending send with current timestamp - // so we know we already triggered a new session with that device - pendingSendSessionsTimestamp.add(device); - // const rawMessage = toRawMessage(message); - // // TODO: Send out the request via MessageSender - - // try { - // await MessageSender.send(rawMessage); - // await _updateSentSessionTimestamp(device, timestamp); - // } catch (e) { - // window.console.log('Failed to send session request to', device); - // } finally { - // pendingSendSessionsTimestamp.delete(device); - // } -} + /** + * Triggers a SessionResetMessage to be sent if: + * - we do not already have a session and + * - we did not sent a session request already to that device and + * - we do not have a session request currently being send to that device + */ + public static async sendSessionRequestIfNeeded( + device: string + ): Promise { + if (SessionProtocol.hasSession(device) || SessionProtocol.hasSentSessionRequest(device)) { + return Promise.resolve(); + } -/** - * Called when a session is establish so we store on database this info. - */ -export async function onSessionEstablished(device: string) { - // remove our existing sent timestamp for that device - return _updateSentSessionTimestamp(device, undefined); -} + const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact( + device + ); + const sessionReset = new SessionResetMessage({ + preKeyBundle, + timestamp: Date.now(), + }); -export async function shouldProcessSessionRequest( - device: string, - messageTimestamp: number -): Promise { - const existingSentTimestamp = (await _getSentSessionRequest(device)) || 0; - const existingProcessedTimestamp = - (await _getProcessedSessionRequest(device)) || 0; - - return ( - messageTimestamp > existingSentTimestamp && - messageTimestamp > existingProcessedTimestamp - ); -} + return SessionProtocol.sendSessionRequest(sessionReset, device); + } -export async function onSessionRequestProcessed(device: string) { - return _updateProcessedSessionTimestamp(device, Date.now()); -} + /** */ + public static async sendSessionRequest( + message: SessionResetMessage, + device: string + ): Promise { + const timestamp = Date.now(); + + // mark the session as being pending send with current timestamp + // so we know we already triggered a new session with that device + SessionProtocol.pendingSendSessionsTimestamp.add(device); + // const rawMessage = toRawMessage(message); + // // TODO: Send out the request via MessageSender + + // try { + // await MessageSender.send(rawMessage); + // await SessionProtocolupdateSentSessionTimestamp(device, timestamp); + // } catch (e) { + // window.console.log('Failed to send session request to', device); + // } finally { + // SessionProtocolpendingSendSessionsTimestamp.delete(device); + // } + } -/** ======= local / utility functions ======= */ + /** + * Called when a session is establish so we store on database this info. + */ + public static async onSessionEstablished(device: string) { + // remove our existing sent timestamp for that device + return SessionProtocol.updateSentSessionTimestamp(device, undefined); + } -/** - * We only need to fetch once from the database, because we are the only one writing to it - */ -async function _fetchFromDBIfNeeded(): Promise { - if (!sentSessionsTimestamp) { - const sentItem = await getItemById( - 'sentSessionsTimestamp' + public static async shouldProcessSessionRequest( + device: string, + messageTimestamp: number + ): Promise { + const existingSentTimestamp = (await SessionProtocol.getSentSessionRequest(device)) || 0; + const existingProcessedTimestamp = + (await SessionProtocol.getProcessedSessionRequest(device)) || 0; + + return ( + messageTimestamp > existingSentTimestamp && + messageTimestamp > existingProcessedTimestamp ); - if (sentItem) { - sentSessionsTimestamp = sentItem.value; - } else { - sentSessionsTimestamp = {}; - } + } - const processedItem = await getItemById( - 'processedSessionsTimestamp' - ); - if (processedItem) { - processedSessionsTimestamp = processedItem.value; - } else { - processedSessionsTimestamp = {}; + public static async onSessionRequestProcessed(device: string) { + return SessionProtocol.updateProcessedSessionTimestamp(device, Date.now()); + } + + public static reset() { + SessionProtocol.dbLoaded = false; + SessionProtocol.sentSessionsTimestamp = {}; + SessionProtocol.processedSessionsTimestamp = {}; + } + + + /** + * We only need to fetch once from the database, because we are the only one writing to it + */ + private static async fetchFromDBIfNeeded(): Promise { + if (!SessionProtocol.dbLoaded) { + const sentItem = await getItemById( + 'sentSessionsTimestamp' + ); + if (sentItem) { + SessionProtocol.sentSessionsTimestamp = sentItem.value; + } else { + SessionProtocol.sentSessionsTimestamp = {}; + } + + const processedItem = await getItemById( + 'processedSessionsTimestamp' + ); + if (processedItem) { + SessionProtocol.processedSessionsTimestamp = processedItem.value; + } else { + SessionProtocol.processedSessionsTimestamp = {}; + } + SessionProtocol.dbLoaded = true; } } -} -async function _writeToDBSentSessions(): Promise { - const data = { - id: 'sentSessionsTimestamp', - value: JSON.stringify(sentSessionsTimestamp), - }; + private static async writeToDBSentSessions(): Promise { + const data = { + id: 'sentSessionsTimestamp', + value: JSON.stringify(SessionProtocol.sentSessionsTimestamp), + }; - await createOrUpdateItem(data); -} + await createOrUpdateItem(data); + } -async function _writeToDBProcessedSessions(): Promise { - const data = { - id: 'processedSessionsTimestamp', - value: JSON.stringify(processedSessionsTimestamp), - }; + private static async writeToDBProcessedSessions(): Promise { + const data = { + id: 'processedSessionsTimestamp', + value: JSON.stringify(SessionProtocol.processedSessionsTimestamp), + }; - await createOrUpdateItem(data); -} + await createOrUpdateItem(data); + } -/** - * This is a utility function to avoid duplicated code of _updateSentSessionTimestamp and _updateProcessedSessionTimestamp - */ -async function _updateSessionTimestamp( - device: string, - timestamp: number | undefined, - map: StringToNumberMap -): Promise { - await _fetchFromDBIfNeeded(); - if (!timestamp) { - if (!!map[device]) { - delete map.device; - - return true; + /** + * This is a utility function to avoid duplicated code of updateSentSessionTimestamp and updateProcessedSessionTimestamp + */ + private static async updateSessionTimestamp( + device: string, + timestamp: number | undefined, + map: StringToNumberMap + ): Promise { + await SessionProtocol.fetchFromDBIfNeeded(); + if (!timestamp) { + if (!!map[device]) { + delete map.device; + // FIXME double check how are args handle in ts (by ref/value) + return true; + } + + return false; } + map[device] = timestamp; - return false; + return true; } - map[device] = timestamp; - return true; -} - -/** - * - * @param device the device id - * @param timestamp undefined to remove the key/value pair, otherwise updates the sent timestamp and write to DB - */ -async function _updateSentSessionTimestamp( - device: string, - timestamp: number | undefined -): Promise { - if (_updateSessionTimestamp(device, timestamp, sentSessionsTimestamp)) { - await _writeToDBSentSessions(); + /** + * + * @param device the device id + * @param timestamp undefined to remove the key/value pair, otherwise updates the sent timestamp and write to DB + */ + private static async updateSentSessionTimestamp( + device: string, + timestamp: number | undefined + ): Promise { + if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.sentSessionsTimestamp)) { + await SessionProtocol.writeToDBSentSessions(); + } } -} -/** - * timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB - */ -async function _updateProcessedSessionTimestamp( - device: string, - timestamp: number | undefined -): Promise { - if (_updateSessionTimestamp(device, timestamp, processedSessionsTimestamp)) { - await _writeToDBProcessedSessions(); + /** + * timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB + */ + private static async updateProcessedSessionTimestamp( + device: string, + timestamp: number | undefined + ): Promise { + if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.processedSessionsTimestamp)) { + await SessionProtocol.writeToDBProcessedSessions(); + } } -} -/** - * This is a utility function to avoid duplicate code between `_getProcessedSessionRequest()` and `_getSentSessionRequest()` - */ -async function _getSessionRequest( - device: string, - map: StringToNumberMap -): Promise { - await _fetchFromDBIfNeeded(); + /** + * This is a utility function to avoid duplicate code between `getProcessedSessionRequest()` and `getSentSessionRequest()` + */ + private static async getSessionRequest( + device: string, + map: StringToNumberMap + ): Promise { + await SessionProtocol.fetchFromDBIfNeeded(); - return map[device]; -} + return map[device]; + } -async function _getSentSessionRequest( - device: string -): Promise { - return _getSessionRequest(device, sentSessionsTimestamp); -} + private static async getSentSessionRequest( + device: string + ): Promise { + return SessionProtocol.getSessionRequest(device, SessionProtocol.sentSessionsTimestamp); + } -async function _getProcessedSessionRequest( - device: string -): Promise { - return _getSessionRequest(device, processedSessionsTimestamp); -} + private static async getProcessedSessionRequest( + device: string + ): Promise { + return SessionProtocol.getSessionRequest(device, SessionProtocol.processedSessionsTimestamp); + } -async function _hasSentSessionRequest(device: string): Promise { - await _fetchFromDBIfNeeded(); + private static async _hasSentSessionRequest(device: string): Promise { + await SessionProtocol.fetchFromDBIfNeeded(); - return !!sentSessionsTimestamp[device]; + return !!SessionProtocol.sentSessionsTimestamp[device]; + } } diff --git a/ts/session/protocols/index.ts b/ts/session/protocols/index.ts index e0cfeb680..dbf3e0fd0 100644 --- a/ts/session/protocols/index.ts +++ b/ts/session/protocols/index.ts @@ -1,4 +1,4 @@ -import * as SessionProtocol from './SessionProtocol'; +import {SessionProtocol} from './SessionProtocol'; import * as MultiDeviceProtocol from './MultiDeviceProtocol'; export { SessionProtocol, MultiDeviceProtocol }; From dc00923f17b4a09c8165ecb840fd401dc2feaec2 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 9 Jun 2020 11:31:36 +1000 Subject: [PATCH 3/9] add SessionProtocol test --- ts/session/protocols/SessionProtocol.ts | 123 +++++-- ts/session/protocols/index.ts | 2 +- .../session/crypto/MessageEncrypter_test.ts | 1 + .../session/protocols/SessionProtocol_test.ts | 343 +++++++++++++++++- .../stubs/ciphers/SessionCipherStub.ts | 4 + 5 files changed, 430 insertions(+), 43 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index e357e46a3..62b0b42bf 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -1,6 +1,11 @@ import { SessionResetMessage } from '../messages/outgoing'; // import { MessageSender } from '../sending'; import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; +import { libloki, libsignal, textsecure } from '../../window'; +import { MessageSender } from '../sending'; +import { RawMessage } from '../types/RawMessage'; +import { EncryptionType } from '../types/EncryptionType'; +import { TextEncoder } from 'util'; interface StringToNumberMap { [key: string]: number; @@ -8,17 +13,15 @@ interface StringToNumberMap { // tslint:disable: function-name // tslint:disable: no-unnecessary-class export class SessionProtocol { - private static dbLoaded: Boolean = false; /** * This map olds the sent session timestamps, i.e. session requests message effectively sent to the recipient. * It is backed by a database entry so it's loaded from db on startup. * This map should not be used directly, but instead through - * `updateSendSessionTimestamp()`, `getSendSessionRequest()` or `hasSendSessionRequest()` + * `updateSendSessionTimestamp()`, or `hasSendSessionRequest()` */ private static sentSessionsTimestamp: StringToNumberMap; - /** * This map olds the processed session timestamps, i.e. when we received a session request and handled it. * It is backed by a database entry so it's loaded from db on startup. @@ -34,13 +37,24 @@ export class SessionProtocol { */ private static readonly pendingSendSessionsTimestamp: Set = new Set(); + public static getSentSessionsTimestamp(): Readonly { + return SessionProtocol.sentSessionsTimestamp; + } + + public static getProcessedSessionsTimestamp(): Readonly { + return SessionProtocol.processedSessionsTimestamp; + } + + public static getPendingSendSessionTimestamp(): Readonly> { + return SessionProtocol.pendingSendSessionsTimestamp; + } /** Returns true if we already have a session with that device */ public static async hasSession(device: string): Promise { // Session does not use the concept of a deviceId, thus it's always 1 - const address = new window.libsignal.SignalProtocolAddress(device, 1); - const sessionCipher = new window.libsignal.SessionCipher( - window.textsecure.storage.protocol, + const address = new libsignal.SignalProtocolAddress(device, 1); + const sessionCipher = new libsignal.SessionCipher( + textsecure.storage.protocol, address ); @@ -52,7 +66,9 @@ export class SessionProtocol { * if a session request to that device is right now being sent. */ public static async hasSentSessionRequest(device: string): Promise { - const pendingSend = SessionProtocol.pendingSendSessionsTimestamp.has(device); + const pendingSend = SessionProtocol.pendingSendSessionsTimestamp.has( + device + ); const hasSent = await SessionProtocol._hasSentSessionRequest(device); return pendingSend || hasSent; @@ -67,13 +83,17 @@ export class SessionProtocol { public static async sendSessionRequestIfNeeded( device: string ): Promise { - if (SessionProtocol.hasSession(device) || SessionProtocol.hasSentSessionRequest(device)) { + if ( + (await SessionProtocol.hasSession(device)) || + (await SessionProtocol.hasSentSessionRequest(device)) + ) { return Promise.resolve(); } - const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact( + const preKeyBundle = await libloki.storage.getPreKeyBundleForContact( device ); + const sessionReset = new SessionResetMessage({ preKeyBundle, timestamp: Date.now(), @@ -92,17 +112,30 @@ export class SessionProtocol { // mark the session as being pending send with current timestamp // so we know we already triggered a new session with that device SessionProtocol.pendingSendSessionsTimestamp.add(device); - // const rawMessage = toRawMessage(message); - // // TODO: Send out the request via MessageSender - - // try { - // await MessageSender.send(rawMessage); - // await SessionProtocolupdateSentSessionTimestamp(device, timestamp); - // } catch (e) { - // window.console.log('Failed to send session request to', device); - // } finally { - // SessionProtocolpendingSendSessionsTimestamp.delete(device); - // } + + // FIXME to remove + function toRawMessage(m: any): RawMessage { + return { + identifier: 'identifier', + plainTextBuffer: new TextEncoder().encode('jk'), + timestamp: Date.now(), + device: 'device', + ttl: 10, + encryption: EncryptionType.SessionReset, + }; + } + + try { + // TODO: Send out the request via MessageSender + const rawMessage = toRawMessage(message); + await MessageSender.send(rawMessage); + await SessionProtocol.updateSentSessionTimestamp(device, timestamp); + } catch (e) { + console.log('Failed to send session request to:', device); + console.log('e:', e); + } finally { + SessionProtocol.pendingSendSessionsTimestamp.delete(device); + } } /** @@ -117,7 +150,8 @@ export class SessionProtocol { device: string, messageTimestamp: number ): Promise { - const existingSentTimestamp = (await SessionProtocol.getSentSessionRequest(device)) || 0; + const existingSentTimestamp = + (await SessionProtocol.getSentSessionRequest(device)) || 0; const existingProcessedTimestamp = (await SessionProtocol.getProcessedSessionRequest(device)) || 0; @@ -137,24 +171,19 @@ export class SessionProtocol { SessionProtocol.processedSessionsTimestamp = {}; } - /** * We only need to fetch once from the database, because we are the only one writing to it */ private static async fetchFromDBIfNeeded(): Promise { if (!SessionProtocol.dbLoaded) { - const sentItem = await getItemById( - 'sentSessionsTimestamp' - ); + const sentItem = await getItemById('sentSessionsTimestamp'); if (sentItem) { SessionProtocol.sentSessionsTimestamp = sentItem.value; } else { SessionProtocol.sentSessionsTimestamp = {}; } - const processedItem = await getItemById( - 'processedSessionsTimestamp' - ); + const processedItem = await getItemById('processedSessionsTimestamp'); if (processedItem) { SessionProtocol.processedSessionsTimestamp = processedItem.value; } else { @@ -190,11 +219,11 @@ export class SessionProtocol { timestamp: number | undefined, map: StringToNumberMap ): Promise { - await SessionProtocol.fetchFromDBIfNeeded(); if (!timestamp) { if (!!map[device]) { - delete map.device; - // FIXME double check how are args handle in ts (by ref/value) + // tslint:disable-next-line: no-dynamic-delete + delete map[device]; + return true; } @@ -214,7 +243,14 @@ export class SessionProtocol { device: string, timestamp: number | undefined ): Promise { - if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.sentSessionsTimestamp)) { + await SessionProtocol.fetchFromDBIfNeeded(); + if ( + SessionProtocol.updateSessionTimestamp( + device, + timestamp, + SessionProtocol.sentSessionsTimestamp + ) + ) { await SessionProtocol.writeToDBSentSessions(); } } @@ -226,7 +262,14 @@ export class SessionProtocol { device: string, timestamp: number | undefined ): Promise { - if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.processedSessionsTimestamp)) { + await SessionProtocol.fetchFromDBIfNeeded(); + if ( + SessionProtocol.updateSessionTimestamp( + device, + timestamp, + SessionProtocol.processedSessionsTimestamp + ) + ) { await SessionProtocol.writeToDBProcessedSessions(); } } @@ -246,16 +289,24 @@ export class SessionProtocol { private static async getSentSessionRequest( device: string ): Promise { - return SessionProtocol.getSessionRequest(device, SessionProtocol.sentSessionsTimestamp); + return SessionProtocol.getSessionRequest( + device, + SessionProtocol.sentSessionsTimestamp + ); } private static async getProcessedSessionRequest( device: string ): Promise { - return SessionProtocol.getSessionRequest(device, SessionProtocol.processedSessionsTimestamp); + return SessionProtocol.getSessionRequest( + device, + SessionProtocol.processedSessionsTimestamp + ); } - private static async _hasSentSessionRequest(device: string): Promise { + private static async _hasSentSessionRequest( + device: string + ): Promise { await SessionProtocol.fetchFromDBIfNeeded(); return !!SessionProtocol.sentSessionsTimestamp[device]; diff --git a/ts/session/protocols/index.ts b/ts/session/protocols/index.ts index dbf3e0fd0..38fe021c0 100644 --- a/ts/session/protocols/index.ts +++ b/ts/session/protocols/index.ts @@ -1,4 +1,4 @@ -import {SessionProtocol} from './SessionProtocol'; +import { SessionProtocol } from './SessionProtocol'; import * as MultiDeviceProtocol from './MultiDeviceProtocol'; export { SessionProtocol, MultiDeviceProtocol }; diff --git a/ts/test/session/crypto/MessageEncrypter_test.ts b/ts/test/session/crypto/MessageEncrypter_test.ts index 5d6b4155d..be53909eb 100644 --- a/ts/test/session/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/crypto/MessageEncrypter_test.ts @@ -7,6 +7,7 @@ import { Stubs, TestUtils } from '../../test-utils'; import { UserUtil } from '../../../util'; import { SignalService } from '../../../protobuf'; +// tslint:disable-next-line: max-func-body-length describe('MessageEncrypter', () => { const sandbox = sinon.createSandbox(); const ourNumber = 'ourNumber'; diff --git a/ts/test/session/protocols/SessionProtocol_test.ts b/ts/test/session/protocols/SessionProtocol_test.ts index 8cd26ddfc..a7b828912 100644 --- a/ts/test/session/protocols/SessionProtocol_test.ts +++ b/ts/test/session/protocols/SessionProtocol_test.ts @@ -1,9 +1,340 @@ -// import { expect } from 'chai'; -// import { SessionProtocol } from '../../../session/protocols'; +import { expect } from 'chai'; +import { SessionProtocol } from '../../../session/protocols'; +import * as sinon from 'sinon'; +import { Stubs, TestUtils, timeout } from '../../test-utils'; +import { UserUtil } from '../../../util'; +import { SessionResetMessage } from '../../../session/messages/outgoing'; +import { TextEncoder } from 'util'; +import { MessageSender } from '../../../session/sending'; -// describe('SessionProtocol', () => { -// it('has ', () => { +// tslint:disable-next-line: max-func-body-length +describe('SessionProtocol', () => { + const sandbox = sinon.createSandbox(); + const ourNumber = 'ourNumber'; + let getItemById: sinon.SinonStub; + let send: sinon.SinonStub; -// }); + const resetMessage: SessionResetMessage = new SessionResetMessage({ + timestamp: Date.now(), + preKeyBundle: { + identityKey: new TextEncoder().encode('identityKey'), + deviceId: 1, + preKeyId: 2, + signedKeyId: 3, + preKey: new TextEncoder().encode('preKey'), + signedKey: new TextEncoder().encode('signedKey'), + signature: new TextEncoder().encode('signature'), + }, + }); -// }); + beforeEach(() => { + TestUtils.stubWindow('libsignal', { + SignalProtocolAddress: sandbox.stub(), + SessionCipher: Stubs.SessionCipherStub, + } as any); + + TestUtils.stubWindow('libloki', { + storage: { + getPreKeyBundleForContact: sandbox.stub(), + }, + }); + + TestUtils.stubWindow('textsecure', { + storage: { + protocol: sandbox.stub(), + }, + }); + + TestUtils.stubData('createOrUpdateItem'); + + getItemById = TestUtils.stubData('getItemById').resolves({ value: {} }); + + sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber); + send = sandbox.stub(MessageSender, 'send' as any); + SessionProtocol.reset(); + }); + + afterEach(() => { + sandbox.restore(); + TestUtils.restoreStubs(); + }); + + describe('db fetch', () => { + it('protocol: should fetch from DB `sentSessionsTimestamp` and `processedSessionsTimestamp`', async () => { + await SessionProtocol.hasSentSessionRequest('test'); + expect(getItemById.calledWith('sentSessionsTimestamp')); + expect(getItemById.calledWith('processedSessionsTimestamp')); + expect(getItemById.callCount).to.equal(2); + }); + + it('protocol: should fetch only once', async () => { + await SessionProtocol.hasSentSessionRequest('test'); + await SessionProtocol.hasSentSessionRequest('test'); + await SessionProtocol.hasSentSessionRequest('test'); + await SessionProtocol.hasSentSessionRequest('test'); + expect(getItemById.calledWith('sentSessionsTimestamp')); + expect(getItemById.calledWith('processedSessionsTimestamp')); + expect(getItemById.callCount).to.equal(2); + }); + }); + + describe('sendSessionRequest', () => { + beforeEach(async () => { + // trigger a sessionReset + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceID'); + }); + + it('protocol: sendSessionRequest should add the deviceID to the sentMap', async () => { + expect(SessionProtocol.getSentSessionsTimestamp()) + .to.have.property('deviceID') + .to.be.approximately(Date.now(), 100); + }); + + it('protocol: sendSessionRequest should not have pendingSend set after', async () => { + expect( + SessionProtocol.getPendingSendSessionTimestamp() + ).to.not.have.property('deviceID'); + }); + }); + + describe('onSessionEstablished', () => { + beforeEach(async () => { + // add an existing entry in the sentMap + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); + }); + + it('protocol: onSessionEstablished should remove the device in sentTimestamps', async () => { + expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( + 'deviceid' + ); + await SessionProtocol.onSessionEstablished('deviceid'); + expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property( + 'deviceid' + ); + }); + + it('protocol: onSessionEstablished should remove the device in sentTimestamps and ONLY that one', async () => { + // add a second item to the map + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid2'); + + expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( + 'deviceid' + ); + expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( + 'deviceid2' + ); + + await SessionProtocol.onSessionEstablished('deviceid'); + expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property( + 'deviceid' + ); + expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( + 'deviceid2' + ); + }); + }); + + describe('hasSentSessionRequest', () => { + it('protocol: hasSentSessionRequest returns false if a message was not sent to that device', async () => { + const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + expect(hasSent).to.be.equal( + false, + 'hasSent should be false for `deviceid`' + ); + }); + + it('protocol: hasSentSessionRequest returns true if a message is already sent for that device', async () => { + // add an existing entry in the sentMap + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); + const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + expect(hasSent).to.be.equal( + true, + 'hasSent should be true for `deviceid`' + ); + }); + + // TODO add a test to validate that pending is filled when message is triggered and not yet sent + }); + + describe('sendSessionRequestIfNeeded', () => { + it('protocol: sendSessionRequestIfNeeded should send a new sessionMessage ', async () => { + // not called before, so the message reset sending should be triggered + await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + expect(send.callCount).to.be.equal( + 1, + 'MessageSender.send() should have been called' + ); + + // check that the map is updated with that ID + const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + expect(hasSent).to.be.equal( + true, + 'hasSent should be true for `deviceid`' + ); + }); + + it('protocol: sendSessionRequestIfNeeded should NOT send a new sessionMessage on second try ', async () => { + await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + expect(send.callCount).to.be.equal( + 1, + 'MessageSender.send() should have been called' + ); + + // check that the map is updated with that ID + const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + expect(hasSent).to.be.equal( + true, + 'hasSent should be true for `deviceid`' + ); + send.resetHistory(); + + // trigger a second call, Message.send().calledCount should still be 1 + await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + expect(send.callCount).to.be.equal( + 0, + 'MessageSender.send() should NOT have been called a second time' + ); + }); + }); + + it('hasSession: returns false as it is stubbed', () => { + expect(SessionProtocol.hasSession('deviceID')).to.be.equal( + false, + 'hasSession() should be false as it is stubbed' + ); + }); + + describe('onSessionRequestProcessed', () => { + it('protocol: onSessionRequestProcessed should insert a new item in the processedMap ', async () => { + // trigger the requestProcessed and check the map is updated + await SessionProtocol.onSessionRequestProcessed('deviceid'); + expect(SessionProtocol.getProcessedSessionsTimestamp()) + .to.have.property('deviceid') + .to.be.approximately(Date.now(), 5); + }); + + it('protocol: onSessionRequestProcessed should update an existing item in the processedMap ', async () => { + // trigger the requestProcessed and check the map is updated + // then trigger it a second time, and expect a change in the processed timestamp + + await SessionProtocol.onSessionRequestProcessed('deviceid'); + expect(SessionProtocol.getProcessedSessionsTimestamp()) + .to.have.property('deviceid') + .to.be.approximately(Date.now(), 5); + await timeout(5); + const oldTimestamp = SessionProtocol.getProcessedSessionsTimestamp() + .deviceid; + await SessionProtocol.onSessionRequestProcessed('deviceid'); + expect(SessionProtocol.getProcessedSessionsTimestamp()) + .to.have.property('deviceid') + .to.be.approximately(Date.now(), 5) + .to.not.be.equal(oldTimestamp); + }); + }); + + describe('shouldProcessSessionRequest', () => { + it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than processed timestamp', async () => { + await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + expect( + SessionProtocol.shouldProcessSessionRequest( + 'deviceid', + Date.now() + 1000 + ) + ).to.be.eventually.equal( + true, + 'shouldProcessSessionRequest should return true when existingProcessed is less recent' + ); + }); + + it('protocol: shouldProcessSessionRequest returns true if there is no processed timestamp yet for this device', async () => { + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + ).to.be.eventually.equal( + true, + 'shouldProcessSessionRequest should return false when existingProcessed is empty for this device' + ); + }); + + it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current processed timestamp', async () => { + await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + ).to.be.eventually.equal( + false, + 'shouldProcessSessionRequest should return false when existingProcessed is more recent' + ); + }); + + it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current sent timestamp', async () => { + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + ).to.be.eventually.equal( + false, + 'shouldProcessSessionRequest should return false when existingSent is more recent' + ); + }); + + it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than current sent timestamp', async () => { + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + expect( + SessionProtocol.shouldProcessSessionRequest( + 'deviceid', + Date.now() + 1000 + ) + ).to.be.eventually.equal( + true, + 'shouldProcessSessionRequest should return true when existingSent is less recent' + ); + }); + + it('protocol: shouldProcessSessionRequest returns true if there is no sent timestamp', async () => { + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + ).to.be.eventually.equal( + true, + 'shouldProcessSessionRequest should return true as there is no sent timestamp' + ); + }); + + it('protocol: shouldProcessSessionRequest returns false if there is a more recent sent but a less recent processed', async () => { + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + await timeout(100); + await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry 100ms after + + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', Date.now() - 50) + ).to.be.eventually.equal( + false, + 'shouldProcessSessionRequest should return false if there is a more recent sent but a less recent processed' + ); + }); + + it('protocol: shouldProcessSessionRequest returns false if there is a more recent processed but a less recent sent', async () => { + await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + await timeout(100); + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry 100ms after + + expect( + SessionProtocol.shouldProcessSessionRequest('deviceid', Date.now() - 50) + ).to.be.eventually.equal( + false, + 'shouldProcessSessionRequest should return false if there is a more recent processed but a less recent sent' + ); + }); + + it('protocol: shouldProcessSessionRequest returns true if both sent and processed timestamp are older', async () => { + await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + expect( + SessionProtocol.shouldProcessSessionRequest( + 'deviceid', + Date.now() + 1000 + ) + ).to.be.eventually.equal( + true, + 'shouldProcessSessionRequest should return true if there if both processed and sent are set but are older' + ); + }); + }); +}); diff --git a/ts/test/test-utils/stubs/ciphers/SessionCipherStub.ts b/ts/test/test-utils/stubs/ciphers/SessionCipherStub.ts index 3d08bebfe..14f4daf88 100644 --- a/ts/test/test-utils/stubs/ciphers/SessionCipherStub.ts +++ b/ts/test/test-utils/stubs/ciphers/SessionCipherStub.ts @@ -17,4 +17,8 @@ export class SessionCipherStub { body: Buffer.from(buffer).toString('binary'), }; } + + public async hasOpenSession(): Promise { + return false; + } } From 886f279540629c8722aebc6fd2e514dbfbf18cb0 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 9 Jun 2020 12:12:37 +1000 Subject: [PATCH 4/9] hide errors of sendSessionRequest message.send() --- ts/session/protocols/SessionProtocol.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index 62b0b42bf..d849a6c32 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -131,8 +131,8 @@ export class SessionProtocol { await MessageSender.send(rawMessage); await SessionProtocol.updateSentSessionTimestamp(device, timestamp); } catch (e) { - console.log('Failed to send session request to:', device); - console.log('e:', e); + // console.log('Failed to send session request to:', device); + // console.log('e:', e); } finally { SessionProtocol.pendingSendSessionsTimestamp.delete(device); } From 73c4020daf3ab8fd4a440e673d711682528db7e3 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 9 Jun 2020 14:01:24 +1000 Subject: [PATCH 5/9] throw error on sendSessionRequest and log it in sendSessionRequestIfNeeded --- ts/session/protocols/SessionProtocol.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index d849a6c32..33931ee12 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -99,7 +99,11 @@ export class SessionProtocol { timestamp: Date.now(), }); - return SessionProtocol.sendSessionRequest(sessionReset, device); + try { + await SessionProtocol.sendSessionRequest(sessionReset, device); + } catch (error) { + window.console.warn('Failed to send session request to:', device, error); + } } /** */ @@ -131,8 +135,7 @@ export class SessionProtocol { await MessageSender.send(rawMessage); await SessionProtocol.updateSentSessionTimestamp(device, timestamp); } catch (e) { - // console.log('Failed to send session request to:', device); - // console.log('e:', e); + throw e; } finally { SessionProtocol.pendingSendSessionsTimestamp.delete(device); } From 7031328eea35a14fac6351cbc8099fd66f381b4c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 9 Jun 2020 14:25:36 +1000 Subject: [PATCH 6/9] remove useless hasSession test --- ts/test/session/protocols/SessionProtocol_test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ts/test/session/protocols/SessionProtocol_test.ts b/ts/test/session/protocols/SessionProtocol_test.ts index a7b828912..3e5889ded 100644 --- a/ts/test/session/protocols/SessionProtocol_test.ts +++ b/ts/test/session/protocols/SessionProtocol_test.ts @@ -197,13 +197,6 @@ describe('SessionProtocol', () => { }); }); - it('hasSession: returns false as it is stubbed', () => { - expect(SessionProtocol.hasSession('deviceID')).to.be.equal( - false, - 'hasSession() should be false as it is stubbed' - ); - }); - describe('onSessionRequestProcessed', () => { it('protocol: onSessionRequestProcessed should insert a new item in the processedMap ', async () => { // trigger the requestProcessed and check the map is updated From 2b58aff532f1ed3b2c9524a90baa13f831f3b864 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 9 Jun 2020 16:30:09 +1000 Subject: [PATCH 7/9] use pubkeys and toRawMessage() --- ts/session/protocols/SessionProtocol.ts | 51 ++++++----- .../session/protocols/SessionProtocol_test.ts | 86 ++++++++++--------- 2 files changed, 68 insertions(+), 69 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index fbf531a09..8a4173538 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -3,10 +3,8 @@ import { SessionResetMessage } from '../messages/outgoing'; import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; import { libloki, libsignal, textsecure } from '../../window'; import { MessageSender } from '../sending'; -import { RawMessage } from '../types/RawMessage'; -import { EncryptionType } from '../types/EncryptionType'; -import { TextEncoder } from 'util'; import * as MessageUtils from '../utils'; +import { PubKey } from '../types'; interface StringToNumberMap { [key: string]: number; @@ -51,9 +49,9 @@ export class SessionProtocol { } /** Returns true if we already have a session with that device */ - public static async hasSession(device: string): Promise { + public static async hasSession(pubkey: PubKey): Promise { // Session does not use the concept of a deviceId, thus it's always 1 - const address = new libsignal.SignalProtocolAddress(device, 1); + const address = new libsignal.SignalProtocolAddress(pubkey.key, 1); const sessionCipher = new libsignal.SessionCipher( textsecure.storage.protocol, address @@ -66,11 +64,11 @@ export class SessionProtocol { * Returns true if we sent a session request to that device already OR * if a session request to that device is right now being sent. */ - public static async hasSentSessionRequest(device: string): Promise { + public static async hasSentSessionRequest(pubkey: PubKey): Promise { const pendingSend = SessionProtocol.pendingSendSessionsTimestamp.has( - device + pubkey.key ); - const hasSent = await SessionProtocol._hasSentSessionRequest(device); + const hasSent = await SessionProtocol._hasSentSessionRequest(pubkey.key); return pendingSend || hasSent; } @@ -82,17 +80,17 @@ export class SessionProtocol { * - we do not have a session request currently being send to that device */ public static async sendSessionRequestIfNeeded( - device: string + pubkey: PubKey ): Promise { if ( - (await SessionProtocol.hasSession(device)) || - (await SessionProtocol.hasSentSessionRequest(device)) + (await SessionProtocol.hasSession(pubkey)) || + (await SessionProtocol.hasSentSessionRequest(pubkey)) ) { return Promise.resolve(); } const preKeyBundle = await libloki.storage.getPreKeyBundleForContact( - device + pubkey.key ); const sessionReset = new SessionResetMessage({ @@ -101,51 +99,50 @@ export class SessionProtocol { }); try { - await SessionProtocol.sendSessionRequest(sessionReset, device); + await SessionProtocol.sendSessionRequest(sessionReset, pubkey); } catch (error) { - window.console.warn('Failed to send session request to:', device, error); + window.console.warn('Failed to send session request to:', pubkey.key, error); } } /** */ public static async sendSessionRequest( message: SessionResetMessage, - device: string + pubkey: PubKey ): Promise { const timestamp = Date.now(); // mark the session as being pending send with current timestamp // so we know we already triggered a new session with that device - SessionProtocol.pendingSendSessionsTimestamp.add(device); + SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key); try { - // TODO: Send out the request via MessageSender - const rawMessage = MessageUtils.toRawMessage(device, message); + const rawMessage = MessageUtils.toRawMessage(pubkey, message); await MessageSender.send(rawMessage); - await SessionProtocol.updateSentSessionTimestamp(device, timestamp); + await SessionProtocol.updateSentSessionTimestamp(pubkey.key, timestamp); } catch (e) { throw e; } finally { - SessionProtocol.pendingSendSessionsTimestamp.delete(device); + SessionProtocol.pendingSendSessionsTimestamp.delete(pubkey.key); } } /** * Called when a session is establish so we store on database this info. */ - public static async onSessionEstablished(device: string) { + public static async onSessionEstablished(pubkey: PubKey) { // remove our existing sent timestamp for that device - return SessionProtocol.updateSentSessionTimestamp(device, undefined); + return SessionProtocol.updateSentSessionTimestamp(pubkey.key, undefined); } public static async shouldProcessSessionRequest( - device: string, + pubkey: PubKey, messageTimestamp: number ): Promise { const existingSentTimestamp = - (await SessionProtocol.getSentSessionRequest(device)) || 0; + (await SessionProtocol.getSentSessionRequest(pubkey.key)) || 0; const existingProcessedTimestamp = - (await SessionProtocol.getProcessedSessionRequest(device)) || 0; + (await SessionProtocol.getProcessedSessionRequest(pubkey.key)) || 0; return ( messageTimestamp > existingSentTimestamp && @@ -153,8 +150,8 @@ export class SessionProtocol { ); } - public static async onSessionRequestProcessed(device: string) { - return SessionProtocol.updateProcessedSessionTimestamp(device, Date.now()); + public static async onSessionRequestProcessed(pubkey: PubKey) { + return SessionProtocol.updateProcessedSessionTimestamp(pubkey.key, Date.now()); } public static reset() { diff --git a/ts/test/session/protocols/SessionProtocol_test.ts b/ts/test/session/protocols/SessionProtocol_test.ts index 3e5889ded..71940c9bd 100644 --- a/ts/test/session/protocols/SessionProtocol_test.ts +++ b/ts/test/session/protocols/SessionProtocol_test.ts @@ -6,11 +6,13 @@ import { UserUtil } from '../../../util'; import { SessionResetMessage } from '../../../session/messages/outgoing'; import { TextEncoder } from 'util'; import { MessageSender } from '../../../session/sending'; +import { PubKey } from '../../../session/types'; // tslint:disable-next-line: max-func-body-length describe('SessionProtocol', () => { const sandbox = sinon.createSandbox(); const ourNumber = 'ourNumber'; + const pubkey = new PubKey('deviceid'); let getItemById: sinon.SinonStub; let send: sinon.SinonStub; @@ -61,17 +63,17 @@ describe('SessionProtocol', () => { describe('db fetch', () => { it('protocol: should fetch from DB `sentSessionsTimestamp` and `processedSessionsTimestamp`', async () => { - await SessionProtocol.hasSentSessionRequest('test'); + await SessionProtocol.hasSentSessionRequest(pubkey); expect(getItemById.calledWith('sentSessionsTimestamp')); expect(getItemById.calledWith('processedSessionsTimestamp')); expect(getItemById.callCount).to.equal(2); }); it('protocol: should fetch only once', async () => { - await SessionProtocol.hasSentSessionRequest('test'); - await SessionProtocol.hasSentSessionRequest('test'); - await SessionProtocol.hasSentSessionRequest('test'); - await SessionProtocol.hasSentSessionRequest('test'); + await SessionProtocol.hasSentSessionRequest(pubkey); + await SessionProtocol.hasSentSessionRequest(pubkey); + await SessionProtocol.hasSentSessionRequest(pubkey); + await SessionProtocol.hasSentSessionRequest(pubkey); expect(getItemById.calledWith('sentSessionsTimestamp')); expect(getItemById.calledWith('processedSessionsTimestamp')); expect(getItemById.callCount).to.equal(2); @@ -81,33 +83,33 @@ describe('SessionProtocol', () => { describe('sendSessionRequest', () => { beforeEach(async () => { // trigger a sessionReset - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceID'); + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); }); it('protocol: sendSessionRequest should add the deviceID to the sentMap', async () => { expect(SessionProtocol.getSentSessionsTimestamp()) - .to.have.property('deviceID') + .to.have.property('deviceid') .to.be.approximately(Date.now(), 100); }); it('protocol: sendSessionRequest should not have pendingSend set after', async () => { expect( SessionProtocol.getPendingSendSessionTimestamp() - ).to.not.have.property('deviceID'); + ).to.not.have.property('deviceid'); }); }); describe('onSessionEstablished', () => { beforeEach(async () => { // add an existing entry in the sentMap - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); }); it('protocol: onSessionEstablished should remove the device in sentTimestamps', async () => { expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( 'deviceid' ); - await SessionProtocol.onSessionEstablished('deviceid'); + await SessionProtocol.onSessionEstablished(pubkey); expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property( 'deviceid' ); @@ -115,7 +117,7 @@ describe('SessionProtocol', () => { it('protocol: onSessionEstablished should remove the device in sentTimestamps and ONLY that one', async () => { // add a second item to the map - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid2'); + await SessionProtocol.sendSessionRequest(resetMessage, new PubKey('deviceid2')); expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( 'deviceid' @@ -124,7 +126,7 @@ describe('SessionProtocol', () => { 'deviceid2' ); - await SessionProtocol.onSessionEstablished('deviceid'); + await SessionProtocol.onSessionEstablished(pubkey); expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property( 'deviceid' ); @@ -136,7 +138,7 @@ describe('SessionProtocol', () => { describe('hasSentSessionRequest', () => { it('protocol: hasSentSessionRequest returns false if a message was not sent to that device', async () => { - const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey); expect(hasSent).to.be.equal( false, 'hasSent should be false for `deviceid`' @@ -145,8 +147,8 @@ describe('SessionProtocol', () => { it('protocol: hasSentSessionRequest returns true if a message is already sent for that device', async () => { // add an existing entry in the sentMap - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); - const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); + const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey); expect(hasSent).to.be.equal( true, 'hasSent should be true for `deviceid`' @@ -159,14 +161,14 @@ describe('SessionProtocol', () => { describe('sendSessionRequestIfNeeded', () => { it('protocol: sendSessionRequestIfNeeded should send a new sessionMessage ', async () => { // not called before, so the message reset sending should be triggered - await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + await SessionProtocol.sendSessionRequestIfNeeded(pubkey); expect(send.callCount).to.be.equal( 1, 'MessageSender.send() should have been called' ); // check that the map is updated with that ID - const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey); expect(hasSent).to.be.equal( true, 'hasSent should be true for `deviceid`' @@ -174,14 +176,14 @@ describe('SessionProtocol', () => { }); it('protocol: sendSessionRequestIfNeeded should NOT send a new sessionMessage on second try ', async () => { - await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + await SessionProtocol.sendSessionRequestIfNeeded(pubkey); expect(send.callCount).to.be.equal( 1, 'MessageSender.send() should have been called' ); // check that the map is updated with that ID - const hasSent = await SessionProtocol.hasSentSessionRequest('deviceid'); + const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey); expect(hasSent).to.be.equal( true, 'hasSent should be true for `deviceid`' @@ -189,7 +191,7 @@ describe('SessionProtocol', () => { send.resetHistory(); // trigger a second call, Message.send().calledCount should still be 1 - await SessionProtocol.sendSessionRequestIfNeeded('deviceid'); + await SessionProtocol.sendSessionRequestIfNeeded(pubkey); expect(send.callCount).to.be.equal( 0, 'MessageSender.send() should NOT have been called a second time' @@ -200,7 +202,7 @@ describe('SessionProtocol', () => { describe('onSessionRequestProcessed', () => { it('protocol: onSessionRequestProcessed should insert a new item in the processedMap ', async () => { // trigger the requestProcessed and check the map is updated - await SessionProtocol.onSessionRequestProcessed('deviceid'); + await SessionProtocol.onSessionRequestProcessed(pubkey); expect(SessionProtocol.getProcessedSessionsTimestamp()) .to.have.property('deviceid') .to.be.approximately(Date.now(), 5); @@ -210,14 +212,14 @@ describe('SessionProtocol', () => { // trigger the requestProcessed and check the map is updated // then trigger it a second time, and expect a change in the processed timestamp - await SessionProtocol.onSessionRequestProcessed('deviceid'); + await SessionProtocol.onSessionRequestProcessed(pubkey); expect(SessionProtocol.getProcessedSessionsTimestamp()) .to.have.property('deviceid') .to.be.approximately(Date.now(), 5); await timeout(5); const oldTimestamp = SessionProtocol.getProcessedSessionsTimestamp() .deviceid; - await SessionProtocol.onSessionRequestProcessed('deviceid'); + await SessionProtocol.onSessionRequestProcessed(pubkey); expect(SessionProtocol.getProcessedSessionsTimestamp()) .to.have.property('deviceid') .to.be.approximately(Date.now(), 5) @@ -227,10 +229,10 @@ describe('SessionProtocol', () => { describe('shouldProcessSessionRequest', () => { it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than processed timestamp', async () => { - await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry expect( SessionProtocol.shouldProcessSessionRequest( - 'deviceid', + pubkey, Date.now() + 1000 ) ).to.be.eventually.equal( @@ -241,7 +243,7 @@ describe('SessionProtocol', () => { it('protocol: shouldProcessSessionRequest returns true if there is no processed timestamp yet for this device', async () => { expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + SessionProtocol.shouldProcessSessionRequest(pubkey, 100) ).to.be.eventually.equal( true, 'shouldProcessSessionRequest should return false when existingProcessed is empty for this device' @@ -249,9 +251,9 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current processed timestamp', async () => { - await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + SessionProtocol.shouldProcessSessionRequest(pubkey, 100) ).to.be.eventually.equal( false, 'shouldProcessSessionRequest should return false when existingProcessed is more recent' @@ -259,9 +261,9 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns false if timestamp is less recent than current sent timestamp', async () => { - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + SessionProtocol.shouldProcessSessionRequest(pubkey, 100) ).to.be.eventually.equal( false, 'shouldProcessSessionRequest should return false when existingSent is more recent' @@ -269,10 +271,10 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than current sent timestamp', async () => { - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry expect( SessionProtocol.shouldProcessSessionRequest( - 'deviceid', + pubkey, Date.now() + 1000 ) ).to.be.eventually.equal( @@ -283,7 +285,7 @@ describe('SessionProtocol', () => { it('protocol: shouldProcessSessionRequest returns true if there is no sent timestamp', async () => { expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', 100) + SessionProtocol.shouldProcessSessionRequest(pubkey, 100) ).to.be.eventually.equal( true, 'shouldProcessSessionRequest should return true as there is no sent timestamp' @@ -291,12 +293,12 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns false if there is a more recent sent but a less recent processed', async () => { - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry await timeout(100); - await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry 100ms after + await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry 100ms after expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', Date.now() - 50) + SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() - 50) ).to.be.eventually.equal( false, 'shouldProcessSessionRequest should return false if there is a more recent sent but a less recent processed' @@ -304,12 +306,12 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns false if there is a more recent processed but a less recent sent', async () => { - await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry + await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry await timeout(100); - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry 100ms after + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry 100ms after expect( - SessionProtocol.shouldProcessSessionRequest('deviceid', Date.now() - 50) + SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() - 50) ).to.be.eventually.equal( false, 'shouldProcessSessionRequest should return false if there is a more recent processed but a less recent sent' @@ -317,11 +319,11 @@ describe('SessionProtocol', () => { }); it('protocol: shouldProcessSessionRequest returns true if both sent and processed timestamp are older', async () => { - await SessionProtocol.onSessionRequestProcessed('deviceid'); // adds a Date.now() entry - await SessionProtocol.sendSessionRequest(resetMessage, 'deviceid'); // adds a Date.now() entry + await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry + await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry expect( SessionProtocol.shouldProcessSessionRequest( - 'deviceid', + pubkey, Date.now() + 1000 ) ).to.be.eventually.equal( From 7a797737e0df22e4c4325c32895fa6ba7e2501ea Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 10 Jun 2020 10:07:10 +1000 Subject: [PATCH 8/9] rename SessionReset -> SessionRequest --- ts/session/crypto/MessageEncrypter.ts | 2 +- .../outgoing/content/EndSessionMessage.ts | 4 ++-- ...setMessage.ts => SessionRequestMessage.ts} | 6 ++--- ts/session/messages/outgoing/content/index.ts | 2 +- ts/session/protocols/SessionProtocol.ts | 19 ++++++++++----- ts/session/types/EncryptionType.ts | 2 +- .../session/crypto/MessageEncrypter_test.ts | 18 ++++++++++---- .../messages/SessionResetMessage_test.ts | 8 +++---- .../session/protocols/SessionProtocol_test.ts | 24 +++++++------------ 9 files changed, 47 insertions(+), 38 deletions(-) rename ts/session/messages/outgoing/content/{SessionResetMessage.ts => SessionRequestMessage.ts} (86%) diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts index 0a6c8d5e3..754afeda6 100644 --- a/ts/session/crypto/MessageEncrypter.ts +++ b/ts/session/crypto/MessageEncrypter.ts @@ -50,7 +50,7 @@ export async function encrypt( } let innerCipherText: CipherTextObject; - if (encryptionType === EncryptionType.SessionReset) { + if (encryptionType === EncryptionType.SessionRequest) { const cipher = new libloki.crypto.FallBackSessionCipher(address); innerCipherText = await cipher.encrypt(plainText.buffer); } else { diff --git a/ts/session/messages/outgoing/content/EndSessionMessage.ts b/ts/session/messages/outgoing/content/EndSessionMessage.ts index f19b111dd..5368dcf85 100644 --- a/ts/session/messages/outgoing/content/EndSessionMessage.ts +++ b/ts/session/messages/outgoing/content/EndSessionMessage.ts @@ -1,7 +1,7 @@ -import { SessionResetMessage } from './SessionResetMessage'; +import { SessionRequestMessage } from './SessionRequestMessage'; import { SignalService } from '../../../../protobuf'; -export class EndSessionMessage extends SessionResetMessage { +export class EndSessionMessage extends SessionRequestMessage { public ttl(): number { return 4 * 24 * 60 * 60 * 1000; // 4 days } diff --git a/ts/session/messages/outgoing/content/SessionResetMessage.ts b/ts/session/messages/outgoing/content/SessionRequestMessage.ts similarity index 86% rename from ts/session/messages/outgoing/content/SessionResetMessage.ts rename to ts/session/messages/outgoing/content/SessionRequestMessage.ts index b4fb8ebb0..89563d9e0 100644 --- a/ts/session/messages/outgoing/content/SessionResetMessage.ts +++ b/ts/session/messages/outgoing/content/SessionRequestMessage.ts @@ -12,14 +12,14 @@ export interface PreKeyBundleType { signature: Uint8Array; } -interface SessionResetParams extends MessageParams { +interface SessionRequestParams extends MessageParams { preKeyBundle: PreKeyBundleType; } -export class SessionResetMessage extends ContentMessage { +export class SessionRequestMessage extends ContentMessage { private readonly preKeyBundle: PreKeyBundleType; - constructor(params: SessionResetParams) { + constructor(params: SessionRequestParams) { super({ timestamp: params.timestamp, identifier: params.identifier }); this.preKeyBundle = params.preKeyBundle; } diff --git a/ts/session/messages/outgoing/content/index.ts b/ts/session/messages/outgoing/content/index.ts index 2089b6932..364fceab0 100644 --- a/ts/session/messages/outgoing/content/index.ts +++ b/ts/session/messages/outgoing/content/index.ts @@ -1,7 +1,7 @@ export * from './ContentMessage'; export * from './EndSessionMessage'; export * from './SessionEstablishedMessage'; -export * from './SessionResetMessage'; +export * from './SessionRequestMessage'; export * from './TypingMessage'; export * from './data'; export * from './link'; diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index 8a4173538..9aa9fc726 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -1,4 +1,4 @@ -import { SessionResetMessage } from '../messages/outgoing'; +import { SessionRequestMessage } from '../messages/outgoing'; // import { MessageSender } from '../sending'; import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; import { libloki, libsignal, textsecure } from '../../window'; @@ -74,7 +74,7 @@ export class SessionProtocol { } /** - * Triggers a SessionResetMessage to be sent if: + * Triggers a SessionRequestMessage to be sent if: * - we do not already have a session and * - we did not sent a session request already to that device and * - we do not have a session request currently being send to that device @@ -93,7 +93,7 @@ export class SessionProtocol { pubkey.key ); - const sessionReset = new SessionResetMessage({ + const sessionReset = new SessionRequestMessage({ preKeyBundle, timestamp: Date.now(), }); @@ -101,13 +101,17 @@ export class SessionProtocol { try { await SessionProtocol.sendSessionRequest(sessionReset, pubkey); } catch (error) { - window.console.warn('Failed to send session request to:', pubkey.key, error); + window.console.warn( + 'Failed to send session request to:', + pubkey.key, + error + ); } } /** */ public static async sendSessionRequest( - message: SessionResetMessage, + message: SessionRequestMessage, pubkey: PubKey ): Promise { const timestamp = Date.now(); @@ -151,7 +155,10 @@ export class SessionProtocol { } public static async onSessionRequestProcessed(pubkey: PubKey) { - return SessionProtocol.updateProcessedSessionTimestamp(pubkey.key, Date.now()); + return SessionProtocol.updateProcessedSessionTimestamp( + pubkey.key, + Date.now() + ); } public static reset() { diff --git a/ts/session/types/EncryptionType.ts b/ts/session/types/EncryptionType.ts index ed27e1023..86d1aeda7 100644 --- a/ts/session/types/EncryptionType.ts +++ b/ts/session/types/EncryptionType.ts @@ -1,5 +1,5 @@ export enum EncryptionType { Signal, - SessionReset, + SessionRequest, MediumGroup, } diff --git a/ts/test/session/crypto/MessageEncrypter_test.ts b/ts/test/session/crypto/MessageEncrypter_test.ts index be53909eb..6f16e77c7 100644 --- a/ts/test/session/crypto/MessageEncrypter_test.ts +++ b/ts/test/session/crypto/MessageEncrypter_test.ts @@ -59,14 +59,18 @@ describe('MessageEncrypter', () => { }); }); - describe('SessionReset', () => { + describe('SessionRequest', () => { it('should call FallbackSessionCipher encrypt', async () => { const data = crypto.randomBytes(10); const spy = sandbox.spy( Stubs.FallBackSessionCipherStub.prototype, 'encrypt' ); - await MessageEncrypter.encrypt('1', data, EncryptionType.SessionReset); + await MessageEncrypter.encrypt( + '1', + data, + EncryptionType.SessionRequest + ); expect(spy.called).to.equal( true, 'FallbackSessionCipher.encrypt should be called.' @@ -79,7 +83,11 @@ describe('MessageEncrypter', () => { Stubs.FallBackSessionCipherStub.prototype, 'encrypt' ); - await MessageEncrypter.encrypt('1', data, EncryptionType.SessionReset); + await MessageEncrypter.encrypt( + '1', + data, + EncryptionType.SessionRequest + ); const paddedData = MessageEncrypter.padPlainTextBuffer(data); const firstArgument = new Uint8Array(spy.args[0][0]); @@ -91,7 +99,7 @@ describe('MessageEncrypter', () => { const result = await MessageEncrypter.encrypt( '1', data, - EncryptionType.SessionReset + EncryptionType.SessionRequest ); expect(result.envelopeType).to.deep.equal( SignalService.Envelope.Type.UNIDENTIFIED_SENDER @@ -136,7 +144,7 @@ describe('MessageEncrypter', () => { describe('Sealed Sender', () => { it('should pass the correct values to SecretSessionCipher encrypt', async () => { - const types = [EncryptionType.SessionReset, EncryptionType.Signal]; + const types = [EncryptionType.SessionRequest, EncryptionType.Signal]; for (const type of types) { const spy = sandbox.spy( Stubs.SecretSessionCipherStub.prototype, diff --git a/ts/test/session/messages/SessionResetMessage_test.ts b/ts/test/session/messages/SessionResetMessage_test.ts index 33e6ac20e..6f7bb758d 100644 --- a/ts/test/session/messages/SessionResetMessage_test.ts +++ b/ts/test/session/messages/SessionResetMessage_test.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; import { beforeEach } from 'mocha'; -import { SessionResetMessage } from '../../../session/messages/outgoing'; +import { SessionRequestMessage } from '../../../session/messages/outgoing'; import { SignalService } from '../../../protobuf'; import { TextDecoder, TextEncoder } from 'util'; -describe('SessionResetMessage', () => { - let message: SessionResetMessage; +describe('SessionRequestMessage', () => { + let message: SessionRequestMessage; const preKeyBundle = { deviceId: 123456, preKeyId: 654321, @@ -19,7 +19,7 @@ describe('SessionResetMessage', () => { beforeEach(() => { const timestamp = Date.now(); - message = new SessionResetMessage({ timestamp, preKeyBundle }); + message = new SessionRequestMessage({ timestamp, preKeyBundle }); }); it('has a preKeyBundle', () => { diff --git a/ts/test/session/protocols/SessionProtocol_test.ts b/ts/test/session/protocols/SessionProtocol_test.ts index 71940c9bd..b78d48e24 100644 --- a/ts/test/session/protocols/SessionProtocol_test.ts +++ b/ts/test/session/protocols/SessionProtocol_test.ts @@ -3,7 +3,7 @@ import { SessionProtocol } from '../../../session/protocols'; import * as sinon from 'sinon'; import { Stubs, TestUtils, timeout } from '../../test-utils'; import { UserUtil } from '../../../util'; -import { SessionResetMessage } from '../../../session/messages/outgoing'; +import { SessionRequestMessage } from '../../../session/messages/outgoing'; import { TextEncoder } from 'util'; import { MessageSender } from '../../../session/sending'; import { PubKey } from '../../../session/types'; @@ -16,7 +16,7 @@ describe('SessionProtocol', () => { let getItemById: sinon.SinonStub; let send: sinon.SinonStub; - const resetMessage: SessionResetMessage = new SessionResetMessage({ + const resetMessage: SessionRequestMessage = new SessionRequestMessage({ timestamp: Date.now(), preKeyBundle: { identityKey: new TextEncoder().encode('identityKey'), @@ -117,7 +117,10 @@ describe('SessionProtocol', () => { it('protocol: onSessionEstablished should remove the device in sentTimestamps and ONLY that one', async () => { // add a second item to the map - await SessionProtocol.sendSessionRequest(resetMessage, new PubKey('deviceid2')); + await SessionProtocol.sendSessionRequest( + resetMessage, + new PubKey('deviceid2') + ); expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property( 'deviceid' @@ -231,10 +234,7 @@ describe('SessionProtocol', () => { it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than processed timestamp', async () => { await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry expect( - SessionProtocol.shouldProcessSessionRequest( - pubkey, - Date.now() + 1000 - ) + SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000) ).to.be.eventually.equal( true, 'shouldProcessSessionRequest should return true when existingProcessed is less recent' @@ -273,10 +273,7 @@ describe('SessionProtocol', () => { it('protocol: shouldProcessSessionRequest returns true if timestamp is more recent than current sent timestamp', async () => { await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry expect( - SessionProtocol.shouldProcessSessionRequest( - pubkey, - Date.now() + 1000 - ) + SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000) ).to.be.eventually.equal( true, 'shouldProcessSessionRequest should return true when existingSent is less recent' @@ -322,10 +319,7 @@ describe('SessionProtocol', () => { await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry expect( - SessionProtocol.shouldProcessSessionRequest( - pubkey, - Date.now() + 1000 - ) + SessionProtocol.shouldProcessSessionRequest(pubkey, Date.now() + 1000) ).to.be.eventually.equal( true, 'shouldProcessSessionRequest should return true if there if both processed and sent are set but are older' From 4c07bc06fd2b9ccbf429acabcba24557a098bcb9 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 10 Jun 2020 10:18:56 +1000 Subject: [PATCH 9/9] address reviews --- ts/session/protocols/SessionProtocol.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts index 9aa9fc726..9070528ac 100644 --- a/ts/session/protocols/SessionProtocol.ts +++ b/ts/session/protocols/SessionProtocol.ts @@ -9,7 +9,6 @@ import { PubKey } from '../types'; interface StringToNumberMap { [key: string]: number; } -// tslint:disable: function-name // tslint:disable: no-unnecessary-class export class SessionProtocol { private static dbLoaded: Boolean = false; @@ -77,7 +76,7 @@ export class SessionProtocol { * Triggers a SessionRequestMessage to be sent if: * - we do not already have a session and * - we did not sent a session request already to that device and - * - we do not have a session request currently being send to that device + * - we do not have a session request currently being sent to that device */ public static async sendSessionRequestIfNeeded( pubkey: PubKey @@ -86,7 +85,7 @@ export class SessionProtocol { (await SessionProtocol.hasSession(pubkey)) || (await SessionProtocol.hasSentSessionRequest(pubkey)) ) { - return Promise.resolve(); + return; } const preKeyBundle = await libloki.storage.getPreKeyBundleForContact( @@ -109,7 +108,10 @@ export class SessionProtocol { } } - /** */ + /** + * Sends a session request message to that pubkey. + * We store the sent timestamp only if the message is effectively sent. + */ public static async sendSessionRequest( message: SessionRequestMessage, pubkey: PubKey @@ -118,6 +120,7 @@ export class SessionProtocol { // mark the session as being pending send with current timestamp // so we know we already triggered a new session with that device + // so sendSessionRequestIfNeeded does not sent another session request SessionProtocol.pendingSendSessionsTimestamp.add(pubkey.key); try { @@ -216,7 +219,7 @@ export class SessionProtocol { map: StringToNumberMap ): Promise { if (!timestamp) { - if (!!map[device]) { + if (device in map) { // tslint:disable-next-line: no-dynamic-delete delete map[device];