You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			403 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			403 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
| import { expect } from 'chai';
 | |
| import { SessionProtocol } from '../../../session/protocols';
 | |
| import * as sinon from 'sinon';
 | |
| import { Stubs, TestUtils } from '../../test-utils';
 | |
| import { UserUtil } from '../../../util';
 | |
| import { SessionRequestMessage } 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 = TestUtils.generateFakePubKey();
 | |
|   const pubkey = TestUtils.generateFakePubKey();
 | |
|   let getItemById: sinon.SinonStub;
 | |
|   let send: sinon.SinonStub;
 | |
| 
 | |
|   const resetMessage: SessionRequestMessage = new SessionRequestMessage({
 | |
|     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.key);
 | |
|     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(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(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);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('sendSessionRequest', () => {
 | |
|     beforeEach(async () => {
 | |
|       // trigger a sessionReset
 | |
|       await SessionProtocol.sendSessionRequest(resetMessage, pubkey);
 | |
|     });
 | |
| 
 | |
|     it('protocol: sendSessionRequest should add the deviceID to the sentMap', async () => {
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp())
 | |
|         .to.have.property(pubkey.key)
 | |
|         .to.be.approximately(Date.now(), 100);
 | |
|     });
 | |
| 
 | |
|     it('protocol: sendSessionRequest should not have pendingSend set after', async () => {
 | |
|       expect(
 | |
|         SessionProtocol.getPendingSendSessionTimestamp()
 | |
|       ).to.not.have.property(pubkey.key);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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
 | |
|       await SessionProtocol.sendSessionRequest(resetMessage, pubkey);
 | |
|     });
 | |
| 
 | |
|     it('protocol: onSessionEstablished should remove the device in sentTimestamps', async () => {
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
 | |
|         pubkey.key
 | |
|       );
 | |
|       await SessionProtocol.onSessionEstablished(pubkey);
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
 | |
|         pubkey.key
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('protocol: onSessionEstablished should remove the device in sentTimestamps and ONLY that one', async () => {
 | |
|       // add a second item to the map
 | |
|       const anotherPubKey = TestUtils.generateFakePubKey();
 | |
|       await SessionProtocol.sendSessionRequest(resetMessage, anotherPubKey);
 | |
| 
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
 | |
|         pubkey.key
 | |
|       );
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
 | |
|         anotherPubKey.key
 | |
|       );
 | |
| 
 | |
|       await SessionProtocol.onSessionEstablished(pubkey);
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.not.have.property(
 | |
|         pubkey.key
 | |
|       );
 | |
|       expect(SessionProtocol.getSentSessionsTimestamp()).to.have.property(
 | |
|         anotherPubKey.key
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('hasSentSessionRequest', () => {
 | |
|     it('protocol: hasSentSessionRequest returns false if a message was not sent to that device', async () => {
 | |
|       const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
 | |
|       expect(hasSent).to.be.equal(
 | |
|         false,
 | |
|         `hasSent should be false for ${pubkey.key}`
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     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, pubkey);
 | |
|       const hasSent = await SessionProtocol.hasSentSessionRequest(pubkey);
 | |
|       expect(hasSent).to.be.equal(
 | |
|         true,
 | |
|         `hasSent should be true for ${pubkey.key}`
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     // 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(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(pubkey);
 | |
|       expect(hasSent).to.be.equal(
 | |
|         true,
 | |
|         `hasSent should be true for ${pubkey.key}`
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('protocol: sendSessionRequestIfNeeded should NOT send a new sessionMessage on second try ', async () => {
 | |
|       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(pubkey);
 | |
|       expect(hasSent).to.be.equal(
 | |
|         true,
 | |
|         `hasSent should be true for ${pubkey.key}`
 | |
|       );
 | |
|       send.resetHistory();
 | |
| 
 | |
|       // trigger a second call, Message.send().calledCount should still be 1
 | |
|       await SessionProtocol.sendSessionRequestIfNeeded(pubkey);
 | |
|       expect(send.callCount).to.be.equal(
 | |
|         0,
 | |
|         'MessageSender.send() should NOT have been called a second time'
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   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(pubkey);
 | |
|       expect(SessionProtocol.getProcessedSessionsTimestamp())
 | |
|         .to.have.property(pubkey.key)
 | |
|         .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(pubkey);
 | |
|       expect(SessionProtocol.getProcessedSessionsTimestamp())
 | |
|         .to.have.property(pubkey.key)
 | |
|         .to.be.approximately(Date.now(), 5);
 | |
|       await TestUtils.timeout(5);
 | |
|       const oldTimestamp = SessionProtocol.getProcessedSessionsTimestamp()[
 | |
|         pubkey.key
 | |
|       ];
 | |
|       await SessionProtocol.onSessionRequestProcessed(pubkey);
 | |
|       expect(SessionProtocol.getProcessedSessionsTimestamp())
 | |
|         .to.have.property(pubkey.key)
 | |
|         .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(pubkey); // adds a Date.now() entry
 | |
|       expect(
 | |
|         SessionProtocol.shouldProcessSessionRequest(pubkey, 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(pubkey, 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(pubkey); // adds a Date.now() entry
 | |
|       expect(
 | |
|         SessionProtocol.shouldProcessSessionRequest(pubkey, 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, pubkey); // adds a Date.now() entry
 | |
|       expect(
 | |
|         SessionProtocol.shouldProcessSessionRequest(pubkey, 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, pubkey); // adds a Date.now() entry
 | |
|       expect(
 | |
|         SessionProtocol.shouldProcessSessionRequest(pubkey, 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(pubkey, 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, pubkey); // adds a Date.now() entry
 | |
|       await TestUtils.timeout(100);
 | |
|       await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry 100ms after
 | |
| 
 | |
|       expect(
 | |
|         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'
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('protocol: shouldProcessSessionRequest returns false if there is a more recent processed but a less recent sent', async () => {
 | |
|       await SessionProtocol.onSessionRequestProcessed(pubkey); // adds a Date.now() entry
 | |
|       await TestUtils.timeout(100);
 | |
|       await SessionProtocol.sendSessionRequest(resetMessage, pubkey); // adds a Date.now() entry 100ms after
 | |
| 
 | |
|       expect(
 | |
|         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'
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     it('protocol: shouldProcessSessionRequest returns true if both sent and processed timestamp are older', async () => {
 | |
|       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)
 | |
|       ).to.be.eventually.equal(
 | |
|         true,
 | |
|         'shouldProcessSessionRequest should return true if there if both processed and sent are set but are older'
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| });
 |