Merge pull request #1200 from Mikunj/session-protocol

Add session request expiry checks
This commit is contained in:
Mikunj Varsani 2020-06-26 15:32:21 +10:00 committed by GitHub
commit 05f7698260
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 4 deletions

View file

@ -1640,6 +1640,15 @@
}
}
libsession.Protocols.SessionProtocol.checkSessionRequestExpiry().catch(
e => {
window.log.error(
'Error occured which checking for session request expiry',
e
);
}
);
storage.onready(async () => {
idleDetector.start();
});

View file

@ -17,6 +17,7 @@ interface SessionRequestParams extends MessageParams {
}
export class SessionRequestMessage extends ContentMessage {
public static readonly ttl = 4 * 24 * 60 * 60 * 1000; // 4 days
private readonly preKeyBundle: PreKeyBundleType;
constructor(params: SessionRequestParams) {
@ -25,7 +26,7 @@ export class SessionRequestMessage extends ContentMessage {
}
public ttl(): number {
return 4 * 24 * 60 * 60 * 1000; // 4 days
return SessionRequestMessage.ttl;
}
protected getPreKeyBundleMessage(): SignalService.PreKeyBundleMessage {

View file

@ -12,7 +12,7 @@ interface StringToNumberMap {
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.
* This map holds 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()`, or `hasSendSessionRequest()`
@ -73,6 +73,29 @@ export class SessionProtocol {
return pendingSend || hasSent;
}
/**
* Checks to see if any outgoing session requests have expired and re-sends them again if they have.
*/
public static async checkSessionRequestExpiry(): Promise<any> {
await this.fetchFromDBIfNeeded();
const now = Date.now();
const sentTimestamps = Object.entries(this.sentSessionsTimestamp);
const promises = sentTimestamps.map(async ([device, sent]) => {
const expireTime = sent + SessionRequestMessage.ttl;
// Check if we need to send a session request
if (now < expireTime) {
return;
}
// Unset the timestamp, so that if it fails to send in this function, it will be guaranteed to send later on.
await this.updateSentSessionTimestamp(device, undefined);
await this.sendSessionRequestIfNeeded(new PubKey(device));
});
return Promise.all(promises) as Promise<any>;
}
/**
* Triggers a SessionRequestMessage to be sent if:
* - we do not already have a session and

View file

@ -11,7 +11,7 @@ import { PubKey } from '../../../session/types';
// tslint:disable-next-line: max-func-body-length
describe('SessionProtocol', () => {
const sandbox = sinon.createSandbox();
const ourNumber = 'ourNumber';
const ourNumber = TestUtils.generateFakePubKey();
const pubkey = TestUtils.generateFakePubKey();
let getItemById: sinon.SinonStub;
let send: sinon.SinonStub;
@ -51,7 +51,7 @@ describe('SessionProtocol', () => {
getItemById = TestUtils.stubData('getItemById').resolves({ value: {} });
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber);
sandbox.stub(UserUtil, 'getCurrentDevicePubKey').resolves(ourNumber.key);
send = sandbox.stub(MessageSender, 'send' as any);
SessionProtocol.reset();
});
@ -99,6 +99,80 @@ describe('SessionProtocol', () => {
});
});
describe('checkSessionRequestExpiry', () => {
let clock: sinon.SinonFakeTimers;
let now: number;
let sendSessionRequestStub: sinon.SinonStub<
[SessionRequestMessage, PubKey],
Promise<void>
>;
beforeEach(() => {
now = Date.now();
clock = sandbox.useFakeTimers(now);
sendSessionRequestStub = sandbox
.stub(SessionProtocol, 'sendSessionRequest')
.resolves();
});
it('should not send a session request if none have expired', async () => {
getItemById.withArgs('sentSessionsTimestamp').resolves({
id: 'sentSessionsTimestamp',
value: {
[pubkey.key]: now,
},
});
// Set the time just before expiry
clock.tick(SessionRequestMessage.ttl - 100);
await SessionProtocol.checkSessionRequestExpiry();
expect(getItemById.calledWith('sentSessionsTimestamp'));
expect(sendSessionRequestStub.callCount).to.equal(0);
});
it('should send a session request if expired', async () => {
getItemById.withArgs('sentSessionsTimestamp').resolves({
id: 'sentSessionsTimestamp',
value: {
[pubkey.key]: now,
},
});
// Expire the request
clock.tick(SessionRequestMessage.ttl + 100);
await SessionProtocol.checkSessionRequestExpiry();
expect(getItemById.calledWith('sentSessionsTimestamp'));
expect(sendSessionRequestStub.callCount).to.equal(1);
});
it('should remove the old sent timestamp when expired', async () => {
getItemById.withArgs('sentSessionsTimestamp').resolves({
id: 'sentSessionsTimestamp',
value: {
[pubkey.key]: now,
},
});
// Remove this call from the equation
sandbox.stub(SessionProtocol, 'sendSessionRequestIfNeeded').resolves();
// Expire the request
clock.tick(SessionRequestMessage.ttl + 100);
await SessionProtocol.checkSessionRequestExpiry();
expect(getItemById.calledWith('sentSessionsTimestamp'));
expect(await SessionProtocol.hasSentSessionRequest(pubkey)).to.equal(
false,
'hasSentSessionRequest should return false.'
);
expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
pubkey.key
);
});
});
describe('onSessionEstablished', () => {
beforeEach(async () => {
// add an existing entry in the sentMap