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.
1. Add the Swift package
Section titled “1. Add the Swift package”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")2. Call SensorChaos.start()
Section titled “2. Call SensorChaos.start()”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.
Startup
Section titled “Startup”// 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() } }}3. Run a scenario
Section titled “3. Run a scenario”iOS Simulator
Section titled “iOS Simulator”The simulator shares the Mac’s loopback interface, so port 19847 is reachable directly — no forwarding step needed:
sensorchaos run gnss/gulf-spoofing-2026 --agentTarget a specific simulator by UDID (from sensorchaos devices):
sensorchaos run gnss/gulf-spoofing-2026 --agent --device <simulator-udid>Real device
Section titled “Real device”The CLI handles port forwarding automatically via idb — install it once and the --agent flag takes care of the rest:
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.
Agent protocol
Section titled “Agent protocol”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).
How injection works
Section titled “How injection works”Location (push-based apps)
Section titled “Location (push-based apps)”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.
Location (pull-based apps)
Section titled “Location (pull-based apps)”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.
Motion sensors
Section titled “Motion sensors”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.
Barometer
Section titled “Barometer”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.
Sensor coverage
Section titled “Sensor coverage”| Sensor | CLI type | Notes |
|---|---|---|
| GPS / Location | location | Signal loss supported (nil from getter + suppressed callbacks) |
| Accelerometer | sensor / accelerometer | x, y, z in m/s² |
| Gyroscope | sensor / gyroscope | x, y, z in rad/s |
| Magnetometer | sensor / magnetometer | x, y, z in µT |
| Barometer | sensor / barometer | single value in hPa |
| WiFi scan | — | Not available on iOS — commands accepted silently |
| Cell tower | — | Not available on iOS — commands accepted silently |
Comparison with Android agent
Section titled “Comparison with Android agent”| Feature | iOS | Android |
|---|---|---|
| Startup | SensorChaos.start() (explicit) | ✅ ChaosContentProvider (auto) |
| GPS injection | ✅ Swizzle | ✅ TestProvider API |
| Pull-API signal loss | ✅ location getter swizzle | ✅ getLastKnownLocation() |
| IMU injection | ✅ Swizzle | ✅ SensorManager proxy |
| Barometer injection | ✅ Swizzle | ✅ SensorManager proxy |
| WiFi scan injection | ⬜ Not available | ⬜ Backlog |
| Cell tower injection | ⬜ Not available | ⬜ Backlog |
| Port forwarding | Not needed (simulator) / auto via idb (device) | auto via adb forward |
Release build behaviour
Section titled “Release build behaviour”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.
Requirements
Section titled “Requirements”- iOS 14+
- Xcode 15+ / Swift 5.9+
NSLocationWhenInUseUsageDescriptioninInfo.plistNSMotionUsageDescriptioninInfo.plist(if testing motion sensors)- Agent linked in debug target only