test_IPProtectionUsage.js (7206B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { IPProtectionUsage } = ChromeUtils.importESModule( 7 "moz-src:///browser/components/ipprotection/IPProtectionUsage.sys.mjs" 8 ); 9 const { HttpServer } = ChromeUtils.importESModule( 10 "resource://testing-common/httpd.sys.mjs" 11 ); 12 const { NetUtil } = ChromeUtils.importESModule( 13 "resource://gre/modules/NetUtil.sys.mjs" 14 ); 15 16 const { XPCOMUtils } = ChromeUtils.importESModule( 17 "resource://gre/modules/XPCOMUtils.sys.mjs" 18 ); 19 const lazy = XPCOMUtils.declareLazy({ 20 ProxyService: { 21 service: "@mozilla.org/network/protocol-proxy-service;1", 22 iid: Ci.nsIProtocolProxyService, 23 }, 24 }); 25 26 /** 27 * Creates a new channel for the given URI. 28 * 29 * @param {*} aUri the URI to create the channel for. 30 * @param {*} method the HTTP method to use (default: "GET"). 31 * @param {*} body the request body (for POST requests). 32 * @param {*} proxyInfo proxy information (if any) makes this channel a proxied channel. 33 * @returns {nsIHttpChannel | nsIProxiedChannel} 34 */ 35 function makeChannel(aUri, method = "GET", body = null, proxyInfo = null) { 36 let channel; 37 if (proxyInfo) { 38 let httpHandler = Services.io.getProtocolHandler("http"); 39 httpHandler.QueryInterface(Ci.nsIProxiedProtocolHandler); 40 let uri = Services.io.newURI(aUri); 41 42 let { loadInfo } = NetUtil.newChannel({ 43 uri, 44 loadUsingSystemPrincipal: true, 45 }); 46 47 channel = httpHandler.newProxiedChannel( 48 uri, 49 proxyInfo, 50 0, // proxy resolve flags 51 null, // proxy resolve URI 52 loadInfo 53 ); 54 } else { 55 channel = NetUtil.newChannel({ 56 uri: aUri, 57 loadUsingSystemPrincipal: true, 58 }).QueryInterface(Ci.nsIHttpChannel); 59 channel.requestMethod = method; 60 } 61 62 if (body) { 63 let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( 64 Ci.nsIStringInputStream 65 ); 66 stream.setUTF8Data(body); 67 channel 68 .QueryInterface(Ci.nsIUploadChannel) 69 .setUploadStream(stream, "text/plain", body.length); 70 } 71 return channel; 72 } 73 74 function promiseChannelDone(chan) { 75 return new Promise((resolve, reject) => { 76 chan.asyncOpen(new ChannelListener(resolve, reject)); 77 }); 78 } 79 80 /** 81 * Mocks a channel listener. 82 */ 83 class ChannelListener { 84 constructor(resolve, reject) { 85 this.resolve = resolve; 86 this.reject = reject; 87 } 88 onStartRequest() {} 89 onDataAvailable() {} 90 onStopRequest() { 91 this.resolve(); 92 } 93 } 94 95 /** 96 * • Creates a profile dir & initialises FOG. 97 * • Resets/flushes metrics so each test starts clean. 98 * • Spins‑up an HttpServer, hands its URL to the test body, then stops it. 99 * 100 * @param {string} path Path for the single route, e.g. "/get". 101 * @param {Function} handler httpd.js style path handler. 102 * @param {Function} testBody async fn(url:string):void – the real test. 103 */ 104 async function withSetup(path, handler, testBody) { 105 do_get_profile(); 106 Services.fog.initializeFOG(); 107 108 await Services.fog.testFlushAllChildren(); 109 Services.fog.testResetFOG(); 110 111 let server = new HttpServer(); 112 server.registerPathHandler(path, handler); 113 server.start(-1); 114 let port = server.identity.primaryPort; 115 let url = `http://localhost:${port}${path}`; 116 117 try { 118 await testBody(url); 119 } finally { 120 await new Promise(r => server.stop(r)); 121 await Services.fog.testResetFOG(); 122 } 123 } 124 125 add_task(async function test_countChannel_get() { 126 await withSetup( 127 "/get", 128 (req, resp) => { 129 resp.setStatusLine(req.httpVersion, 200, "OK"); 130 resp.write("hello world"); 131 }, 132 async url => { 133 let channel = makeChannel(url, "GET"); 134 await promiseChannelDone(channel); 135 136 IPProtectionUsage.countChannel(channel); 137 138 Assert.greater( 139 Glean.ipprotection.usageRx.testGetValue().sum, 140 0, 141 "usageRx should have recorded bytes" 142 ); 143 Assert.greater( 144 Glean.ipprotection.usageTx.testGetValue().sum, 145 0, 146 "usageTx should record for GET requests" 147 ); 148 } 149 ); 150 }); 151 152 add_task(async function test_countChannel_post() { 153 await withSetup( 154 "/post", 155 (req, resp) => { 156 let body = NetUtil.readInputStreamToString( 157 req.bodyInputStream, 158 req.bodyInputStream.available() 159 ); 160 Assert.equal( 161 body, 162 "some data", 163 "Request body should contain 'some data'" 164 ); 165 resp.setStatusLine(req.httpVersion, 200, "OK"); 166 resp.write("posted!"); 167 }, 168 async url => { 169 let channel = makeChannel(url, "POST", "some data"); 170 await promiseChannelDone(channel); 171 172 IPProtectionUsage.countChannel(channel); 173 174 Assert.greater( 175 Glean.ipprotection.usageRx.testGetValue().sum, 176 0, 177 "usageRx should have recorded bytes" 178 ); 179 Assert.greater( 180 Glean.ipprotection.usageTx.testGetValue().sum, 181 0, 182 "usageTx should record bytes for POST requests" 183 ); 184 } 185 ); 186 }); 187 188 add_task(async function test_countChannel_cache() { 189 await withSetup( 190 "/cache", 191 (req, resp) => { 192 resp.setStatusLine(req.httpVersion, 200, "OK"); 193 resp.setHeader("Cache-Control", "max-age=1000", false); 194 resp.write("cached response"); 195 }, 196 async url => { 197 let channel = makeChannel(url, "GET"); 198 await promiseChannelDone(channel); 199 200 IPProtectionUsage.countChannel(channel); 201 202 const afterRx = Glean.ipprotection.usageRx.testGetValue().sum; 203 Assert.greater( 204 afterRx, 205 0, 206 "usageRx should record bytes for first network request" 207 ); 208 209 let channel2 = makeChannel(url, "GET"); 210 await promiseChannelDone(channel2); 211 212 IPProtectionUsage.countChannel(channel2); 213 214 Assert.equal( 215 afterRx, 216 Glean.ipprotection.usageRx.testGetValue().sum, 217 "usageRx should not record bytes for cached request" 218 ); 219 } 220 ); 221 }); 222 223 add_task(async function test_shouldCountChannel() { 224 const usage = new IPProtectionUsage(); 225 const makeInfo = key => { 226 return lazy.ProxyService.newProxyInfo( 227 "http", 228 "127.0.0.1", 229 8888, 230 "authToken", 231 key, 232 1, // TRANSPARENT_PROXY_RESOLVES_HOST 233 100, 234 null // Failover proxy info 235 ); 236 }; 237 238 const trackedIsolationKey = "is-tracked"; 239 240 usage.addIsolationKey(trackedIsolationKey); 241 let testCases = [ 242 { 243 info: makeInfo(trackedIsolationKey), 244 result: true, 245 description: "Tracked proxy info should be counted", 246 }, 247 { 248 info: undefined, 249 result: false, 250 description: "No proxy info should not be counted", 251 }, 252 { 253 info: makeInfo("is-untracked"), 254 result: false, 255 description: "Untracked proxy info should not be counted", 256 }, 257 { 258 info: makeInfo(""), 259 result: false, 260 description: "proxy info with empty isolation key should not be counted", 261 }, 262 ]; 263 for (let { info, result, description } of testCases) { 264 let channel = makeChannel("http://example.com", "GET", null, info); 265 let shouldCount = usage.shouldCountChannel(channel); 266 Assert.equal( 267 shouldCount, 268 result, 269 `shouldCountChannel should return ${result} for ${description}` 270 ); 271 } 272 });