implement Milestone 15: sensor interface with fingerprinting prevention

This commit is contained in:
2026-01-18 16:14:18 +01:00
parent 779f66b2bb
commit 4ab5e52259
6 changed files with 1258 additions and 1 deletions

View File

@@ -0,0 +1,439 @@
// sensor_interface.cpp - Sensor interface implementation for Lua sandbox
// Milestone 15: Motion sensors with fingerprinting prevention
#include "sensor_interface.h"
#include <algorithm>
#include <cmath>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
namespace mosis {
// ============================================================================
// SensorSubscription Implementation
// ============================================================================
SensorSubscription::SensorSubscription(int id, SensorType type, const SensorOptions& options)
: m_id(id)
, m_type(type)
, m_frequency(std::min(options.frequency, 60)) // Cap at 60 Hz
{
}
SensorSubscription::~SensorSubscription() {
Stop();
}
void SensorSubscription::Start() {
m_active = true;
}
void SensorSubscription::Stop() {
m_active = false;
}
void SensorSubscription::SimulateData(const SensorData& data) {
if (m_active && m_on_data) {
m_on_data(data);
}
}
// ============================================================================
// SensorInterface Implementation
// ============================================================================
SensorInterface::SensorInterface(const std::string& app_id)
: m_app_id(app_id)
{
}
SensorInterface::~SensorInterface() {
Shutdown();
}
bool SensorInterface::RequiresPermission(SensorType type) {
switch (type) {
case SensorType::Accelerometer:
case SensorType::Gyroscope:
case SensorType::Magnetometer:
return true; // Requires sensors.motion permission
case SensorType::Proximity:
case SensorType::AmbientLight:
return false; // Auto-granted
default:
return true;
}
}
bool SensorInterface::ParseSensorType(const std::string& name, SensorType& out_type) {
if (name == "accelerometer") {
out_type = SensorType::Accelerometer;
return true;
} else if (name == "gyroscope") {
out_type = SensorType::Gyroscope;
return true;
} else if (name == "magnetometer") {
out_type = SensorType::Magnetometer;
return true;
} else if (name == "proximity") {
out_type = SensorType::Proximity;
return true;
} else if (name == "light" || name == "ambientlight") {
out_type = SensorType::AmbientLight;
return true;
}
return false;
}
const char* SensorInterface::GetSensorName(SensorType type) {
switch (type) {
case SensorType::Accelerometer: return "accelerometer";
case SensorType::Gyroscope: return "gyroscope";
case SensorType::Magnetometer: return "magnetometer";
case SensorType::Proximity: return "proximity";
case SensorType::AmbientLight: return "light";
default: return "unknown";
}
}
SensorData SensorInterface::ApplyPrecision(const SensorData& data) const {
// Round to 2 decimal places to prevent device fingerprinting
SensorData result = data;
result.x = std::round(data.x * 100.0) / 100.0;
result.y = std::round(data.y * 100.0) / 100.0;
result.z = std::round(data.z * 100.0) / 100.0;
return result;
}
std::shared_ptr<SensorSubscription> SensorInterface::Subscribe(
SensorType type,
const SensorOptions& options,
std::string& error
) {
std::lock_guard<std::mutex> lock(m_mutex);
// Check permission for motion sensors
if (RequiresPermission(type) && !m_has_motion_permission) {
error = "Motion sensor permission denied (requires sensors.motion)";
return nullptr;
}
// Clean up stopped subscriptions first
CleanupStoppedSubscriptions();
// Check subscription limit
if (m_subscriptions.size() >= MAX_SUBSCRIPTIONS) {
error = "Maximum subscription limit reached (10)";
return nullptr;
}
// Create subscription with capped frequency
int id = m_next_subscription_id++;
SensorOptions capped_options = options;
capped_options.frequency = std::min(options.frequency, MAX_FREQUENCY);
auto subscription = std::make_shared<SensorSubscription>(id, type, capped_options);
subscription->Start();
m_subscriptions[id] = subscription;
return subscription;
}
std::shared_ptr<SensorSubscription> SensorInterface::Subscribe(
const std::string& sensor_name,
const SensorOptions& options,
std::string& error
) {
SensorType type;
if (!ParseSensorType(sensor_name, type)) {
error = "Unknown sensor type: " + sensor_name;
return nullptr;
}
return Subscribe(type, options, error);
}
void SensorInterface::Unsubscribe(int subscription_id) {
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_subscriptions.find(subscription_id);
if (it != m_subscriptions.end()) {
it->second->Stop();
m_subscriptions.erase(it);
}
}
void SensorInterface::UnsubscribeAll() {
std::lock_guard<std::mutex> lock(m_mutex);
for (auto& [id, subscription] : m_subscriptions) {
subscription->Stop();
}
m_subscriptions.clear();
}
size_t SensorInterface::GetActiveSubscriptionCount() const {
std::lock_guard<std::mutex> lock(m_mutex);
size_t count = 0;
for (const auto& [id, subscription] : m_subscriptions) {
if (subscription->IsActive()) {
count++;
}
}
return count;
}
void SensorInterface::Shutdown() {
UnsubscribeAll();
}
std::shared_ptr<SensorSubscription> SensorInterface::GetSubscription(int id) {
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_subscriptions.find(id);
if (it != m_subscriptions.end()) {
return it->second;
}
return nullptr;
}
void SensorInterface::CleanupStoppedSubscriptions() {
// Called with lock held
for (auto it = m_subscriptions.begin(); it != m_subscriptions.end();) {
if (!it->second->IsActive()) {
it = m_subscriptions.erase(it);
} else {
++it;
}
}
}
// ============================================================================
// Lua API Implementation
// ============================================================================
// Weak reference to subscription stored in userdata
struct LuaSubscriptionRef {
std::weak_ptr<SensorSubscription> subscription;
SensorInterface* sensors;
};
static const char* SUBSCRIPTION_METATABLE = "mosis.SensorSubscription";
static LuaSubscriptionRef* GetSubscriptionRef(lua_State* L, int idx) {
return static_cast<LuaSubscriptionRef*>(luaL_checkudata(L, idx, SUBSCRIPTION_METATABLE));
}
static std::shared_ptr<SensorSubscription> GetSubscription(lua_State* L, int idx) {
auto ref = GetSubscriptionRef(L, idx);
return ref->subscription.lock();
}
// Subscription methods
static int LuaSubscription_stop(lua_State* L) {
auto ref = GetSubscriptionRef(L, 1);
auto subscription = ref->subscription.lock();
if (subscription && ref->sensors) {
ref->sensors->Unsubscribe(subscription->GetId());
}
return 0;
}
static int LuaSubscription_getId(lua_State* L) {
auto subscription = GetSubscription(L, 1);
if (subscription) {
lua_pushinteger(L, subscription->GetId());
} else {
lua_pushinteger(L, 0);
}
return 1;
}
static int LuaSubscription_isActive(lua_State* L) {
auto subscription = GetSubscription(L, 1);
lua_pushboolean(L, subscription && subscription->IsActive());
return 1;
}
static int LuaSubscription_getFrequency(lua_State* L) {
auto subscription = GetSubscription(L, 1);
if (subscription) {
lua_pushinteger(L, subscription->GetFrequency());
} else {
lua_pushinteger(L, 0);
}
return 1;
}
static int LuaSubscription_gc(lua_State* L) {
auto ref = GetSubscriptionRef(L, 1);
ref->~LuaSubscriptionRef();
return 0;
}
static const luaL_Reg subscription_methods[] = {
{"stop", LuaSubscription_stop},
{"getId", LuaSubscription_getId},
{"isActive", LuaSubscription_isActive},
{"getFrequency", LuaSubscription_getFrequency},
{nullptr, nullptr}
};
static void CreateSubscriptionMetatable(lua_State* L) {
luaL_newmetatable(L, SUBSCRIPTION_METATABLE);
// __index = methods table
lua_newtable(L);
luaL_setfuncs(L, subscription_methods, 0);
lua_setfield(L, -2, "__index");
// __gc
lua_pushcfunction(L, LuaSubscription_gc);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1);
}
static void PushSubscription(lua_State* L, std::shared_ptr<SensorSubscription> subscription, SensorInterface* sensors) {
auto ref = static_cast<LuaSubscriptionRef*>(lua_newuserdata(L, sizeof(LuaSubscriptionRef)));
new (ref) LuaSubscriptionRef{subscription, sensors};
luaL_setmetatable(L, SUBSCRIPTION_METATABLE);
}
// sensors.subscribe(sensorName, callback, options)
static int LuaSensors_subscribe(lua_State* L) {
auto sensors = static_cast<SensorInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!sensors) {
lua_pushnil(L);
lua_pushstring(L, "Sensor interface not available");
return 2;
}
// Get sensor name
const char* sensor_name = luaL_checkstring(L, 1);
// Validate callback
if (!lua_isfunction(L, 2)) {
return luaL_error(L, "Second argument must be a callback function");
}
// Parse options from third argument (optional table)
SensorOptions options;
if (lua_istable(L, 3)) {
lua_getfield(L, 3, "frequency");
if (lua_isnumber(L, -1)) {
options.frequency = static_cast<int>(lua_tointeger(L, -1));
}
lua_pop(L, 1);
}
// Store callback in registry
lua_pushvalue(L, 2);
int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
std::string error;
auto subscription = sensors->Subscribe(sensor_name, options, error);
if (!subscription) {
luaL_unref(L, LUA_REGISTRYINDEX, callback_ref);
lua_pushnil(L);
lua_pushstring(L, error.c_str());
return 2;
}
// Set up callback that applies precision reduction
subscription->SetOnData([L, callback_ref, sensors](const SensorData& raw_data) {
// Apply precision reduction
SensorData data = sensors->ApplyPrecision(raw_data);
lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
if (lua_isfunction(L, -1)) {
// Create data table
lua_newtable(L);
lua_pushnumber(L, data.x);
lua_setfield(L, -2, "x");
lua_pushnumber(L, data.y);
lua_setfield(L, -2, "y");
lua_pushnumber(L, data.z);
lua_setfield(L, -2, "z");
lua_pushinteger(L, data.timestamp);
lua_setfield(L, -2, "timestamp");
lua_pcall(L, 1, 0, 0);
} else {
lua_pop(L, 1);
}
});
PushSubscription(L, subscription, sensors);
return 1;
}
// sensors.unsubscribeAll()
static int LuaSensors_unsubscribeAll(lua_State* L) {
auto sensors = static_cast<SensorInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
if (sensors) {
sensors->UnsubscribeAll();
}
return 0;
}
// sensors.getSubscriptionCount()
static int LuaSensors_getSubscriptionCount(lua_State* L) {
auto sensors = static_cast<SensorInterface*>(lua_touserdata(L, lua_upvalueindex(1)));
if (sensors) {
lua_pushinteger(L, static_cast<lua_Integer>(sensors->GetActiveSubscriptionCount()));
} else {
lua_pushinteger(L, 0);
}
return 1;
}
// Helper to set a global in the real _G (bypassing any proxy)
static void SetGlobalInRealG(lua_State* L, const char* name) {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
if (lua_getmetatable(L, -1)) {
lua_getfield(L, -1, "__index");
if (lua_istable(L, -1)) {
lua_pushvalue(L, -4);
lua_setfield(L, -2, name);
lua_pop(L, 4);
return;
}
lua_pop(L, 2);
}
lua_pushvalue(L, -2);
lua_setfield(L, -2, name);
lua_pop(L, 2);
}
void RegisterSensorAPI(lua_State* L, SensorInterface* sensors) {
// Create subscription metatable
CreateSubscriptionMetatable(L);
// Create sensors table
lua_newtable(L);
// sensors.subscribe
lua_pushlightuserdata(L, sensors);
lua_pushcclosure(L, LuaSensors_subscribe, 1);
lua_setfield(L, -2, "subscribe");
// sensors.unsubscribeAll
lua_pushlightuserdata(L, sensors);
lua_pushcclosure(L, LuaSensors_unsubscribeAll, 1);
lua_setfield(L, -2, "unsubscribeAll");
// sensors.getSubscriptionCount
lua_pushlightuserdata(L, sensors);
lua_pushcclosure(L, LuaSensors_getSubscriptionCount, 1);
lua_setfield(L, -2, "getSubscriptionCount");
// Set as global (bypasses sandbox proxy)
SetGlobalInRealG(L, "sensors");
}
} // namespace mosis

View File

@@ -0,0 +1,138 @@
// sensor_interface.h - Sensor interface for Lua sandbox
// Milestone 15: Motion sensors with fingerprinting prevention
#pragma once
#include <string>
#include <memory>
#include <functional>
#include <mutex>
#include <atomic>
#include <vector>
#include <unordered_map>
#include <chrono>
struct lua_State;
namespace mosis {
enum class SensorType {
Accelerometer, // Requires sensors.motion
Gyroscope, // Requires sensors.motion
Magnetometer, // Requires sensors.motion
Proximity, // Auto-granted
AmbientLight // Auto-granted
};
struct SensorData {
double x = 0.0; // Primary axis / value
double y = 0.0; // Secondary axis (if applicable)
double z = 0.0; // Tertiary axis (if applicable)
int64_t timestamp = 0; // Milliseconds since epoch
};
struct SensorOptions {
int frequency = 60; // Hz, capped at 60
};
class SensorSubscription {
public:
using DataCallback = std::function<void(const SensorData&)>;
SensorSubscription(int id, SensorType type, const SensorOptions& options);
~SensorSubscription();
int GetId() const { return m_id; }
SensorType GetType() const { return m_type; }
bool IsActive() const { return m_active; }
int GetFrequency() const { return m_frequency; }
void Start();
void Stop();
void SetOnData(DataCallback cb) { m_on_data = std::move(cb); }
// For mock mode - simulate sensor data
void SimulateData(const SensorData& data);
private:
int m_id;
SensorType m_type;
int m_frequency;
std::atomic<bool> m_active{false};
DataCallback m_on_data;
mutable std::mutex m_mutex;
};
class SensorInterface {
public:
SensorInterface(const std::string& app_id);
~SensorInterface();
// Check if app has motion sensor permission
bool HasMotionPermission() const { return m_has_motion_permission; }
void SetMotionPermission(bool granted) { m_has_motion_permission = granted; }
// Subscribe to a sensor
// Returns subscription on success, nullptr on failure
std::shared_ptr<SensorSubscription> Subscribe(
SensorType type,
const SensorOptions& options,
std::string& error
);
// Subscribe by sensor name
std::shared_ptr<SensorSubscription> Subscribe(
const std::string& sensor_name,
const SensorOptions& options,
std::string& error
);
// Stop a specific subscription
void Unsubscribe(int subscription_id);
// Stop all subscriptions for this app
void UnsubscribeAll();
// Get active subscription count
size_t GetActiveSubscriptionCount() const;
// Cleanup on app stop
void Shutdown();
// For testing
void SetMockMode(bool enabled) { m_mock_mode = enabled; }
bool IsMockMode() const { return m_mock_mode; }
// Get subscription by ID
std::shared_ptr<SensorSubscription> GetSubscription(int id);
// Check if sensor type requires permission
static bool RequiresPermission(SensorType type);
// Parse sensor name to type
static bool ParseSensorType(const std::string& name, SensorType& out_type);
// Get sensor name from type
static const char* GetSensorName(SensorType type);
// Apply precision reduction to prevent fingerprinting
SensorData ApplyPrecision(const SensorData& data) const;
private:
std::string m_app_id;
std::unordered_map<int, std::shared_ptr<SensorSubscription>> m_subscriptions;
mutable std::mutex m_mutex;
bool m_mock_mode = true;
bool m_has_motion_permission = false;
int m_next_subscription_id = 1;
static constexpr int MAX_SUBSCRIPTIONS = 10;
static constexpr int MAX_FREQUENCY = 60; // Hz
void CleanupStoppedSubscriptions();
};
// Register sensors.* APIs as globals
void RegisterSensorAPI(lua_State* L, SensorInterface* sensors);
} // namespace mosis