Skip to content

iOS Integration

The SensorChaos iOS agent intercepts CLLocationManager, CMMotionManager, and CMAltimeter calls via Objective-C method swizzling. The app receives injected sensor data through the same APIs it normally uses and cannot distinguish it from real hardware readings.

In Xcode, go to File → Add Package Dependencies and enter the repository URL. Add SensorChaosAgent to your debug target only.

In Package.swift (if you manage dependencies that way):

.package(url: "https://github.com/sensorchaos/sensorchaos-ios-agent", from: "0.1.0")
// In your debug target's dependencies:
.product(name: "SensorChaosAgent", package: "sensorchaos-ios-agent")

Call SensorChaos.start() in your AppDelegate or @main struct before creating any CLLocationManager, CMMotionManager, or CMAltimeter instances. The call is idempotent — safe to call multiple times.

// AppDelegate.swift (UIKit)
import SensorChaosAgent
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
#if DEBUG
SensorChaos.start()
#endif
return true
}
// MyApp.swift (SwiftUI)
import SensorChaosAgent
@main struct MyApp: App {
init() {
#if DEBUG
SensorChaos.start()
#endif
}
var body: some Scene { WindowGroup { ContentView() } }
}

The simulator shares the Mac’s loopback interface, so port 19847 is reachable directly — no forwarding step needed:

Terminal window
sensorchaos run gnss/gulf-spoofing-2026 --agent

Target a specific simulator by UDID (from sensorchaos devices):

Terminal window
sensorchaos run gnss/gulf-spoofing-2026 --agent --device <simulator-udid>

The CLI handles port forwarding automatically via idb — install it once and the --agent flag takes care of the rest:

Terminal window
sensorchaos run gnss/gulf-spoofing-2026 --agent --device <device-udid>

The CLI runs idb forward 19847 19847 before the scenario and removes it when the scenario ends. No manual port-forward step is required.

The CLI communicates with the agent over a JSON Lines TCP socket on port 19847. Each command gets an ack response on the same connection. Commands include:

{"cmd":"location","lat":37.334,"lng":-122.009,"accuracy_m":5.0}
{"cmd":"sensor","type":"accelerometer","values":[0.1,-0.2,9.8]}
{"cmd":"status"}
{"cmd":"stop"}

The status command returns the agent’s current injection state:

{"ok":true,"status":{"injecting":true,"location":{"lat":37.334,"lng":-122.009,"accuracy_m":5.0},"sensors":{"accelerometer":[0.1,-0.2,9.8]}}}

status.location is null when signal loss is active or no fix has been sent. status.sensors is a map of sensor type to the last-written values array. Note that satellites is absent on iOS (Android only).

CLLocationManager.setDelegate: is swizzled. Every delegate assignment in the app wraps the app’s delegate in a ChaosLocationDelegate proxy. The proxy intercepts locationManager(_:didUpdateLocations:) and replaces the delivered CLLocation objects with injected values. All other delegate methods (authorization changes, heading updates, etc.) are forwarded transparently.

The CLLocationManager.location property getter is also swizzled. Apps that read manager.location directly — instead of using the delegate — see nil during signal loss and the injected fix when active.

CMMotionManager.startAccelerometerUpdatesToQueue:withHandler:, startGyroUpdatesToQueue:withHandler:, and startMagnetometerUpdatesToQueue:withHandler: are swizzled to wrap the app’s handler block. When injection is active, the wrapped handler writes injected values into the CMMotionData object’s ivars before forwarding to the app.

CMAltimeter.startRelativeAltitudeUpdatesToQueue:withHandler: is swizzled similarly. Injected pressure (in hPa) and relative altitude (in metres) are written into CMAltitudeData via object_setIvar before the app’s handler is called.

SensorCLI typeNotes
GPS / LocationlocationSignal loss supported (nil from getter + suppressed callbacks)
Accelerometersensor / accelerometerx, y, z in m/s²
Gyroscopesensor / gyroscopex, y, z in rad/s
Magnetometersensor / magnetometerx, y, z in µT
Barometersensor / barometersingle value in hPa
WiFi scanNot available on iOS — commands accepted silently
Cell towerNot available on iOS — commands accepted silently
FeatureiOSAndroid
StartupSensorChaos.start() (explicit)ChaosContentProvider (auto)
GPS injection✅ SwizzleTestProvider API
Pull-API signal losslocation getter swizzlegetLastKnownLocation()
IMU injection✅ SwizzleSensorManager proxy
Barometer injection✅ SwizzleSensorManager proxy
WiFi scan injection⬜ Not available⬜ Backlog
Cell tower injection⬜ Not available⬜ Backlog
Port forwardingNot needed (simulator) / auto via idb (device)auto via adb forward

SensorChaosAgent is added to the debug target only, so the agent is completely absent from release builds at the linker level. No runtime checks or #if DEBUG guards are needed — all agent classes simply do not exist in the release binary.

  • iOS 14+
  • Xcode 15+ / Swift 5.9+
  • NSLocationWhenInUseUsageDescription in Info.plist
  • NSMotionUsageDescription in Info.plist (if testing motion sensors)
  • Agent linked in debug target only