Skip to content

TCP Protocol

The SensorChaos agent exposes a JSON-Lines TCP server on port 19847. Every command is a single JSON object terminated by a newline (\n). The agent responds with exactly one JSON object per command, also newline-terminated.

This means you can drive sensor injection from any language that can open a TCP connection — Python, Go, Ruby, shell scripts, or a custom test harness.

Client Agent (port 19847)
│ │
│── {"cmd":"ping"}\n ──────────────► │
│◄─── {"ok":true,"agent":"SensorChaos","version":"0.1.0"}\n ─│
│ │
│── {"cmd":"location","lat":25.197,"lng":55.274,...}\n ──────►│
│◄─── {"ok":true}\n ──────────────────────────────────────── │
PlatformHow to reach port 19847
Android emulatoradb forward tcp:19847 tcp:19847 (CLI does this automatically)
Android real deviceSame ADB forward over USB
iOS Simulator127.0.0.1:19847 directly (shares Mac loopback)
iOS real deviceidb forward 19847 19847

Handshake and version check. Use this to confirm the agent is alive and read its version before sending injection commands.

{
"cmd": "ping"
}

Response:

{
"ok": true,
"agent": "SensorChaos",
"version": "0.1.0"
}

Query which injectors are available on the connected device. wifi and cell are reserved for later development and always return false.

{
"cmd": "capabilities"
}

barometer is present on iOS only. Android agents omit it.

Response:

{
"ok": true,
"capabilities": {
"location": true,
"sensor": true,
"barometer": true,
"wifi": false,
"cell": false
}
}

Read the agent’s current injection state — last injected values per injector type. Useful for debugging and live diff UIs.

{
"cmd": "status"
}

location is null when signal loss is active or no fix has been sent yet.

satellites is present on Android only.

Response:

{
"ok": true,
"status": {
"injecting": true,
"location": {
"lat": 25.197,
"lng": 55.274,
"accuracy_m": 5,
"satellites": 12
},
"sensors": {
"accelerometer": [
0.1,
9.7,
0.3
],
"gyroscope": [
0,
0,
0
]
}
}
}

Inject a GPS fix into the app. Overrides the location returned to all LocationManager / CLLocationManager listeners.

{
"cmd": "location",
"lat": 25.197,
"lng": 55.274,
"accuracy_m": 5,
"satellites": 12,
"altitude_m": 10,
"speed_mps": 8.3,
"bearing_deg": 92
}
FieldTypeRequiredDescription
latnumber | nullYesLatitude in decimal degrees. null triggers signal loss.
lngnumber | nullYesLongitude in decimal degrees. null triggers signal loss.
accuracy_mnumber | nullYesReported accuracy radius in metres.
satellitesintegerNoSatellite count (Android only).
altitude_mnumberNoAltitude in metres above sea level.
speed_mpsnumberNoSpeed in metres per second.
bearing_degnumberNoBearing in degrees (0–360).

Signal loss: send "lat": null, "lng": null, "accuracy_m": null to simulate GPS unavailability. The agent stops delivering location updates until the next fix.

Response:

{
"ok": true
}

Inject IMU or barometer values. The agent replaces the real sensor readings delivered to the app’s SensorEventListener / CMMotionManager handlers.

{
"cmd": "sensor",
"type": "accelerometer",
"values": [
0.1,
9.7,
0.3
],
"accuracy": 3
}
FieldTypeRequiredDescription
type"accelerometer" | "gyroscope" | "magnetometer" | "barometer"YesSensor type — see table below.
valuesnumber[]YesSensor reading (see units per type below).
accuracyintegerNoAndroid SensorManager.SENSOR_STATUS_* constant (0–3). Default: 3 (high).

Sensor types and units:

typevaluesUnits
accelerometer[x, y, z]m/s²
gyroscope[x, y, z]rad/s
magnetometer[x, y, z]µT
barometer[pressure_hpa]hPa

Response:

{
"ok": true
}

Inject Wi-Fi scan results (reserved — not yet implemented).

{
"cmd": "wifi",
"results": [
{
"ssid": "CafeNet",
"bssid": "aa:bb:cc:dd:ee:ff",
"rssi": -65,
"frequency": 2412
}
]
}
FieldTypeRequiredDescription
resultsWifiResult[] | nullYesScan results to inject, or null to clear the override and resume real scans.

Response:

{
"ok": true,
"ignored": true
}

Inject cell tower data (reserved — not yet implemented).

{
"cmd": "cell",
"towers": [
{
"mcc": 234,
"mnc": 20,
"lac": 1234,
"cid": 56789,
"rssi": -80
}
]
}
FieldTypeRequiredDescription
towersCellTower[] | nullYesTower list to inject, or null to clear the override.

Response:

{
"ok": true,
"ignored": true
}

Every command gets exactly one response line:

{"ok": true}
{"ok": false, "error": "unknown command: foo"}
{"ok": true, "ignored": true}
FieldAlways presentDescription
okYestrue on success, false on error
errorWhen ok: falseHuman-readable error message
ignoredWhen command was skippedtrue when the command is recognised but not yet implemented (forward-compat)

import socket, json
def send(sock, cmd):
sock.sendall((json.dumps(cmd) + "\n").encode())
return json.loads(sock.makefile().readline())
with socket.create_connection(("127.0.0.1", 19847)) as s:
print(send(s, {"cmd": "ping"}))
# Inject a spoofed GPS fix
send(s, {
"cmd": "location",
"lat": 25.197, "lng": 55.274,
"accuracy_m": 5.0, "satellites": 12
})
# Simulate signal loss
send(s, {"cmd": "location", "lat": None, "lng": None, "accuracy_m": None})

Always send ping first and check version in the response. The CLI requires agent version ≥ 0.1.0. If you’re building your own client, reject agents below your minimum supported version to avoid silent protocol mismatches.

{"ok": true, "agent": "SensorChaos", "version": "0.1.0"}

Version follows semver. Breaking protocol changes increment the minor version.