#!/usr/bin/env python3 """Real Pi 5 firmware configuration roundtrips without any hardware access.""" import importlib.machinery import importlib.util import json import os from pathlib import Path import subprocess import sys import tempfile project=Path(__file__).resolve().parents[2] sys.path.insert(0,str(project/'tools')) from eeprom_inputs import prepare,sha256 cache,pin=prepare() work=Path(tempfile.mkdtemp(prefix='m12-eeprom.',dir=project/'out')) base=cache/'pieeprom.bin';before=sha256(base) # Use the pinned upstream parser to compare immutable firmware payloads as well # as configuration text, including both AB slots where present. loader=importlib.machinery.SourceFileLoader('upstream_eeprom',str(cache/'rpi-eeprom-config')) spec=importlib.util.spec_from_loader(loader.name,loader);upstream=importlib.util.module_from_spec(spec);loader.exec_module(upstream) original=upstream.BootloaderImage(str(base)) protected={s.filename:original.get_file(s.filename) for s in original._sections if s.filename and s.filename not in ['bootconf.txt','bootconf.sig']} def run(arguments,ok=True): result=subprocess.run([str(project/'tools/configure-pi-eeprom'),*map(str,arguments)],capture_output=True,text=True,env={**os.environ,'FDS_OFFLINE':'1'}) assert (result.returncode==0)==ok,(arguments,result.stdout,result.stderr) return result for profile, boot_order in [('production','0xf1'),('development','0xf41'),('maintenance','0xf14')]: output=work/profile run(['--profile',profile,'--output-directory',output]) manifest=json.loads((output/'manifest.json').read_text()) assert manifest['hardware_modified'] is False and manifest['custom_inputs_provided'] is False assert manifest['settings']['BOOT_ORDER'] == boot_order for name,digest in manifest['files'].items():assert sha256(output/name)==digest image=upstream.BootloaderImage(str(output/'configured.bin')) for name,data in protected.items():assert image.get_file(name)==data,(profile,name) assert 'NET_INSTALL_ENABLED=0' in image.get_file('bootconf.txt').decode() second=work/(profile+'-repeat');run(['--profile',profile,'--output-directory',second]) assert sha256(output/'configured.bin')==sha256(second/'configured.bin') assert sha256(output/'rollback.bin')==sha256(second/'rollback.bin') run(['--profile',profile,'--output-directory',output],ok=False) print(f'PASS: {profile}: actual Pi 5 firmware roundtrip, immutable payloads, deterministic files and overwrite refusal',flush=True) saved=work/'board.conf' saved.write_text('[all]\nBOOT_ORDER=0xf461\nCUSTOM_BOARD_SETTING=retained\n[gpio8=0]\nBOOT_ORDER=0xf7\nNET_INSTALL_ENABLED=1\nOTHER_SETTING=unchanged\n') output=work/'custom' run(['--current-config',saved,'--output-directory',output]) changed=(output/'configured.conf').read_text() assert changed.count('BOOT_ORDER=')==1 and 'BOOT_ORDER=0xf1' in changed assert 'CUSTOM_BOARD_SETTING=retained' in changed and '[gpio8=0]\nOTHER_SETTING=unchanged' in changed rollback=upstream.BootloaderImage(str(output/'rollback.bin')).get_file('bootconf.txt').decode() assert rollback==saved.read_text(),'Rollback lost the original conditional settings' bad=work/'bad.bin';bad.write_bytes(b'not firmware') run(['--base-image',bad,'--output-directory',work/'bad'],ok=False) run(['--base-image','/dev/null','--output-directory',work/'device'],ok=False) large=work/'large.conf';large.write_bytes(b'x'*5000) run(['--current-config',large,'--output-directory',work/'large'],ok=False) assert sha256(base)==before (work/'evidence.json').write_text(json.dumps({'format':1,'upstream_commit':pin['commit'],'base_sha256':before,'hardware_modified':False},indent=2)+'\n') link=project/'out/m12-eeprom-latest';link.unlink(missing_ok=True);link.symlink_to(work.name) print(f'PASS: preserved custom settings, exact rollback, invalid-input rejection and unchanged firmware input: {work}') print('SKIP: EEPROM application, physical boot order, PMIC and Pi timing')