UNPKG

@anlix-io/genieacs-sim

Version:

TR-069 client simulator for GenieACS

652 lines (577 loc) 30.7 kB
const event = '8 DIAGNOSTICS COMPLETE'; const diagnosticDuration = 2000; // in milliseconds. // Sets a new session, for the diagnostic complete message, to be started after // the simulated diagnostic finishes running. async function finish(simulator, name, func, afterMilliseconds, resolve) { // reference to object containing this diagnostic's execution data. const state = simulator.diagnosticsStates[name]; // executing diagnostic logic after 'afterMilliseconds' and sending results. state.running = setTimeout(async () => { await func(simulator); // executing diagnostic result. state.running = undefined; // clearing diagnostic's timeout object. state.reject = undefined; // clearing diagnostic's reject function. simulator.runPendingActions(async () => { await simulator.startSession(event); // creating a new session where diagnostic complete message is sent. resolve(name); // next diagnostic can be executed after calling 'resolve()'. simulator.emit('diagnostic', name); // sent diagnostic completion event to ACS and got a response. }); }, afterMilliseconds); } // Adds a diagnostic to a queue so it can be started (simulated) after session ends. async function queue(simulator, name, func, afterMilliseconds) { // When requested, the CPE SHOULD wait until after completion of the communication session // with the ACS before starting the diagnostic. [[from tr069]]. simulator.diagnosticQueue.push(() => new Promise((resolve, reject) => { simulator.diagnosticsStates[name].reject = reject; // saving reject so we can interrupt this diagnostic. finish(simulator, name, func, afterMilliseconds, resolve); })); } // clears diagnostic timeout and rejects the diagnostic. function interrupt(simulator, name) { const state = simulator.diagnosticsStates[name]; clearTimeout(state.running); if (state.reject) { // The caller of the diagnostic promise should catch the reject but ignore // the catch because another diagnostic may start after ACS request it. state.reject(name); state.reject = undefined; // removing reject after using it. } state.running = undefined; // removing timeout after clearing it. } // Helper functions. // Produces a random MAC Address, in uniform distribution. const randomMAC = () => "XX:XX:XX:XX:XX:XX" .replace(/X/g, () => "0123456789ABCDEF"[Math.random()*16|0]); // Produces a random integer between given interval, in uniform distribution. const randomN = (max, min=1) => Math.floor(Math.random()*(max+1-min))+min; // Returns a random element from given array, in uniform distribution. const getRandomFromArray = (array) => array[Math.random()*array.length|0]; /** * A diagnostic is an exported object with the following structure: * const x = { * path: {tr098: String, tr181: String}, * run: function(simulator, modified), * results: { * default: function(simulator), * error: function(simulator, errorName), * ...: function(simulator), * }, * } * exports.diag = x; * * The key of the object inside 'exports' is the name of the diagnostic. In the * above example, the diagnostic name is 'diag' * * - 'path' is an object where each value is the path for the root node of the * diagnostics data branch in the tree corresponding to each TR specification. * Currently there are tr069 and tr181, which are selected by the simulator.TR * attribute defined automatically in the simulator constructor. * * - 'run' is a function that received a simulator and the modified fields from * a task and it should check if the modified fields implies the diagnostic * should be started, and if that's true, should set the initial state of the * that diagnostic. The ping diagnostic has instructions taken from tr069 paper. * * - 'results' is object where each value is a function that sets the final * results for that diagnostic. It should always have 2 keys, 'default' and * 'error'. Other keys are allowed too. The selected function is defined by the * key given as the 'result' argument in simulator.setResultForDiagnostic(name, * result). For the 'error' function, the argument 'errorName' is the tree's * DiagnosticsState value to be set which is a string defined by the protocol. * * Helper functions for a diagnostic can be added to the diagnostic structure * and can be called, from inside their own structure, by prefixing the call * with the variable name of the diagnostic structure. In the above example * it's called "x", so a helper function should be called by 'x.helperfunc()'. * */ const ping = { path: { tr098: 'InternetGatewayDevice.IPPingDiagnostics.', tr181: 'Device.IP.Diagnostics.IPPing.', }, run: function(simulator, modified) { // executes Ping Diagnostic logic. const path = ping.path[simulator.TR]; if (modified[path+'DiagnosticsState'] === undefined) { // Modifying any of the writable parameters except for 'DiganosticState' MUST result // in its value being set to "None". [[from tr069]]. if ( modified[path+'Interface'] !== undefined || modified[path+'Host'] !== undefined || modified[path+'Timeout'] !== undefined || modified[path+'NumberOfRepetitions'] !== undefined || modified[path+'DataBlockSize'] !== undefined || modified[path+'DSCP'] !== undefined ) { // While the test is in progress, modifying any of the writable parameters except for 'DiganosticState' // MUST result in the test being terminated and its value being set to "None". [[from tr069]]. interrupt(simulator, 'ping'); simulator.device.get(path+'DiagnosticsState')[1] = 'None'; } // if no ping parameter has been modified, this diagnostic is not executed. return; } if (simulator.device.get(path+'DiagnosticsState')[1] !== 'Requested') return; // If the ACS sets the value of 'DiganosticState' to "Requested", the CPE MUST initiate the // corresponding diagnostic test. When writing, the only allowed value is Requested. To ensure the // use of the proper test parameters (the writable parameters in this object), the test parameters // MUST be set either prior to or at the same time as (in the same SetParameterValues) setting the // DiagnosticsState to Requested. [[from tr069]]. // While the test is in progress, setting 'DiganosticState' to "Requested" (and possibly modifying // other writable parameters in this object) MUST result in the test being terminated and then // restarted using the current values of the test parameters. [[from tr069]]. interrupt(simulator, 'ping'); // When the test is completed, the value of 'DiganosticState' MUST be either "Complete" or // one of the Error values. [[from tr069]]. // If the value of 'DiganosticState' is anything other than "Complete", the values of the // results parameters for this test are indeterminate. [[from tr069]]. // When the diagnostic initiated by the ACS is completed (successfully or not), the CPE MUST // establish a new connection to the ACS to allow theACS to view the results, indicating the // Event code "8 DIAGNOSTICS COMPLETE" in the Inform message. [[from tr069]]. const host = simulator.device.get(path+'Host')[1]; // checking host. if (host === '' || host.length > 256) { queue(simulator, 'ping', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_CannotResolveHostName'; }, diagnosticDuration); return; } const interface = (simulator.device.get(path+'Interface') || [,''])[1]; const timeout = parseInt((simulator.device.get(path+'Timeout') || [,1000])[1]); // unsignedInt[1:]. const NumberOfRepetitions = parseInt((simulator.device.get(path+'NumberOfRepetitions') || [,1])[1]); // unsignedInt[1:]. const dataBlockSize = parseInt((simulator.device.get(path+'DataBlockSize') || [,1])[1]); // unsignedInt[1:65535]. const dscp = parseInt((simulator.device.get(path+'DSCP') || [,0])[1]); // unsignedInt[0:63]. // checking other parameter's. if ( // The value of 'Interface' MUST be either a valid interface or an empty string. An attempt to set that // parameter to a different value MUST be rejected as an invalid parameter value. If an empty string is // specified, the CPE MUST use the interface as directed by its routing policy (Forwarding table entries) // to determine the appropriate interface. [[from tr069]]. interface.length > 256 || timeout < 1 || NumberOfRepetitions < 1 || dataBlockSize < 1 || dataBlockSize > 65535 || dscp < 0 || dscp > 63 ) { queue(simulator, 'ping', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_Other'; }, diagnosticDuration) return; } // all parameters are valid. queue(simulator, 'ping', simulator.diagnosticsStates.ping.result, diagnosticDuration); // After the diagnostic is complete, the value of all result parameters (all read-only parameters in this // object) MUST be retained by the CPE until either this diagnostic is run again, or the CPE reboots. }, results: { // possible results for a ping diagnostic. default: function(simulator) { // successful ping result. const path = ping.path[simulator.TR]; simulator.device.get(path+'DiagnosticsState')[1] = 'Complete'; simulator.device.get(path+'SuccessCount')[1] = simulator.device.get(path+'NumberOfRepetitions')[1]; simulator.device.get(path+'FailureCount')[1] = '0'; simulator.device.get(path+'AverageResponseTime')[1] = '11'; simulator.device.get(path+'MinimumResponseTime')[1] = '9'; simulator.device.get(path+'MaximumResponseTime')[1] = '14'; }, error: function(simulator, errorName='Error_Internal') { // internal error. const path = ping.path[simulator.TR]; simulator.device.get(path+'DiagnosticsState')[1] = errorName; }, }, }; exports.ping = ping; const traceroute = { path: { tr098: 'InternetGatewayDevice.TraceRouteDiagnostics.', tr181: 'Device.IP.Diagnostics.TraceRoute.', }, run: function(simulator, modified) { const path = traceroute.path[simulator.TR]; if (modified[path+'DiagnosticsState'] === undefined) { if ( modified[path+'Interface'] !== undefined || modified[path+'Host'] !== undefined || modified[path+'NumberOfTries'] !== undefined || modified[path+'Timeout'] !== undefined || modified[path+'DataBlockSize'] !== undefined || modified[path+'MaxHopCount'] !== undefined ) { interrupt(simulator, 'traceroute'); simulator.device.get(path+'DiagnosticsState')[1] = 'None'; } // if no traceroute parameter has been modified, this diagnostic is not executed. return; } if (simulator.device.get(path+'DiagnosticsState')[1] !== 'Requested') return; interrupt(simulator, 'traceroute'); const host = simulator.device.get(path+'Host')[1]; // checking host. if (host === '' || host.length > 256) { queue(simulator, 'traceroute', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_CannotResolveHostName'; }, diagnosticDuration); return; } const interface = (simulator.device.get(path+'Interface') || [,''])[1]; const numberOfTries = parseInt((simulator.device.get(path+'NumberOfTries') || [,1])[1]); const timeout = parseInt((simulator.device.get(path+'Timeout') || [,1000])[1]); const dataBlockSize = parseInt((simulator.device.get(path+'DataBlockSize') || [,1])[1]); const dscp = parseInt((simulator.device.get(path+'DSCP') || [,0])[1]); const maxHopCount = parseInt((simulator.device.get(path+'MaxHopCount') || [,30])[1]); // checking other parameter's. if ( interface.length > 256 || numberOfTries < 1 || numberOfTries > 3 || timeout < 1 || dataBlockSize < 1 || dataBlockSize > 65535 || dscp < 0 || dscp > 63 || maxHopCount < 1 || maxHopCount > 64 ) { queue(simulator, 'traceroute', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_MaxHopCountExceeded'; }, diagnosticDuration) return; } // all parameters are valid. queue(simulator, 'traceroute', simulator.diagnosticsStates.traceroute.result, diagnosticDuration); }, // Removes old traceroute results. eraseResult: function(simulator, path) { simulator.device.get(path+'RouteHopsNumberOfEntries')[1] = '0'; const fieldSuffix = simulator.TR === 'tr098' ? 'Hop' : ''; const fields = [ 'Host', 'HostAddress', 'ErrorCode', 'RTTimes', ]; let hop = 1; // starts from 1. while (true) { const routeHop = path+`RouteHops.${hop}.`; if (simulator.device.has(routeHop)) { for (let field of fields) { simulator.device.delete(routeHop+fieldSuffix+field); } simulator.device.delete(routeHop); } else { simulator.device.delete(path+'RouteHops.'); break; } hop++; } delete simulator.device._sortedPaths; }, // Produces hop results given some parameters. // When 'forcedMaxHopsError' is true, the produced result simulates a // traceroute result that have reached the max hops amounts and still haven't // reached the destination address. // 'amoutOfHops' is the amount of hops that will be produced in the result. // 'amoutOfHops' is expected to be smaller than traceroute's 'MaxHopCount'. produceHopResults: function(simulator, path, forcedMaxHopsError=false, amoutOfHops=8) { traceroute.eraseResult(simulator, path); // Erasing previous results. const maxHops = parseInt(simulator.device.get(path+'MaxHopCount')[1]); const hops = forcedMaxHopsError ? maxHops : amoutOfHops; // doing max hops or the given amount of hops. // Setting results. simulator.device.get(path+'DiagnosticsState')[1] = forcedMaxHopsError ? 'Error_MaxHopCountExceeded' : 'Complete'; simulator.device.get(path+'ResponseTime')[1] = forcedMaxHopsError ? `${((5+2*hops)*1.025).toFixed(3)}` : '3000'; simulator.device.get(path+'RouteHopsNumberOfEntries')[1] = hops.toString(); const host = simulator.device.get(path+`Host`)[1]; const tries = parseInt((simulator.device.get(path+'NumberOfTries') || [,0])[1]); path += 'RouteHops.'; simulator.device.set(path, [false]); // adding "RouteHops" node to the data model. const fieldSuffix = simulator.TR === 'tr098' ? 'Hop' : ''; // hop indexes start from 1. 'rtt' is a simulation of the round trip time increase with each following hop. for (let hop = 1, rtt = 5; hop <= hops; hop++, rtt += 5) { const hopPath = path+`${hop}.` // path for the i-th hop. simulator.device.set(hopPath, [false]); // creating node for the i-th hop. let hopHost = `hop-${hop}.com`; let hopHostAddress = `123.123.${123+hop}.123`; // if we are not forcing the max amount of hops in the last iteration, that means // the last iteration has reached the target host. if (!forcedMaxHopsError && hop === hops) { hopHost = host; // we will use the 'Host' attribute as the 'HopHost' value. // And if the 'Host' value is an IP address, 'HopHostAddress' will be empty. if (hopHost.match(/\d{1,3}(\.\d{1,3}){3}/)) hopHostAddress = ''; // That way, 'HopHost', in the last hop, will be whatever host has been defined in the 'Host' attribute. } // Result parameter indicating the Host Name if DNS is able to resolve or IP Address of a hop along // the discovered route. simulator.device.set(hopPath+fieldSuffix+'Host', [false, hopHost, 'xsd:string']); // If this parameter is non empty it will contain the last IP address of the host returned for this hop and the // HopHost will contain the Host Name returned from the reverse DNS query. simulator.device.set(hopPath+fieldSuffix+'HostAddress', [false, hopHostAddress, 'xsd:string']); // Contains the error code returned for this hop. This code is directly from the ICMP CODE field. simulator.device.set(hopPath+fieldSuffix+'ErrorCode', [false, 0, 'xsd:unsignedInt']); // Contains the comma separated list of one or more round trip times in milliseconds (one for each // repetition) for this hop. let hopRtTimes = [(rtt*1.01).toFixed(3), (rtt*0.975).toFixed(3), (rtt*1.025).toFixed(3)].slice(0, tries).toString(); simulator.device.set(hopPath+fieldSuffix+'RTTimes', [false, hopRtTimes, 'xsd:string']); } }, results: { default: function(simulator) { const path = traceroute.path[simulator.TR]; traceroute.produceHopResults(simulator, path, false, 8); // Producing 8 hops. }, Error_MaxHopCountExceeded: function(simulator) { // max hop count exceeded error. const path = traceroute.path[simulator.TR]; traceroute.produceHopResults(simulator, path, true) }, error: function(simulator, errorName='Error_Internal') { const path = traceroute.path[simulator.TR]; traceroute.eraseResult(simulator, path); simulator.device.get(path+'DiagnosticsState')[1] = errorName; }, }, }; exports.traceroute = traceroute; const sitesurvey = { path: { tr181: 'Device.WiFi.NeighboringWiFiDiagnostic.', }, run: function(simulator, modified) { const path = sitesurvey.path[simulator.TR]; if (modified[path+'DiagnosticsState'] === undefined) return; const field = simulator.device.get(path+'DiagnosticsState'); // this device may not implement neighboring networks diagnostic. if (!field) return; if (field[1] !== 'Requested') return; interrupt(simulator, 'sitesurvey'); queue(simulator, 'sitesurvey', simulator.diagnosticsStates.sitesurvey.result, diagnosticDuration); }, // Removes old site survey results. eraseResult: function (simulator, path) { simulator.device.get(path+'ResultNumberOfEntries')[1] = '0'; const fields = [ 'BSSID', 'BasicDataTransferRates', 'BeaconPeriod', 'Channel', 'DTIMPeriod', 'EncryptionMode', 'Mode', 'Noise', 'OperatingChannelBandwidth', 'OperatingFrequencyBand', 'OperatingStandards', 'Radio', 'SSID', 'SecurityModeEnabled', 'SignalStrength', 'SupportedDataTransferRates', 'SupportedStandards', ]; path += 'Result.'; let i = 0; while (true) { const neighbourPath = path+`${i}.`; if (simulator.device.has(neighbourPath)) { for (let field of fields) { simulator.device.delete(neighbourPath+field); } simulator.device.delete(neighbourPath); } else { simulator.device.delete(path); break; } i++; } delete simulator.device._sortedPaths; }, results: { default: function (simulator) { let path = sitesurvey.path[simulator.TR]; sitesurvey.eraseResult(simulator, path); const n = 20; // 20 neighboring WiFi networks. simulator.device.get(path+'DiagnosticsState')[1] = 'Complete'; simulator.device.get(path+'ResultNumberOfEntries')[1] = n.toString(); path += 'Result.'; simulator.device.set(path, [false]); for (let i = 0; i < n; i++) { const wifi5 = Math.random() > 0.5; // randomly assigning this network's WiFi frequency. const bandwidthIndex = randomN(wifi5 ? 3 : 2); // WiFi 5Ghz has 20, 40 and 80Mhz bandwidth. 2.4Ghz has 20 and 40Mhz. const channel = wifi5 // WiFi 5GHz channel number depends on it's bandwidth. 2.4Ghz does not. ? getRandomFromArray([[36,40,44,48,149,153,157,161,165],[38,46,151,159],[42,155]][bandwidthIndex-1]) : randomN(13); const neighbourPath = path+`${i}.`; simulator.device.set(neighbourPath, [false]); simulator.device.set(neighbourPath+'BSSID', [false, randomMAC(), 'xsd:string']); simulator.device.set(neighbourPath+'BasicDataTransferRates', [false, '', 'xsd:string']); simulator.device.set(neighbourPath+'BeaconPeriod', [false, '0', 'xsd:unsignedInt']); simulator.device.set(neighbourPath+'Channel', [false, channel.toString(), 'xsd:unsignedInt']); simulator.device.set(neighbourPath+'DTIMPeriod', [false, '0', 'xsd:unsignedInt']); simulator.device.set(neighbourPath+'EncryptionMode', [false, Math.random() > 0.5 ? 'TKIP' : 'AES', 'xsd:string']); simulator.device.set(neighbourPath+'Mode', [false, '0', 'xsd:string']); simulator.device.set(neighbourPath+'Noise', [false, '0', 'xsd:unsignedInt']); simulator.device.set(neighbourPath+'OperatingChannelBandwidth', [false, `${(2**bandwidthIndex)*10}MHz`, 'xsd:string']); simulator.device.set(neighbourPath+'OperatingFrequencyBand', [false, '', 'xsd:string']); simulator.device.set(neighbourPath+'OperatingStandards', [false, '', 'xsd:string']); simulator.device.set(neighbourPath+'Radio', [false, `Device.WiFi.Radio.${wifi5 ? 2 : 1}`, 'xsd:string']); simulator.device.set(neighbourPath+'SSID', [false, 'my beautiful SSID '+i, 'xsd:string']); simulator.device.set(neighbourPath+'SecurityModeEnabled', [false, 'Encrypted', 'xsd:string']); simulator.device.set(neighbourPath+'SignalStrength', [false, (-randomN(95,30)).toString(), 'xsd:int']); simulator.device.set(neighbourPath+'SupportedDataTransferRates', [false, '', 'xsd:string']); simulator.device.set(neighbourPath+'SupportedStandards', [false, '', 'xsd:string']); } }, error: function (simulator, errorName='Error_Internal') { const path = sitesurvey.path[simulator.TR]; sitesurvey.eraseResult(simulator, path); simulator.device.get(path+'DiagnosticsState')[1] = errorName; }, }, }; exports.sitesurvey = sitesurvey; const speedtest = { path: { tr098: 'InternetGatewayDevice.DownloadDiagnostics.', tr181: 'Device.IP.Diagnostics.DownloadDiagnostics.', }, run: function(simulator, modified) { // executes Speed test Diagnostic logic. const path = speedtest.path[simulator.TR]; if (modified[path+'DiagnosticsState'] === undefined) { if ( modified[path+'Interface'] !== undefined || modified[path+'DownloadURL'] !== undefined || modified[path+'DSCP'] !== undefined || modified[path+'EthernetPriority'] !== undefined || modified[path+'TimeBasedTestDuration'] !== undefined || modified[path+'TimeBasedTestMeasurementInterval'] !== undefined || modified[path+'TimeBasedTestMeasurementOffset'] !== undefined || modified[path+'NumberOfConnections'] !== undefined || modified[path+'EnablePerConnectionResults'] !== undefined ) { interrupt(simulator, 'speedtest'); simulator.device.get(path+'DiagnosticsState')[1] = 'None'; } // if no speedtest parameter has been modified, this diagnostic is not executed. return; } if (simulator.device.get(path+'DiagnosticsState')[1] !== 'Requested') return; interrupt(simulator, 'speedtest'); // checking 'downloadURL'. const downloadURL = simulator.device.get(path+'DownloadURL')[1]; if (downloadURL === '' || downloadURL.length > 2048) { queue(simulator, 'speedtest', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_CannotResolveHostName'; }, diagnosticDuration); return; } // checking other parameter's. const dscp = parseInt((simulator.device.get(path+'DSCP') || [,0])[1]); const ethernetPriority = parseInt((simulator.device.get(path+'EthernetPriority') || [,0])[1]); const tbtDuration = parseInt((simulator.device.get(path+'TimeBasedTestDuration') || [,0])[1]); const tbtInterval = parseInt((simulator.device.get(path+'TimeBasedTestMeasurementInterval') || [,0])[1]); const tbtOffset = parseInt((simulator.device.get(path+'TimeBasedTestMeasurementOffset') || [,0])[1]); const protocol = (simulator.device.get(path+'ProtocolVersion') || [,'Any'])[1]; const transports = new Set(( simulator.device.get(path+'DownloadTransports') || simulator.device.get('InternetGatewayDevice.Capabilities.PerformanceDiagnostic.DownloadTransports') )[1].split(',')); const numberOfConnections = parseInt((simulator.device.get(path+'NumberOfConnections') || [,1])[1]); if ( simulator.device.get(path+'Interface')[1].length > 256 || dscp < 0 || dscp > 63 || ethernetPriority < 0 || ethernetPriority > 7 || tbtDuration < 0 || tbtDuration > 999 || tbtInterval < 0 || tbtInterval > 999 || tbtOffset < 0 || tbtOffset > 255 || tbtOffset > tbtInterval || tbtInterval > tbtDuration || !(protocol === "Any" || protocol === "IPv4" || protocol === "IPv6") || numberOfConnections < 1 || !(transports.has('HTTP') || transports.has('FTP')) ) { queue(simulator, 'speedtest', (simulator) => { simulator.device.get(path+'DiagnosticsState')[1] = 'Error_Other'; }, diagnosticDuration) return; } // all parameters are valid. queue(simulator, 'speedtest', simulator.diagnosticsStates.speedtest.result, tbtDuration || diagnosticDuration); // After the diagnostic is complete, the value of all result parameters (all read-only parameters in this // object) MUST be retained by the CPE until either this diagnostic is run again, or the CPE reboots. }, // Removes old speed test results. eraseResult: function(simulator, path) { simulator.device.get(path+'IncrementalResultNumberOfEntries')[1] = '0'; const fields = [ 'TestBytesReceived', 'TotalBytesReceived', 'TotalBytesSent', 'StartTime', 'EndTime', ]; path += 'IncrementalResult.'; let i = 0; while (true) { const intervalPath = path+`${i}.`; if (simulator.device.has(intervalPath)) { for (let field of fields) { simulator.device.delete(intervalPath+field); } simulator.device.delete(intervalPath); } else { return; } i++; } delete simulator.device._sortedPaths; }, results: { // possible results for a speedtest diagnostic. default: function(simulator) { // successful speedtest result. const path = speedtest.path[simulator.TR]; simulator.device.get(path+'DiagnosticsState')[1] = 'Complete'; const tbtDuration = parseInt((simulator.device.get(path+'TimeBasedTestDuration') || [,0])[1]); const tbtInterval = parseInt((simulator.device.get(path+'TimeBasedTestMeasurementInterval') || [,0])[1]); const tbtOffset = parseInt((simulator.device.get(path+'TimeBasedTestMeasurementOffset') || [,0])[1]); let duration = tbtDuration || diagnosticDuration/1000; // in seconds. const date = new Date(); // current date, to be used as end date. let d = new Date(date); // copy of current date, to be used in date arithmetic. d.setSeconds(d.getSeconds()-duration+tbtOffset); // this works for both time based and file size based testing. simulator.device.get(path+'TCPOpenRequestTime')[1] = d.toISOString().replace('Z', '000Z'); d.setMilliseconds(d.getMilliseconds()+2); simulator.device.get(path+'TCPOpenResponseTime')[1] = d.toISOString().replace('Z', '000Z'); d.setMilliseconds(d.getMilliseconds()+2); simulator.device.get(path+'ROMTime')[1] = d.toISOString().replace('Z', '000Z'); d.setMilliseconds(d.getMilliseconds()+2); simulator.device.get(path+'BOMTime')[1] = d.toISOString().replace('Z', '000Z'); simulator.device.get(path+'EOMTime')[1] = date.toISOString().replace('Z', '000Z'); const throughput = 104857600; // 100MB/s. let v; const total = throughput*duration; // 100MB/s throughput throughout the duration. if (v = simulator.device.get(path+'PeriodOfFullLoading')) v[1] = ''+duration*0.95*10**6|0; // 95% of the duration. if (v = simulator.device.get(path+'TestBytesReceived')) v[1] = `${total}`; if (v = simulator.device.get(path+'TotalBytesReceived')) v[1] = `${total*1.05}`; if (v = simulator.device.get(path+'TotalBytesSent')) v[1] = `${total*0.02}`; if (v = simulator.device.get(path+'TestBytesReceivedUnderFullLoading')) v[1] = `${total*0.9}`; if (v = simulator.device.get(path+'TotalBytesReceivedUnderFullLoading')) v[1] = `${total*0.9*1.05}`; if (v = simulator.device.get(path+'TotalBytesSentUnderFullLoading')) v[1] = `${total*0.9*0.02}`; if (tbtInterval) { speedtest.eraseResult(simulator, path); const n = tbtDuration/tbtInterval|0; simulator.device.get(path+'IncrementalResultNumberOfEntries')[1] = n.toString(); path += 'IncrementalResult.' simulator.device.set(path, [false]); const total = throughput*tbtInterval; // 100MB/s throughput throughout the duration. // const remainder = tbtDuration%tbtInterval; for (let i = 1; i <= n; i++) { const intervalPath = path+`${i}.`; simulator.device.set(intervalPath, [false]); simulator.device.set(intervalPath+'TestBytesReceived', [false, `${total}`, 'xsd:unsignedInt']); simulator.device.set(intervalPath+'TotalBytesReceived', [false, `${total*1.05}`, 'xsd:unsignedInt']); simulator.device.set(intervalPath+'TotalBytesSent', [false, `${total*0.02}`, 'xsd:unsignedInt']); simulator.device.set(intervalPath+'StartTime', [false, d.toISOString().replace('Z', '000Z'), 'xsd:dateTime']); d.setSeconds(d.getSeconds()+tbtInterval); simulator.device.set(intervalPath+'EndTime', [false, d.toISOString().replace('Z', '000Z'), 'xsd:dateTime']); } } }, error: function(simulator, errorName='Error_Internal') { // internal error. const path = speedtest.path[simulator.TR]; speedtest.eraseResult(simulator, path); simulator.device.get(path+'DiagnosticsState')[1] = errorName; }, Error_Timeout: function(simulator) { // internal error. speedtest.results.error(simulator, 'Error_Timeout'); }, failed: function(simulator) { // internal error. speedtest.results.error(simulator, 'Error_TransferFailed'); }, Error_NoResponse: function(simulator) { // internal error. speedtest.results.error(simulator, 'Error_NoResponse'); }, Error_NoRouteToHost: function(simulator) { // internal error. speedtest.results.error(simulator, 'Error_NoRouteToHost'); }, }, }; exports.speedtest = speedtest;