Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { IPPLifecycleHelper } = ChromeUtils.importESModule(
"moz-src:///toolkit/components/ipprotection/IPPLifecycleHelper.sys.mjs"
);
const SLEEP_TOPIC = "sleep_notification";
const WAKE_TOPIC = "wake_notification";
add_setup(async function () {
await putServerInRemoteSettings();
});
/**
* Brings the proxy to the ACTIVE state, at which point the lifecycle helper is
* observing sleep/wake.
*/
async function startActiveProxy() {
const readyEvent = waitForEvent(
IPProtectionService,
"IPProtectionService:StateChanged",
() => IPProtectionService.state === IPProtectionStates.READY
);
IPProtectionService.init();
await readyEvent;
const activeEvent = waitForEvent(
IPPProxyManager,
"IPPProxyManager:StateChanged",
() => IPPProxyManager.state === IPPProxyStates.ACTIVE
);
await IPPProxyManager.start();
await activeEvent;
}
/**
* On sleep, the channel filter is suspended (proxyInfo cleared).
*/
add_task(async function test_sleep_suspends_connection() {
setupStubs({ validProxyPass: true });
await startActiveProxy();
const channelFilter = IPPProxyManager.channelFilter();
Assert.ok(channelFilter.proxyInfo, "Connection has proxyInfo while active");
Assert.ok(IPPProxyManager.isolationKey, "Connection has an isolation key");
Services.obs.notifyObservers(null, SLEEP_TOPIC);
Assert.equal(
channelFilter.proxyInfo,
null,
"Channel filter is suspended after sleep"
);
await IPPProxyManager.stop();
IPProtectionService.uninit();
});
/**
* On wake with a still-good pass, the saved isolation key is re-injected
* (same key, no rotation).
*/
add_task(async function test_wake_with_valid_pass_resumes_same_key() {
let sandbox = sinon.createSandbox();
setupStubs({ validProxyPass: true });
await startActiveProxy();
const channelFilter = IPPProxyManager.channelFilter();
const savedKey = IPPProxyManager.isolationKey;
const rotateSpy = sandbox.spy(IPPProxyManager, "rotateProxyPass");
Services.obs.notifyObservers(null, SLEEP_TOPIC);
Assert.equal(channelFilter.proxyInfo, null, "Suspended after sleep");
Services.obs.notifyObservers(null, WAKE_TOPIC);
Assert.ok(channelFilter.proxyInfo, "Connection resumed after wake");
Assert.equal(
IPPProxyManager.isolationKey,
savedKey,
"Resuming reuses the isolation key captured before sleep"
);
Assert.ok(rotateSpy.notCalled, "A valid pass should not trigger a rotation");
await IPPProxyManager.stop();
IPProtectionService.uninit();
sandbox.restore();
});
/**
* On wake with an expired pass, the helper triggers a rotation, which yields a
* fresh isolation key.
*/
add_task(async function test_wake_with_expired_pass_rotates() {
let sandbox = sinon.createSandbox();
setupStubs({ validProxyPass: true });
await startActiveProxy();
// Rotate to an expired pass while staying active so canResume is false on the
// next wake.
setupStubs({ validProxyPass: false });
await IPPProxyManager.rotateProxyPass();
Assert.ok(
!IPPProxyManager.hasValidProxyPass,
"Pass is expired after rotating to an expired pass"
);
const savedKey = IPPProxyManager.isolationKey;
const rotateSpy = sandbox.spy(IPPProxyManager, "rotateProxyPass");
Services.obs.notifyObservers(null, SLEEP_TOPIC);
// The wake rotation should fetch a fresh, valid pass.
setupStubs({ validProxyPass: true });
Services.obs.notifyObservers(null, WAKE_TOPIC);
Assert.ok(rotateSpy.called, "An expired pass should trigger a rotation");
await rotateSpy.returnValues[0];
Assert.ok(channelFilterProxyInfoPresent(), "Connection has proxyInfo again");
Assert.notEqual(
IPPProxyManager.isolationKey,
savedKey,
"Rotation produces a new isolation key"
);
Assert.equal(
IPPProxyManager.state,
IPPProxyStates.ACTIVE,
"Proxy stays active across a wake rotation"
);
await IPPProxyManager.stop();
IPProtectionService.uninit();
sandbox.restore();
});
/**
* On wake with a pass that is valid but already inside its rotation window, the
* helper rotates instead of resuming (validates the canResume threshold).
*/
add_task(async function test_wake_within_rotation_window_rotates() {
let sandbox = sinon.createSandbox();
setupStubs({ validProxyPass: true });
await startActiveProxy();
// Rotate to a pass that is still valid but due for rotation (expires in 30s,
// well within the 2 minute rotation window).
const now = Temporal.Now.instant();
const soonToExpire = new ProxyPass(
createProxyPassToken(now, now.add({ seconds: 30 }))
);
IPPDummyAuthProvider.setProxyPass({
status: 200,
error: undefined,
pass: soonToExpire,
usage: new ProxyUsage(
"5368709120",
"4294967296",
"3026-02-01T00:00:00.000Z"
),
});
await IPPProxyManager.rotateProxyPass();
Assert.ok(
IPPProxyManager.hasValidProxyPass,
"Pass is still valid before its expiry"
);
Assert.ok(
!IPPProxyManager.channelFilter().canResume,
"Pass within the rotation window cannot be resumed"
);
const savedKey = IPPProxyManager.isolationKey;
const rotateSpy = sandbox.spy(IPPProxyManager, "rotateProxyPass");
Services.obs.notifyObservers(null, SLEEP_TOPIC);
setupStubs({ validProxyPass: true });
Services.obs.notifyObservers(null, WAKE_TOPIC);
Assert.ok(
rotateSpy.called,
"A pass within the rotation window should trigger a rotation"
);
await rotateSpy.returnValues[0];
Assert.notEqual(
IPPProxyManager.isolationKey,
savedKey,
"Rotation produces a new isolation key"
);
await IPPProxyManager.stop();
IPProtectionService.uninit();
sandbox.restore();
});
/**
* Sleep/wake observers are only registered while the proxy is ACTIVE.
*/
add_task(async function test_observers_only_active_while_active() {
let sandbox = sinon.createSandbox();
setupStubs({ validProxyPass: true });
await startActiveProxy();
await IPPProxyManager.stop();
Assert.notEqual(
IPPProxyManager.state,
IPPProxyStates.ACTIVE,
"Proxy is no longer active after stop"
);
const observeSpy = sandbox.spy(IPPLifecycleHelper, "observe");
Services.obs.notifyObservers(null, SLEEP_TOPIC);
Services.obs.notifyObservers(null, WAKE_TOPIC);
Assert.ok(
observeSpy.notCalled,
"The helper stops observing power events once the proxy is inactive"
);
IPProtectionService.uninit();
sandbox.restore();
});
function channelFilterProxyInfoPresent() {
return !!IPPProxyManager.channelFilter()?.proxyInfo;
}