Commit 497c54a8 authored by Chris Mumford's avatar Chris Mumford Committed by Commit Bot

Quantize sensor values to protect user privacy.

Round sensor values to multiples of a reasonably small value to
protect user privacy. This makes the readings slightly less
accurate, but protects users from fingerprinting attacks which
might allow a site to generate a unique id based on the sensor
calibration data. The affected sensors are:

1. ACCELEROMETER.
2. LINEAR_ACCELERATION.
3. GYROSCOPE.
4. ORIENTATION (Euler & Quaternion, absolute & relative).

Bug: 1018180, 1031190
Change-Id: Iafe84ec9fcf12cfdfde1898da011d63dd419bfa4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2003614Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarVincent Scheib <scheib@chromium.org>
Reviewed-by: default avatarKai Ninomiya <kainino@chromium.org>
Reviewed-by: default avatarBrandon Jones <bajones@chromium.org>
Commit-Queue: Chris Mumford <cmumford@google.com>
Cr-Commit-Position: refs/heads/master@{#746199}
parent 6e690dc7
......@@ -130,6 +130,7 @@ source_set("tests") {
"generic_sensor/platform_sensor_and_provider_unittest_win.cc",
"generic_sensor/platform_sensor_fusion_unittest.cc",
"generic_sensor/platform_sensor_provider_unittest_android.cc",
"generic_sensor/platform_sensor_util_unittest.cc",
"generic_sensor/relative_orientation_euler_angles_fusion_algorithm_using_accelerometer_and_gyroscope_unittest.cc",
"generic_sensor/relative_orientation_euler_angles_fusion_algorithm_using_accelerometer_unittest.cc",
"geolocation/geolocation_provider_impl_unittest.cc",
......
......@@ -55,6 +55,8 @@ source_set("generic_sensor") {
"platform_sensor_reader_win.cc",
"platform_sensor_reader_win.h",
"platform_sensor_reader_win_base.h",
"platform_sensor_util.cc",
"platform_sensor_util.h",
"platform_sensor_win.cc",
"platform_sensor_win.h",
"relative_orientation_euler_angles_fusion_algorithm_using_accelerometer.cc",
......
......@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "services/device/generic_sensor/platform_sensor_provider.h"
#include "services/device/generic_sensor/platform_sensor_util.h"
#include "services/device/public/cpp/generic_sensor/platform_sensor_configuration.h"
#include "services/device/public/cpp/generic_sensor/sensor_reading_shared_buffer_reader.h"
......@@ -21,7 +22,8 @@ PlatformSensor::PlatformSensor(mojom::SensorType type,
: task_runner_(base::ThreadTaskRunnerHandle::Get()),
reading_buffer_(reading_buffer),
type_(type),
provider_(provider) {}
provider_(provider),
have_raw_reading_(false) {}
PlatformSensor::~PlatformSensor() {
if (provider_)
......@@ -104,6 +106,14 @@ bool PlatformSensor::GetLatestReading(SensorReading* result) {
return SensorReadingSharedBufferReader::GetReading(reading_buffer_, result);
}
bool PlatformSensor::GetLatestRawReading(SensorReading* result) const {
base::AutoLock auto_lock(lock_);
if (!have_raw_reading_)
return false;
*result = last_raw_reading_;
return true;
}
void PlatformSensor::UpdateSharedBufferAndNotifyClients(
const SensorReading& reading) {
UpdateSharedBuffer(reading);
......@@ -115,8 +125,20 @@ void PlatformSensor::UpdateSharedBufferAndNotifyClients(
void PlatformSensor::UpdateSharedBuffer(const SensorReading& reading) {
ReadingBuffer* buffer = reading_buffer_;
auto& seqlock = buffer->seqlock.value();
// Save the raw (non-rounded) reading for fusion sensors.
{
base::AutoLock auto_lock(lock_);
last_raw_reading_ = reading;
have_raw_reading_ = true;
}
// Round the reading to guard user privacy. See https://crbug.com/1018180.
SensorReading rounded_reading = reading;
RoundSensorReading(&rounded_reading, type_);
seqlock.WriteBegin();
buffer->reading = reading;
buffer->reading = rounded_reading;
seqlock.WriteEnd();
}
......
......@@ -14,6 +14,7 @@
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "mojo/public/cpp/system/buffer.h"
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"
#include "services/device/public/mojom/sensor.mojom.h"
......@@ -69,6 +70,8 @@ class PlatformSensor : public base::RefCountedThreadSafe<PlatformSensor> {
void RemoveClient(Client*);
bool GetLatestReading(SensorReading* result);
// Return the last raw (i.e. unrounded) sensor reading.
bool GetLatestRawReading(SensorReading* result) const;
// Returns 'true' if the sensor is started; returns 'false' otherwise.
bool IsActiveForTesting() const;
using ConfigMap = std::map<Client*, std::list<PlatformSensorConfiguration>>;
......@@ -110,6 +113,9 @@ class PlatformSensor : public base::RefCountedThreadSafe<PlatformSensor> {
ConfigMap config_map_;
PlatformSensorProvider* provider_;
bool is_active_ = false;
SensorReading last_raw_reading_;
mutable base::Lock lock_; // Protect have_raw_reading_ and last_raw_reading_.
bool have_raw_reading_;
base::WeakPtrFactory<PlatformSensor> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PlatformSensor);
};
......
......@@ -20,6 +20,7 @@
#include "services/device/generic_sensor/linux/sensor_data_linux.h"
#include "services/device/generic_sensor/linux/sensor_device_manager.h"
#include "services/device/generic_sensor/platform_sensor_provider_linux.h"
#include "services/device/generic_sensor/platform_sensor_util.h"
#include "services/device/public/cpp/generic_sensor/sensor_traits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -74,6 +75,14 @@ std::string ReadValueFromFile(const base::FilePath& path,
return new_read_value;
}
double RoundAccelerometerValue(double value) {
return RoundToMultiple(value, kAccelerometerRoundingMultiple);
}
double RoundGyroscopeValue(double value) {
return RoundToMultiple(value, kGyroscopeRoundingMultiple);
}
} // namespace
// Mock for SensorDeviceService that SensorDeviceManager owns.
......@@ -570,17 +579,23 @@ TEST_F(PlatformSensorAndProviderLinuxTest,
static_cast<SensorReadingSharedBuffer*>(mapping.get());
#if defined(OS_CHROMEOS)
double scaling = base::kMeanGravityDouble / kAccelerometerScalingValue;
EXPECT_THAT(buffer->reading.accel.x, scaling * sensor_values[0]);
EXPECT_THAT(buffer->reading.accel.y, scaling * sensor_values[1]);
EXPECT_THAT(buffer->reading.accel.z, scaling * sensor_values[2]);
EXPECT_THAT(buffer->reading.accel.x,
RoundAccelerometerValue(scaling * sensor_values[0]));
EXPECT_THAT(buffer->reading.accel.y,
RoundAccelerometerValue(scaling * sensor_values[1]));
EXPECT_THAT(buffer->reading.accel.z,
RoundAccelerometerValue(scaling * sensor_values[2]));
#else
double scaling = kAccelerometerScalingValue;
EXPECT_THAT(buffer->reading.accel.x,
-scaling * (sensor_values[0] + kAccelerometerOffsetValue));
RoundAccelerometerValue(
-scaling * (sensor_values[0] + kAccelerometerOffsetValue)));
EXPECT_THAT(buffer->reading.accel.y,
-scaling * (sensor_values[1] + kAccelerometerOffsetValue));
RoundAccelerometerValue(
-scaling * (sensor_values[1] + kAccelerometerOffsetValue)));
EXPECT_THAT(buffer->reading.accel.z,
-scaling * (sensor_values[2] + kAccelerometerOffsetValue));
RoundAccelerometerValue(
-scaling * (sensor_values[2] + kAccelerometerOffsetValue)));
#endif
EXPECT_TRUE(sensor->StopListening(client.get(), configuration));
......@@ -678,17 +693,23 @@ TEST_F(PlatformSensorAndProviderLinuxTest, CheckGyroscopeReadingConversion) {
#if defined(OS_CHROMEOS)
double scaling =
gfx::DegToRad(base::kMeanGravityDouble) / kGyroscopeScalingValue;
EXPECT_THAT(buffer->reading.gyro.x, -scaling * sensor_values[0]);
EXPECT_THAT(buffer->reading.gyro.y, -scaling * sensor_values[1]);
EXPECT_THAT(buffer->reading.gyro.z, -scaling * sensor_values[2]);
EXPECT_THAT(buffer->reading.gyro.x,
RoundGyroscopeValue(-scaling * sensor_values[0]));
EXPECT_THAT(buffer->reading.gyro.y,
RoundGyroscopeValue(-scaling * sensor_values[1]));
EXPECT_THAT(buffer->reading.gyro.z,
RoundGyroscopeValue(-scaling * sensor_values[2]));
#else
double scaling = kGyroscopeScalingValue;
EXPECT_THAT(buffer->reading.gyro.x,
scaling * (sensor_values[0] + kGyroscopeOffsetValue));
RoundGyroscopeValue(scaling *
(sensor_values[0] + kGyroscopeOffsetValue)));
EXPECT_THAT(buffer->reading.gyro.y,
scaling * (sensor_values[1] + kGyroscopeOffsetValue));
RoundGyroscopeValue(scaling *
(sensor_values[1] + kGyroscopeOffsetValue)));
EXPECT_THAT(buffer->reading.gyro.z,
scaling * (sensor_values[2] + kGyroscopeOffsetValue));
RoundGyroscopeValue(scaling *
(sensor_values[2] + kGyroscopeOffsetValue)));
#endif
EXPECT_TRUE(sensor->StopListening(client.get(), configuration));
......
......@@ -19,6 +19,7 @@
#include "services/device/generic_sensor/fake_platform_sensor_and_provider.h"
#include "services/device/generic_sensor/generic_sensor_consts.h"
#include "services/device/generic_sensor/platform_sensor_provider_win.h"
#include "services/device/generic_sensor/platform_sensor_util.h"
#include "services/device/public/mojom/sensor_provider.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -388,6 +389,14 @@ class PlatformSensorAndProviderTestWin : public ::testing::Test {
std::unique_ptr<base::RunLoop> run_loop_;
};
double RoundAccelerometerValue(double value) {
return RoundToMultiple(value, kAccelerometerRoundingMultiple);
}
double RoundGyroscopeValue(double value) {
return RoundToMultiple(value, kGyroscopeRoundingMultiple);
}
// Tests that PlatformSensorManager returns null sensor when sensor
// is not implemented.
TEST_F(PlatformSensorAndProviderTestWin, SensorIsNotImplemented) {
......@@ -563,9 +572,12 @@ TEST_F(PlatformSensorAndProviderTestWin, CheckAccelerometerReadingConversion) {
base::RunLoop().RunUntilIdle();
SensorReadingSharedBuffer* buffer =
static_cast<SensorReadingSharedBuffer*>(mapping.get());
EXPECT_THAT(buffer->reading.accel.x, -x_accel * base::kMeanGravityDouble);
EXPECT_THAT(buffer->reading.accel.y, -y_accel * base::kMeanGravityDouble);
EXPECT_THAT(buffer->reading.accel.z, -z_accel * base::kMeanGravityDouble);
EXPECT_THAT(buffer->reading.accel.x,
RoundAccelerometerValue(-x_accel * base::kMeanGravityDouble));
EXPECT_THAT(buffer->reading.accel.y,
RoundAccelerometerValue(-y_accel * base::kMeanGravityDouble));
EXPECT_THAT(buffer->reading.accel.z,
RoundAccelerometerValue(-z_accel * base::kMeanGravityDouble));
EXPECT_TRUE(sensor->StopListening(client.get(), configuration));
}
......@@ -602,9 +614,12 @@ TEST_F(PlatformSensorAndProviderTestWin, CheckGyroscopeReadingConversion) {
base::RunLoop().RunUntilIdle();
SensorReadingSharedBuffer* buffer =
static_cast<SensorReadingSharedBuffer*>(mapping.get());
EXPECT_THAT(buffer->reading.gyro.x, gfx::DegToRad(x_ang_accel));
EXPECT_THAT(buffer->reading.gyro.y, gfx::DegToRad(y_ang_accel));
EXPECT_THAT(buffer->reading.gyro.z, gfx::DegToRad(z_ang_accel));
EXPECT_THAT(buffer->reading.gyro.x,
RoundGyroscopeValue(gfx::DegToRad(x_ang_accel)));
EXPECT_THAT(buffer->reading.gyro.y,
RoundGyroscopeValue(gfx::DegToRad(y_ang_accel)));
EXPECT_THAT(buffer->reading.gyro.z,
RoundGyroscopeValue(gfx::DegToRad(z_ang_accel)));
EXPECT_TRUE(sensor->StopListening(client.get(), configuration));
}
......@@ -708,11 +723,15 @@ TEST_F(PlatformSensorAndProviderTestWin,
EXPECT_TRUE(StartListening(sensor, client.get(), configuration));
EXPECT_CALL(*client, OnSensorReadingChanged(sensor->GetType())).Times(1);
double x = -0.5;
double y = -0.5;
double z = 0.5;
double w = 0.5;
float quat_elements[4] = {x, y, z, w};
// The axis (unit vector) around which to rotate.
const double axis[3] = {1.0 / std::sqrt(3), 1.0 / std::sqrt(3),
-1.0 / std::sqrt(3)};
// Create the unit quaternion manually.
const double theta = 2.0943951023931953; // 120 degrees in radians.
const float quat_elements[4] = {
axis[0] * std::sin(theta / 2.0), axis[1] * std::sin(theta / 2.0),
axis[2] * std::sin(theta / 2.0), std::cos(theta / 2.0)};
base::win::ScopedPropVariant pvQuat;
......@@ -730,10 +749,11 @@ TEST_F(PlatformSensorAndProviderTestWin,
SensorReadingSharedBuffer* buffer =
static_cast<SensorReadingSharedBuffer*>(mapping.get());
EXPECT_THAT(buffer->reading.orientation_quat.x, x);
EXPECT_THAT(buffer->reading.orientation_quat.y, y);
EXPECT_THAT(buffer->reading.orientation_quat.z, z);
EXPECT_THAT(buffer->reading.orientation_quat.w, w);
const float epsilon = 1.0e-3;
EXPECT_NEAR(buffer->reading.orientation_quat.x, quat_elements[0], epsilon);
EXPECT_NEAR(buffer->reading.orientation_quat.y, quat_elements[1], epsilon);
EXPECT_NEAR(buffer->reading.orientation_quat.z, quat_elements[2], epsilon);
EXPECT_FLOAT_EQ(buffer->reading.orientation_quat.w, quat_elements[3]);
EXPECT_TRUE(sensor->StopListening(client.get(), configuration));
}
......
......@@ -11,6 +11,7 @@
#include "base/memory/scoped_refptr.h"
#include "services/device/generic_sensor/platform_sensor_fusion_algorithm.h"
#include "services/device/generic_sensor/platform_sensor_provider.h"
#include "services/device/generic_sensor/platform_sensor_util.h"
namespace device {
......@@ -195,6 +196,9 @@ void PlatformSensorFusion::OnSensorReadingChanged(mojom::SensorType type) {
if (!fusion_algorithm_->GetFusedData(type, &reading))
return;
// Round the reading to guard user privacy. See https://crbug.com/1018180.
RoundSensorReading(&reading, type);
if (GetReportingMode() == mojom::ReportingMode::ON_CHANGE &&
!fusion_algorithm_->IsReadingSignificantlyDifferent(reading_, reading)) {
return;
......@@ -220,7 +224,7 @@ bool PlatformSensorFusion::GetSourceReading(mojom::SensorType type,
SensorReading* result) {
auto it = source_sensors_.find(type);
if (it != source_sensors_.end())
return it->second->GetLatestReading(result);
return it->second->GetLatestRawReading(result);
NOTREACHED();
return false;
}
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/generic_sensor/platform_sensor_util.h"
#include <cmath>
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"
namespace device {
namespace {
static_assert(kAccelerometerRoundingMultiple > 0.0,
"Rounding multiple must be positive.");
static_assert(kGyroscopeRoundingMultiple > 0.0,
"Rounding multiple must be positive.");
static_assert(kOrientationEulerRoundingMultiple > 0.0,
"Rounding multiple must be positive.");
static_assert(kOrientationQuaternionRoundingMultiple > 0.0,
"Rounding multiple must be positive.");
template <typename T>
T square(T x) {
return x * x;
}
} // namespace
double RoundToMultiple(double value, double multiple) {
double scaledValue = value / multiple;
if (value < 0.0) {
return -multiple * floor(-scaledValue + 0.5);
} else {
return multiple * floor(scaledValue + 0.5);
}
}
void RoundAccelerometerReading(SensorReadingXYZ* reading) {
reading->x = RoundToMultiple(reading->x, kAccelerometerRoundingMultiple);
reading->y = RoundToMultiple(reading->y, kAccelerometerRoundingMultiple);
reading->z = RoundToMultiple(reading->z, kAccelerometerRoundingMultiple);
}
void RoundGyroscopeReading(SensorReadingXYZ* reading) {
reading->x = RoundToMultiple(reading->x, kGyroscopeRoundingMultiple);
reading->y = RoundToMultiple(reading->y, kGyroscopeRoundingMultiple);
reading->z = RoundToMultiple(reading->z, kGyroscopeRoundingMultiple);
}
void RoundOrientationQuaternionReading(SensorReadingQuat* reading) {
double original_angle_div_2 = std::acos(reading->w);
double rounded_angle_div_2 =
RoundToMultiple(original_angle_div_2 * 2.0,
kOrientationQuaternionRoundingMultiple) /
2.0;
if (rounded_angle_div_2 == 0.0) {
// If there's no rotation after rounding, return the identity quaternion.
reading->w = 1;
reading->x = reading->y = reading->z = 0;
return;
}
// After this, original_angle_div_2 will definitely not be too close to 0.
double sin_angle_div_2 = std::sin(original_angle_div_2);
double axis_x = reading->x / sin_angle_div_2;
double axis_y = reading->y / sin_angle_div_2;
double axis_z = reading->z / sin_angle_div_2;
// Convert from (x,y,z) vector to azimuth/elevation.
double xy_dist = std::sqrt(square(axis_x) + square(axis_y));
double azim = std::atan2(axis_x, axis_y);
double elev = std::atan2(axis_z, xy_dist);
azim = RoundToMultiple(azim, kOrientationQuaternionRoundingMultiple);
elev = RoundToMultiple(elev, kOrientationQuaternionRoundingMultiple);
// Convert back from azimuth/elevation to the (x,y,z) unit vector.
axis_x = std::sin(azim) * std::cos(elev);
axis_y = std::cos(azim) * std::cos(elev);
axis_z = std::sin(elev);
// Reconstruct Quaternion from (x,y,z,rotation).
sin_angle_div_2 = std::sin(rounded_angle_div_2);
reading->x = axis_x * sin_angle_div_2;
reading->y = axis_y * sin_angle_div_2;
reading->z = axis_z * sin_angle_div_2;
reading->w = std::cos(rounded_angle_div_2);
}
void RoundOrientationEulerReading(SensorReadingXYZ* reading) {
reading->x = RoundToMultiple(reading->x, kOrientationEulerRoundingMultiple);
reading->y = RoundToMultiple(reading->y, kOrientationEulerRoundingMultiple);
reading->z = RoundToMultiple(reading->z, kOrientationEulerRoundingMultiple);
}
void RoundSensorReading(SensorReading* reading, mojom::SensorType sensor_type) {
switch (sensor_type) {
case mojom::SensorType::ACCELEROMETER:
FALLTHROUGH;
case mojom::SensorType::LINEAR_ACCELERATION:
RoundAccelerometerReading(&reading->accel);
break;
case mojom::SensorType::GYROSCOPE:
RoundGyroscopeReading(&reading->gyro);
break;
case mojom::SensorType::ABSOLUTE_ORIENTATION_EULER_ANGLES:
FALLTHROUGH;
case mojom::SensorType::RELATIVE_ORIENTATION_EULER_ANGLES:
RoundOrientationEulerReading(&reading->orientation_euler);
break;
case mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION:
FALLTHROUGH;
case mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION:
RoundOrientationQuaternionReading(&reading->orientation_quat);
break;
default:
break;
}
}
} // namespace device
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_UTIL_H_
#define SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_UTIL_H_
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"
namespace device {
// Sensors are accurate enough that it is possible to sample sensor data to
// implement a "fingerprint attack" to reveal the sensor calibration data.
// This data is effectively a unique number, or fingerprint, for a device. To
// combat this, sensor values are rounded to the nearest multiple of some small
// number. This is still accurate enough to satisfy the needs of the sensor
// user, but guards against this attack.
//
// Additional info can be found at https://crbug.com/1018180.
//
// Rounding also protects against using the gyroscope as a primitive microphone
// to record audio. Additional info at https://crbug.com/1031190.
// Units are SI meters per second squared (m/s^2).
constexpr double kAccelerometerRoundingMultiple = 0.1;
// Units are radians/second. This value corresponds to 0.1 deg./sec.
constexpr double kGyroscopeRoundingMultiple = 0.00174532925199432963;
// Units are degrees.
constexpr double kOrientationEulerRoundingMultiple = 0.1;
// Units are radians. This value corresponds to 0.1 degrees.
constexpr double kOrientationQuaternionRoundingMultiple = 0.0017453292519943296;
// Round |value| to be a multiple of |multiple|.
//
// NOTE: Exposed for testing. Please use other Rounding functions below.
//
// Some examples:
//
// ( 1.24, 0.1) => 1.2
// ( 1.25, 0.1) => 1.3
// (-1.24, 0.1) => -1.2
// (-1.25, 0.1) => -1.3
double RoundToMultiple(double value, double multiple);
// Round accelerometer sensor reading to guard user privacy.
void RoundAccelerometerReading(SensorReadingXYZ* reading);
// Round gyroscope sensor reading to guard user privacy.
void RoundGyroscopeReading(SensorReadingXYZ* reading);
// Round orientation Euler angle sensor reading to guard user privacy.
void RoundOrientationEulerReading(SensorReadingXYZ* reading);
// Round orientation quaternion sensor reading to guard user privacy.
// |reading| is assumed to be unscaled (unit length).
void RoundOrientationQuaternionReading(SensorReadingQuat* reading);
// Round the sensor reading to guard user privacy.
void RoundSensorReading(SensorReading* reading, mojom::SensorType sensor_type);
} // namespace device
#endif // SERVICES_DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_UTIL_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/generic_sensor/platform_sensor_util.h"
#include <limits>
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
TEST(PlatformSensorUtil, RoundPositive) {
EXPECT_DOUBLE_EQ(1.2, RoundToMultiple(1.20, 0.1));
EXPECT_DOUBLE_EQ(1.2, RoundToMultiple(1.24, 0.1));
EXPECT_DOUBLE_EQ(1.3, RoundToMultiple(1.25, 0.1));
EXPECT_DOUBLE_EQ(1.3, RoundToMultiple(1.29, 0.1));
}
TEST(PlatformSensorUtil, RoundNegative) {
EXPECT_DOUBLE_EQ(-1.2, RoundToMultiple(-1.20, 0.1));
EXPECT_DOUBLE_EQ(-1.2, RoundToMultiple(-1.24, 0.1));
EXPECT_DOUBLE_EQ(-1.3, RoundToMultiple(-1.25, 0.1));
EXPECT_DOUBLE_EQ(-1.3, RoundToMultiple(-1.29, 0.1));
}
TEST(PlatformSensorUtil, RoundZero) {
EXPECT_DOUBLE_EQ(0.0, RoundToMultiple(0.0, 0.1));
}
TEST(PlatformSensorUtil, RoundMax) {
EXPECT_DOUBLE_EQ(std::numeric_limits<double>::max(),
RoundToMultiple(std::numeric_limits<double>::max(), 0.1));
EXPECT_DOUBLE_EQ(std::numeric_limits<double>::max(),
RoundToMultiple(std::numeric_limits<double>::max(), 5.0));
}
TEST(PlatformSensorUtil, RoundLowest) {
EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(),
RoundToMultiple(std::numeric_limits<double>::lowest(), 0.1));
EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(),
RoundToMultiple(std::numeric_limits<double>::lowest(), 5.0));
}
TEST(PlatformSensorUtil, RoundMin) {
EXPECT_DOUBLE_EQ(0.0,
RoundToMultiple(std::numeric_limits<double>::min(), 0.1));
EXPECT_DOUBLE_EQ(0.0,
RoundToMultiple(std::numeric_limits<double>::min(), 5.0));
}
TEST(PlatformSensorUtil, RoundQuaternion) {
// A hard coded quaternion and known outputs.
SensorReadingQuat quat;
quat.x = 0.408333;
quat.y = -0.694318;
quat.z = -0.107955;
quat.w = 0.582693;
RoundOrientationQuaternionReading(&quat);
EXPECT_DOUBLE_EQ(0.40879894045956067, quat.x);
EXPECT_DOUBLE_EQ(-0.69400288443777802, quat.y);
EXPECT_DOUBLE_EQ(-0.10747054463443845, quat.z);
EXPECT_DOUBLE_EQ(0.58283231268278346, quat.w);
// Test Quaternion with zero angle to detect division by zero.
quat.x = 0.408333;
quat.y = -0.694318;
quat.z = -0.107955;
quat.w = 1.0; // This is a zero rotation value.
RoundOrientationQuaternionReading(&quat);
EXPECT_DOUBLE_EQ(0.0, quat.x);
EXPECT_DOUBLE_EQ(0.0, quat.y);
EXPECT_DOUBLE_EQ(0.0, quat.z);
EXPECT_DOUBLE_EQ(1.0, quat.w);
// w is set to cos(angle/2), but when rounding this operation is done:
//
// cos(round(acos(w)*2)/2)
//
// Which results is a bit more floating point error than EXPECT_DOUBLE_EQ
// can tolerate, so explicitly verify less than some small value.
const double epsilon = 1.0e-15;
quat.x = std::sqrt(2.0) / 2.0;
quat.y = 0.0;
quat.z = std::sqrt(2.0) / 2.0;
quat.w = 0.0;
RoundOrientationQuaternionReading(&quat);
EXPECT_DOUBLE_EQ(std::sqrt(2.0) / 2.0, quat.x);
EXPECT_LT(std::abs(quat.y), epsilon);
EXPECT_DOUBLE_EQ(std::sqrt(2.0) / 2.0, quat.z);
EXPECT_LT(std::abs(quat.w), epsilon);
}
} // namespace device
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment