Document X4 Android reconnect protocol research and recovery options
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""Read selected protobuf descriptors from the pinned SDK without loading its code.
|
||||
|
||||
Research only: schema presence does not establish X4 firmware support, writability,
|
||||
transport availability or a complete command frame. No device/network access.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SDK_SHA256 = "6d20aca1930101293308c056cef552c0beb8cbf1d6c7d567a79e95a52f9b1373"
|
||||
FILES = {"options.proto", "message_code.proto", "commands/get_options.proto", "commands/set_options.proto"}
|
||||
OPTIONS = {"udisk_mode", "bt_wakeup_sw"}
|
||||
VALUES = {"UDISK_MODE", "UDISK_MODE_PC", "UDISK_MODE_ANDROID", "BT_WAKEUP_SW",
|
||||
"PHONE_COMMAND_GET_OPTIONS", "PHONE_COMMAND_SET_OPTIONS", "PHONE_COMMAND_REBOOT_CAMERA"}
|
||||
|
||||
|
||||
def varint(data, cursor):
|
||||
result = 0
|
||||
for shift in range(0, 70, 7):
|
||||
value = data[cursor]
|
||||
cursor += 1
|
||||
result |= (value & 127) << shift
|
||||
if not value & 128:
|
||||
return result, cursor
|
||||
raise ValueError("Invalid varint")
|
||||
|
||||
|
||||
def fields(data, terminated=False):
|
||||
result, cursor = {}, 0
|
||||
while cursor < len(data):
|
||||
key, cursor = varint(data, cursor)
|
||||
if key == 0 and terminated:
|
||||
break
|
||||
number, wire = key >> 3, key & 7
|
||||
if not number:
|
||||
raise ValueError("Invalid field number")
|
||||
if wire == 0:
|
||||
value, cursor = varint(data, cursor)
|
||||
elif wire == 2:
|
||||
length, cursor = varint(data, cursor)
|
||||
value = data[cursor:cursor + length]
|
||||
cursor += length
|
||||
if len(value) != length:
|
||||
raise ValueError("Truncated field")
|
||||
else:
|
||||
raise ValueError("Unsupported descriptor wire type")
|
||||
result.setdefault(number, []).append(value)
|
||||
return result
|
||||
|
||||
|
||||
def name(record):
|
||||
return record[1][0].decode("utf-8")
|
||||
|
||||
|
||||
def enum(record):
|
||||
return {name(item): item[2][0] for raw in record.get(2, [])
|
||||
if (item := fields(raw)) and name(item) in VALUES}
|
||||
|
||||
|
||||
def message(record, all_fields=False):
|
||||
selected = []
|
||||
for raw in record.get(2, []):
|
||||
field = fields(raw)
|
||||
if all_fields or name(field) in OPTIONS:
|
||||
selected.append({"name": name(field), "number": field[3][0],
|
||||
"label": field[4][0], "type": field[5][0],
|
||||
"type_name": field.get(6, [b""])[0].decode()})
|
||||
enums = {name(item): values for raw in record.get(4, [])
|
||||
if (item := fields(raw)) and (values := enum(item))}
|
||||
return {"fields": selected, "enums": enums}
|
||||
|
||||
|
||||
def inspect(data):
|
||||
if hashlib.sha256(data).hexdigest() != SDK_SHA256:
|
||||
raise ValueError("SDK bytes differ from the inspected version")
|
||||
found = {}
|
||||
for match in re.finditer(rb"\n([\x01-\x7f])([\w/.-]+\.proto)\x12", data):
|
||||
filename = match[2].decode()
|
||||
if match[1][0] != len(match[2]) or filename not in FILES:
|
||||
continue
|
||||
descriptor = fields(data[match.start():match.start() + 65536], terminated=True)
|
||||
messages = {name(item): message(item, filename.startswith("commands/"))
|
||||
for raw in descriptor.get(4, []) if (item := fields(raw))}
|
||||
found[filename] = {"offset": match.start(), "messages": messages,
|
||||
"enums": {name(item): enum(item) for raw in descriptor.get(5, [])
|
||||
if (item := fields(raw)) and enum(item)}}
|
||||
if set(found) != FILES:
|
||||
raise ValueError("Required descriptors not found")
|
||||
return found
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = (ROOT / "build/sdk/lib/libCameraSDK.so").read_bytes()
|
||||
print(json.dumps({"session": "RESEARCH01-schema", "utc": datetime.now(timezone.utc).isoformat(),
|
||||
"monotonic": time.monotonic(), "sdk_sha256": SDK_SHA256,
|
||||
"source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
"hardware_tested": False, "descriptors": inspect(data)}, indent=2))
|
||||
Reference in New Issue
Block a user