#!/usr/bin/env python3 """Relances natives dans une session de test déjà démarrée (firmware non distribué).""" import argparse import http.server import json import os from pathlib import Path import runpy import subprocess import threading import time import uuid def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--emu-home', type=Path, required=True) p.add_argument('--agent-port', type=int, required=True) p.add_argument('--action', choices=('respring', 'reset'), required=True) p.add_argument('--cycles', type=int, default=3) p.add_argument('--output', type=Path, required=True) args = p.parse_args() if args.cycles < 1: p.error('--cycles doit être positif') home = args.emu_home.resolve() run = home / 'run' bindir = Path(__file__).resolve().parents[1] / 'bin' os.environ.update(IPOD_EMU_HOME=str(home), IPOD_AGENT_PORT=str(args.agent_port)) Agent = runpy.run_path(str(bindir / 'ipod-agent'))['Agent'] def call(*fields, data=None): a = Agent(timeout=10) try: ok, body, detail = a.call(*fields, data=data) if not ok: raise RuntimeError(body.decode(errors='replace')) return body finally: a.f.close() a.s.close() def springboard(): return next(line.split()[0] for line in call('ps').decode().splitlines() if line.endswith(' SpringBoard')) nonce = uuid.uuid4().hex.encode() marker = '/private/var/mobile/Library/test-display-' + nonce.decode() class Fixture(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Length', str(len(nonce))) self.end_headers() self.wfile.write(nonce) def log_message(self, *unused): pass server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Fixture) threading.Thread(target=server.serve_forever, daemon=True).start() url = f'http://10.0.2.2:{server.server_port}/preuve' args.output.mkdir(parents=True, exist_ok=True) qpid = (run / 'qemu.pid').read_text() npid = (run / 'netd.pid').read_text() inode = (run / 'nand').stat().st_ino results = [] try: call('put', marker, '600', len(nonce), data=nonce) for cycle in range(args.cycles): oldpid = springboard() offset = (run / 'serial.log').stat().st_size started = time.monotonic() subprocess.run([str(bindir / 'ipod-emu'), args.action, '-W'], check=True, timeout=340) newpid = springboard() fresh = (run / 'serial.log').read_bytes()[offset:] if args.action == 'reset': assert b'Darwin Kernel Version' in fresh else: assert oldpid != newpid assert b'Darwin Kernel Version' not in fresh assert b'locked=0 passcode=0' in call('screen') assert (run / 'qemu.pid').read_text() == qpid assert (run / 'netd.pid').read_text() == npid assert (run / 'nand').stat().st_ino == inode assert call('get', marker) == nonce assert nonce in call('fetch', url) call('launch', 'com.apple.calculator', '-') time.sleep(3) assert call('front').strip() == b'com.apple.calculator' capture = args.output.resolve() / f'{args.action}-{cycle + 1}.png' subprocess.run([str(bindir / 'ipod-qmp'), 'screenshot', str(capture)], check=True, timeout=15) result = dict(cycle=cycle + 1, seconds=round(time.monotonic() - started, 1), qemu_pid=int(qpid), netd_pid=int(npid), springboard_pid=newpid, unlocked=True, nand_preserved=True, http=True, net=call('net').decode(), front='com.apple.calculator') results.append(result) print(json.dumps(result, ensure_ascii=False), flush=True) (args.output / 'validation.json').write_text( json.dumps(results, ensure_ascii=False, indent=2) + '\n') call('rm', marker) call('sync') finally: server.shutdown() server.server_close() if __name__ == '__main__': main()