summaryrefslogtreecommitdiff
path: root/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js')
-rw-r--r--data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js451
1 files changed, 451 insertions, 0 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js b/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js
new file mode 100644
index 0000000..9023c1f
--- /dev/null
+++ b/data/extensions/jsr@javascriptrestrictor/wrappingS-SENSOR-ACCEL.js
@@ -0,0 +1,451 @@
+/** \file
+ * \brief Wrappers for the Accelerometer Sensor, LinearAccelerationSensor,
+ * and GravitySensor
+ *
+ * \see https://www.w3.org/TR/accelerometer/
+ * \see https://www.w3.org/TR/accelerometer/#linearaccelerationsensor
+ * \see https://www.w3.org/TR/accelerometer/#gravitysensor
+ *
+ * \author Copyright (C) 2021 Radek Hranicky
+ *
+ * \license SPDX-License-Identifier: GPL-3.0-or-later
+ */
+ //
+ // This program is free software: you can redistribute it and/or modify
+ // it under the terms of the GNU General Public License as published by
+ // the Free Software Foundation, either version 3 of the License, or
+ // (at your option) any later version.
+ //
+ // This program is distributed in the hope that it will be useful,
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ // GNU General Public License for more details.
+ //
+ // You should have received a copy of the GNU General Public License
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
+ //
+
+ /** \file
+ * \ingroup wrappers
+ * MOTIVATION
+ * Readings from the Accelerometer, LinearAccelerationSensor, and GravitySensor
+ * of the Generic Sensor API should be secured as they provide a potentially
+ * valuable data for creating fingerprints. There are multiple options.
+ * A unique fingerprint can be obtained by describing the device's vibrations
+ * (See https://link.springer.com/chapter/10.1007/978-3-319-30806-7_7).
+ * Using trajectory inference and matching of the model to map data, one may
+ * use the readings from the Accelerometer to determing the device's position
+ * (See https://www.researchgate.net/publication/220990763_ACComplice_Location_
+ * inference_using_accelerometers_on_smartphones).
+ * Accelerometer readings can also be used for determining human walking patterns
+ * (See https://www.researchgate.net/publication/322835708_Classifying_Human_
+ * Walking_Patterns_using_Accelerometer_Data_from_Smartphone).
+ *
+ *
+ * WRAPPING
+ * The wrapper replaces the "XYZ" getters of the Accelerometer sensor,
+ * LinearAccelerationSensor, and GravitySensor. The wrapping's goal is to
+ * simulate a stationary device that can be possibly rotated. The rotation
+ * of the device is represented by the fake rotation matrix "orient.rotMat".
+ *
+ * The GravitySensor should provide readings of gravity acceleration applied
+ * to the device. This is represented by a vector made of x, y, z portions.
+ * To get this faked gravity vector for the device, the reference vector
+ * [0, 0, 9.8] is multipled with the rotation matrix. Wrappers for the
+ * GravitySensor's getters return x, y, z portions of the fake gravity vector.
+ *
+ * Next, the LinearAccelerationSensor should return acceleration values without
+ * the contribution of gravity. For a stationary device, it should be all zeroes.
+ * Yet, there could be vibrations that may change values a little bit, e.g.,
+ * spin around -0.1 to +0.1, as seen on the examined devices. This usually does
+ * not happed with every reading but only in intervals of seconds. And thus,
+ * after a few seconds we pseudo-randomly change these values.
+ *
+ * Finally, the Accelerometer sensor combines the previous two. Our wrappers thus
+ * return tha values from the LinearAccelerationSensor with the fake gravity
+ * vector portions added.
+ *
+ *
+ * POSSIBLE IMPROVEMENTS
+ * Support for simulation of a non-stationary device where the rotation
+ * can change. Currently, the calculation of the gravity vector is done
+ * only once by the initDataGenerator() where the reference vector is
+ * multiplied with the rotation matrix. If orient.rotMat could change,
+ * the dataGen would have to be updated periodically.
+ * Moreover, such a change should also be taken into account in wrappers
+ * for other movement-related sensors (Gyroscope, etc.).
+ *
+ */
+
+ /*
+ * Create private namespace
+ */
+(function() {
+ /*
+ * \brief Initialization of data for storing sensor readings
+ */
+ var init_data = `
+ var currentReading = currentReading || {orig_x: null, orig_y: null, orig_z: null, timestamp: null,
+ fake_x: null, fake_y: null, fake_z: null, gVector: null};
+ var previousReading = previousReading || {orig_x: null, orig_y: null, orig_z: null, timestamp: null,
+ fake_x: null, fake_y: null, fake_z: null, gVector: null};
+ var emulateStationaryDevice = (typeof args === 'undefined') ? true : args[0];
+ var debugMode = false;
+
+ const TWOPI = 2 * Math.PI;
+ `;
+
+ /*
+ * \brief Property getters of the original sensor object
+ */
+ var orig_getters = `
+ var origGetX = Object.getOwnPropertyDescriptor(Accelerometer.prototype, "x").get;
+ var origGetY = Object.getOwnPropertyDescriptor(Accelerometer.prototype, "y").get;
+ var origGetZ = Object.getOwnPropertyDescriptor(Accelerometer.prototype, "z").get;
+ var origGetTimestamp = Object.getOwnPropertyDescriptor(Sensor.prototype, "timestamp").get;
+ `;
+
+ /*
+ * \brief Changes the value on the given axis to a new one from the given interval
+ *
+ * \param the axis object (min, max, value, and decimalPlaces properties required)
+ */
+ function shake(axis) {
+ val = sen_prng() * (axis.max - axis.min) + axis.min;
+
+ var precision = Math.pow(10, -1 * axis.decimalPlaces);
+ if (val < precision) {
+ val = 0;
+ }
+
+ if (axis.canBeNegative) {
+ val *= Math.round(sen_prng()) ? 1 : -1;
+ }
+
+ if (val == 0) {
+ axis.value = 0;
+ } else {
+ axis.value = fixedNumber(val, axis.decimalPlaces);
+ }
+ }
+
+ /*
+ * \brief The data generator for creating fake accelerometer values
+ */
+ class DataGenerator {
+ constructor() {
+ this.NEXT_CHANGE_MS_MIN = 1000;
+ this.NEXT_CHANGE_MS_MAX = 10000;
+
+ /* Reference gravity vector
+ * For a non-rotated device lying bottom-down on a flat surface,
+ * only axis "z" is afected by g.
+ */
+ let referenceGravityVector = [0, 0, 9.8];
+
+ /*
+ * For a rotated device, the reference gravity vector needs to be
+ * multiplied by the rotation matrix.
+ * Here we calculate and store the device gravity vector
+ */
+ this.gVector = multVectRot(referenceGravityVector, orient.rotMat);
+
+ /*
+ * Values for the linear acceleration are fluctuating
+ */
+ this.x = {
+ name: "x",
+ min: 0.0,
+ max: 0.11,
+ decimalPlaces: 1,
+ canBeNegative: true,
+ value: null
+ };
+ this.y = {
+ name: "y",
+ min: 0.0,
+ max: 0.11,
+ decimalPlaces: 1,
+ canBeNegative: true,
+ value: null
+ };
+ this.z = {
+ name: "z",
+ min: 0.0,
+ max: 0.11,
+ decimalPlaces: 1,
+ canBeNegative: true,
+ value: null
+ };
+ this.nextChangeTimeX = null; // miliseconds
+ this.nextChangeTimeY = null;
+ this.nextChangeTimeZ = null;
+ }
+
+ /*
+ * \brief Updates the x/y/z axes values based on the current timestamp
+ *
+ * \param Current timestamp from the sensor object
+ */
+ update(currentTimestamp) {
+ // Simulate the Gyroscope changes
+ if (this.shouldWeUpdateX(currentTimestamp)) {
+ shake(this.x);
+ this.setNextChangeX(currentTimestamp);
+ };
+ if (this.shouldWeUpdateY(currentTimestamp)) {
+ shake(this.y);
+ this.setNextChangeY(currentTimestamp);
+ };
+ if (this.shouldWeUpdateZ(currentTimestamp)) {
+ shake(this.z);
+ this.setNextChangeZ(currentTimestamp);
+ };
+ }
+
+ /*
+ * \brief Boolean function that decides if the value on the axis X
+ * should be updated. Returns true if update is needed.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ shouldWeUpdateX(currentTimestamp) {
+ if (currentTimestamp === null || this.nextChangeTimeX === null) {
+ return true;
+ }
+ if (currentTimestamp >= this.nextChangeTimeX) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * \brief Boolean function that decides if the value on the axis Y
+ * should be updated. Returns true if update is needed.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ shouldWeUpdateY(currentTimestamp) {
+ if (currentTimestamp === null || this.nextChangeTimeY === null) {
+ return true;
+ }
+ if (currentTimestamp >= this.nextChangeTimeY) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * \brief Boolean function that decides if the value on the axis Z
+ * should be updated. Returns true if update is needed.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ shouldWeUpdateZ(currentTimestamp) {
+ if (currentTimestamp === null || this.nextChangeTimeZ === null) {
+ return true;
+ }
+ if (currentTimestamp >= this.nextChangeTimeZ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * \brief Sets the timestamp of the next update of value on the axis X.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ setNextChangeX(currentTimestamp) {
+ let interval_ms = Math.floor(
+ sen_prng() * (this.NEXT_CHANGE_MS_MAX - this.NEXT_CHANGE_MS_MIN + 1)
+ + this.NEXT_CHANGE_MS_MIN
+ );
+ this.nextChangeTimeX = currentTimestamp + interval_ms;
+ }
+
+ /*
+ * \brief Sets the timestamp of the next update of value on the axis Y.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ setNextChangeY(currentTimestamp) {
+ let interval_ms = Math.floor(
+ sen_prng() * (this.NEXT_CHANGE_MS_MAX - this.NEXT_CHANGE_MS_MIN + 1)
+ + this.NEXT_CHANGE_MS_MIN
+ );
+ this.nextChangeTimeY = currentTimestamp + interval_ms;
+ }
+
+ /*
+ * \brief Sets the timestamp of the next update of value on the axis Z.
+ *
+ * \param Current timestamp from the sensor object
+ */
+ setNextChangeZ(currentTimestamp) {
+ let interval_ms = Math.floor(
+ sen_prng() * (this.NEXT_CHANGE_MS_MAX - this.NEXT_CHANGE_MS_MIN + 1)
+ + this.NEXT_CHANGE_MS_MIN
+ );
+ this.nextChangeTimeZ = currentTimestamp + interval_ms;
+ }
+ }
+
+ /*
+ * \brief Updates the stored (both real and fake) sensor readings
+ * according to the data from the sensor object.
+ *
+ * \param The sensor object
+ */
+ function updateReadings(sensorObject) {
+
+ // We need the original reading's timestamp to see if it differs
+ // from the previous sample. If so, we need to update the faked x,y,z
+ let previousTimestamp = previousReading.timestamp;
+ let currentTimestamp = origGetTimestamp.call(sensorObject);
+
+ if (debugMode) {
+ // [!] Debug mode: overriding timestamp
+ // This allows test suites to set a custom timestamp externally
+ // by modifying the property of the sensor object directly.
+ currentTimestamp = sensorObject.timestamp;
+ }
+
+ if (currentTimestamp === previousReading.timestamp) {
+ // No new reading, nothing to update
+ return;
+ }
+
+ // Rotate the readings: previous <- current
+ previousReading = JSON.parse(JSON.stringify(currentReading));
+
+ // Update current reading
+ // NOTE: Original values are also stored for possible future use
+ currentReading.orig_x = origGetX.call(sensorObject);
+ currentReading.orig_y = origGetY.call(sensorObject);
+ currentReading.orig_z = origGetZ.call(sensorObject);
+ currentReading.timestamp = currentTimestamp;
+
+ dataGenerator.update(currentTimestamp);
+
+ currentReading.fake_x = dataGenerator.x.value;
+ currentReading.fake_y = dataGenerator.y.value;
+ currentReading.fake_z = dataGenerator.z.value;
+ currentReading.fake_gVector = dataGenerator.gVector;
+
+ if (debugMode) {
+ }
+ }
+
+ /*
+ * \brief Initializes the related generators
+ */
+ var generators = `
+ // Initialize the data generator, if not initialized before
+ var dataGenerator = dataGenerator || new DataGenerator();
+ `;
+
+ var helping_functions = sensorapi_prng_functions + device_orientation_functions
+ + DataGenerator + shake + updateReadings;
+ var hc = init_data + orig_getters + helping_functions + generators;
+
+ var wrappers = [
+ {
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "x",
+ wrapped_objects: [],
+ helping_code: hc,
+ post_wrapping_code: [
+ {
+ code_type: "object_properties",
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "x",
+ wrapped_objects: [],
+ /** \brief replaces Sensor.prototype.x getter to return a faked value
+ */
+ wrapped_properties: [
+ {
+ property_name: "get",
+ property_value: `
+ function() {
+ updateReadings(this);
+ if (this.__proto__.constructor.name === 'GravitySensor') {
+ return fixedNumber(currentReading.fake_gVector[0], 1);
+ } else if (this.__proto__.constructor.name === 'LinearAccelerationSensor') {
+ return fixedNumber(currentReading.fake_x, 1);
+ }
+ return fixedNumber(currentReading.fake_x + currentReading.fake_gVector[0], 1);
+ }`,
+ },
+ ],
+ }
+ ],
+ },
+ {
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "y",
+ wrapped_objects: [],
+ helping_code: hc,
+ post_wrapping_code: [
+ {
+ code_type: "object_properties",
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "y",
+ wrapped_objects: [],
+ /** \brief replaces Sensor.prototype.y getter to return a faked value
+ */
+ wrapped_properties: [
+ {
+ property_name: "get",
+ property_value: `
+ function() {
+ updateReadings(this);
+ if (this.__proto__.constructor.name === 'GravitySensor') {
+ return fixedNumber(currentReading.fake_gVector[1], 1);
+ } else if (this.__proto__.constructor.name === 'LinearAccelerationSensor') {
+ return fixedNumber(currentReading.fake_y, 1);
+ }
+ return fixedNumber(currentReading.fake_y + currentReading.fake_gVector[1], 1);
+ }`,
+ },
+ ],
+ }
+ ],
+ },
+ {
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "z",
+ wrapped_objects: [],
+ helping_code: hc,
+ post_wrapping_code: [
+ {
+ code_type: "object_properties",
+ parent_object: "Accelerometer.prototype",
+ parent_object_property: "z",
+ wrapped_objects: [],
+ /** \brief replaces Sensor.prototype.z getter to return a faked value
+ */
+ wrapped_properties: [
+ {
+ property_name: "get",
+ property_value: `
+ function() {
+ updateReadings(this);
+ if (this.__proto__.constructor.name === 'GravitySensor') {
+ return fixedNumber(currentReading.fake_gVector[2], 1);
+ } else if (this.__proto__.constructor.name === 'LinearAccelerationSensor') {
+ return fixedNumber(currentReading.fake_z, 1);
+ }
+ return fixedNumber(currentReading.fake_z + currentReading.fake_gVector[2], 1);
+ }`,
+ },
+ ],
+ }
+ ],
+ },
+ ]
+ add_wrappers(wrappers);
+})()