XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
"""Actual independent VGA scanouts, UI input, topology changes and USB cold-boot persistence."""
import argparse
import json
from pathlib import Path
import re
import shutil
import socket
import subprocess
import time

from PIL import Image
from TestBoot import ROOT, Qmp, digest, read_json
from IndependentDocumentEvidence import window_geometry


def verify_displays(image):
    image = Path(image).resolve()
    manifest = read_json(str(image) + '.json')
    assert manifest['Edition'] == 'Desktop' and manifest['UsbWorkspace']
    proof = ROOT / 'build/IndependentKernel' / ('MultiDisplay-' + str(time.time_ns()))
    proof.mkdir()
    disk = proof / 'PrivateUsb.img'
    shutil.copyfile(image, disk)
    sources = dict(manifest['IndependentKernelBuild']['SourceSha256'])
    sources['Tools/IndependentKernel/TestMultiDisplay.py'] = digest(__file__)
    result = {'Kind': 'XfeIndependentMultiDisplayProof', 'Version': 1, 'Passed': False,
              'ImagePath': str(image), 'ImageSha256': digest(image), 'ManifestSha256': digest(str(image) + '.json'),
              'KernelSha256': manifest['KernelSha256'], 'SourceHashes': sources,
              'Input': 'Actual QMP keyboard and relative mouse packets only', 'HostWorkspaceInjection': False,
              'OutputBackend': 'Two independent PCI VGA devices and real GOP/Limine framebuffers',
              'PhysicalHardwareAcceptancePassed': False, 'HotplugAcceptancePassed': False,
              'Boots': [], 'EvidenceFiles': []}

    def ref(path):
        return {'Path': path.relative_to(proof).as_posix(), 'Sha256': digest(path)}

    try:
        for boot_number in (1, 2, 3):
            folder = proof / ('Boot' + str(boot_number)); folder.mkdir()
            shutil.copyfile(ROOT / '.tools/firmware/edk2-i386-vars.fd', folder / 'Vars.fd')
            with socket.socket() as reservation:
                reservation.bind(('127.0.0.1', 0)); port = reservation.getsockname()[1]
            command = [str(ROOT / '.tools/qemu/qemu-system-x86_64.exe'), '-machine', 'q35', '-accel', 'tcg',
                       '-cpu', 'max', '-smp', '8', '-m', '1024M', '-vga', 'none',
                       '-device', 'VGA,id=Display1,vgamem_mb=64,edid=on,xres=1280,yres=800,xmax=1280,ymax=800',
                       '-display', 'none', '-net', 'none', '-rtc', 'base=utc',
                       '-drive', f'if=pflash,format=raw,readonly=on,file={ROOT / ".tools/firmware/edk2-x86_64-code.fd"}',
                       '-drive', f'if=pflash,format=raw,file={folder / "Vars.fd"}',
                       '-device', 'qemu-xhci,id=Usb', '-drive', f'if=none,id=BootUsb,format=raw,file={disk}',
                       '-device', 'usb-storage,drive=BootUsb,bus=Usb.0,bootindex=1',
                       '-serial', f'file:{folder / "Serial.log"}', '-qmp', f'tcp:127.0.0.1:{port},server=on,wait=off',
                       '-no-reboot', '-no-shutdown']
            if boot_number != 3:
                command += ['-device', 'secondary-vga,id=Display2,vgamem_mb=64,edid=on,xres=1280,yres=800,xmax=1280,ymax=800']
            boot = {'Number': boot_number, 'QemuArguments': command, 'InputEvents': [], 'Captures': []}
            result['Boots'].append(boot)
            qmp = qemu = None

            def text():
                assert qemu.poll() is None, 'Multi-display guest exited unexpectedly'
                value = (folder / 'Serial.log').read_text(encoding='utf-8', errors='replace')
                assert not any(marker in value for marker in ('XFE_NATIVE_FAIL', 'XFE_NATIVE_EXCEPTION', 'XFE_RUNTIME_FATAL',
                    'XFE_ZERO_FAILFAST', 'XFE_INDEPENDENT_APPLICATION_FAILED', 'Rendering failed:')), value[-3000:]
                return value

            def wait(marker, after=0, seconds=60):
                end = time.monotonic() + seconds
                while time.monotonic() < end:
                    if marker in text()[after:]:
                        return
                    time.sleep(.1)
                raise AssertionError('Display acknowledgement timeout: ' + marker + '\n' + text()[-3000:])

            def state():
                records = re.findall(r'^XFE_DESKTOP_DISPLAYS\|([^\r\n]+)\r?\n', text(), re.M)
                assert records, 'No actual presented display state'
                fields = records[-1].split('|')
                return dict(zip(fields[::2], fields[1::2]))

            def wait_state(**required):
                end = time.monotonic() + 35
                while time.monotonic() < end:
                    # FIRST_FRAME precedes the asynchronous diagnostics worker.
                    # Missing/partial records are pending until the same deadline;
                    # they must never be substituted for a presented state.
                    records = re.findall(r'^XFE_DESKTOP_DISPLAYS\|([^\r\n]+)\r?\n', text(), re.M)
                    if records:
                        fields = records[-1].split('|')
                        actual = dict(zip(fields[::2], fields[1::2])) if len(fields) % 2 == 0 else {}
                        if all(actual.get(key) == str(value) for key, value in required.items()): return
                    time.sleep(.1)
                raise AssertionError('Unexpected actual display state: ' + repr(state()) + ' expected ' + repr(required))

            def key(chord):
                boot['InputEvents'].append({'Key': chord, 'SerialOffset': len(text())})
                qmp.call('human-monitor-command', {'command-line': 'sendkey ' + chord + ' 30'})
                time.sleep(.17)

            pointer = [640, 400]

            def move(x, y):
                boot['InputEvents'].append({'PointerTarget': [x, y]})
                while pointer != [x, y]:
                    dx, dy = max(-80, min(80, x-pointer[0])), max(-80, min(80, y-pointer[1]))
                    qmp.call('input-send-event', {'events': [
                        {'type': 'rel', 'data': {'axis': 'x', 'value': dx}},
                        {'type': 'rel', 'data': {'axis': 'y', 'value': dy}}]})
                    pointer[0] += dx; pointer[1] += dy; time.sleep(.035)

            def button(down):
                qmp.call('input-send-event', {'events': [{'type': 'btn', 'data': {'down': down, 'button': 'left'}}]})
                time.sleep(.14)

            def click(x, y):
                move(x, y); button(True); button(False)

            def captures(name, expected='different'):
                time.sleep(.4)
                before = state()
                images = []
                for index in range(1, 2 if boot_number == 3 else 3):
                    ppm = folder / (name + '-' + str(index) + '.ppm')
                    qmp.call('screendump', {'filename': str(ppm), 'device': 'Display' + str(index), 'head': 0})
                    with Image.open(ppm) as source:
                        frame = source.convert('RGB'); assert frame.size == (1280, 800), frame.size
                        path = ppm.with_suffix('.png'); frame.save(path); images.append(frame.copy())
                    record = ref(path); result['EvidenceFiles'].append(record)
                    boot['Captures'].append({'Name': name, 'Output': index, **record, 'State': before})
                assert state() == before, 'Display state changed while capturing independent outputs'
                if expected == 'same':
                    assert images[0].tobytes() == images[1].tobytes(), 'Duplicated scanouts have different actual pixels'
                if expected == 'different':
                    assert images[0].tobytes() != images[1].tobytes(), 'Independent scanouts unexpectedly contain identical pixels'
                if expected == 'first-black':
                    assert images[0].getextrema() == ((0, 0), (0, 0), (0, 0)), 'Inactive display is not black'
                    assert images[1].getextrema() != ((0, 0), (0, 0), (0, 0)), 'Selected display is blank'
                return images

            try:
                with (folder / 'Qemu.log').open('w', encoding='utf-8') as log:
                    qemu = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT,
                        creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
                end = time.monotonic() + 20
                while qmp is None:
                    assert qemu.poll() is None, (folder / 'Qemu.log').read_text()
                    try:
                        qmp = Qmp(port)
                    except OSError:
                        assert time.monotonic() < end
                        time.sleep(.1)
                wait('XFE_INDEPENDENT_DESKTOP_FIRST_FRAME', seconds=120)
                wait('XFE_WORKSPACE|Mode|USB|Persistent|1|')
                assert 'XFE_DISPLAY_OUTPUTS|Count|' + ('1' if boot_number == 3 else '2') + '|' in text()
                if boot_number == 1:
                    wait_state(Mode='Duplicate', Outputs=2, Pending=0)
                    captures('Initial-Duplicate', 'same')
                    key('meta_l-p'); wait('XFE_DESKTOP_ACTIVE|Settings')
                    captures('Settings', 'same')
                    key('i'); wait_state(Identify=1); captures('Identify')
                    wait_state(Identify=0); captures('Identify-Removed', 'same')
                    _, (x, y, w, h) = window_geometry(text(), 'Settings')
                    controls_y = y + 100 + min(220, max(110, h-385)) + 12
                    click(x + w - 150, controls_y + 16)
                    click(x + w - 150, controls_y + 34 + 34 + 16)
                    click(x + 90, y + h - 57)
                    wait_state(Mode='Extend', Width=2560, Pending=1)
                    captures('Extend-Preview')
                    key('k'); wait_state(Pending=0)

                    # Drag the actual monitor tile across its neighbor. Preview
                    # changes must reach separate physical scanouts, then Escape reverts.
                    area_w, area_h = w - 48, min(220, max(110, h-385))
                    scale = min((area_w-36)/2560, (area_h-30)/800)
                    tile_x = int(x+24+(area_w-2560*scale)/2+1920*scale)
                    tile_y = int(y+100+area_h/2)
                    move(tile_x, tile_y); button(True); move(int(tile_x-2560*scale), tile_y); button(False)
                    key('ret'); wait_state(Pending=1)
                    captures('Arranged-Left')
                    key('esc'); wait_state(Pending=0)
                    captures('Arrangement-Reverted')

                    # A fresh file window moves across the real monitor edge.
                    key('alt-f4'); key('meta_l-e'); time.sleep(.4)
                    window_id, (fx, fy, fw, fh) = window_geometry(text(), 'Files')
                    move(fx+70, fy+16); button(True); move(2140, 100); button(False)
                    key('meta_l-up'); time.sleep(.4)
                    _, geometry = window_geometry(text(), 'Files')
                    assert list(geometry) == [1280, 0, 1280, 800], geometry
                    captures('Window-On-Second')
                    boot['WindowMovedAndMaximizedOnSecond'] = True

                    key('meta_l-p'); key('2'); key('p'); key('ret')
                    wait_state(Primary=2, Pending=1); key('k'); wait_state(Pending=0)
                    captures('Second-Main')
                    key('m'); key('ret'); wait_state(Mode='SelectedOnly', Primary=2, Pending=1)
                    captures('Only-Second', 'first-black')
                    wait_state(Mode='Extend', Pending=0)  # real 15-second timer, no host rollback
                    captures('Timeout-Restored')
                    boot['AutomaticRevertPassed'] = True
                    key('m'); key('m'); key('ret'); wait_state(Mode='Duplicate', Pending=1)
                    key('k'); wait_state(Pending=0); captures('Duplicate-Again', 'same')
                    key('m'); key('ret'); wait_state(Mode='Extend', Pending=1)
                    key('k'); wait_state(Pending=0, Primary=2)
                    captures('Saved-Extended')
                elif boot_number == 2:
                    wait_state(Mode='Extend', Outputs=2, Width=2560, Primary=2, Pending=0)
                    captures('ColdBoot-Restored')
                    boot['SavedLayoutRestored'] = True
                else:
                    wait_state(Mode='Duplicate', Outputs=1, Width=1280, Primary=1, Pending=0)
                    captures('Missing-Display-Safe-Fallback', 'single')
                    boot['MissingDisplaySafeFallback'] = True
                boot['Passed'] = True
                qmp.call('quit'); qemu.wait(timeout=10)
                boot['QemuExitCode'] = qemu.returncode; assert qemu.returncode == 0
            finally:
                if qmp is not None:
                    qmp.connection.close()
                if qemu is not None and qemu.poll() is None:
                    qemu.terminate(); qemu.wait(timeout=10)
                if (folder / 'Serial.log').exists():
                    result['EvidenceFiles'].append(ref(folder / 'Serial.log'))
        original, actual = image.read_bytes(), disk.read_bytes()
        offset, size = manifest['WorkspaceFirstLba']*512, manifest['WorkspaceBytes']
        assert original[:offset+512] == actual[:offset+512] and original[offset+size:] == actual[offset+size:]
        assert original[offset+512:offset+size] != actual[offset+512:offset+size]
        assert digest(image) == result['ImageSha256']
        assert all(digest(ROOT / path) == value for path, value in sources.items())
        result.update(Passed=True, SourcesUnchanged=True, SourceImageUnchanged=True, OnlyWorkspaceSectorsChanged=True,
            DuplicatePixelEqualityPassed=True, IndependentExtendPassed=True, DistinctPhysicalIdentifyPassed=True,
            SelectedOnlyPassed=True, AutomaticRevertPassed=True, WindowMovedAndMaximizedOnSecond=True,
            UsbColdBootLayoutRestorePassed=True, MissingDisplaySafeFallbackPassed=True)
    except Exception as error:
        result['Failure'] = str(error)
    output = proof / 'Result.json'
    output.write_text(json.dumps(result, indent=2), encoding='utf-8')
    print(json.dumps({'Passed': result['Passed'], 'Result': str(output), 'Failure': result.get('Failure')}, indent=2), flush=True)
    return result, output


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--image', type=Path, required=True)
    args = parser.parse_args()
    passed, _ = verify_displays(args.image)
    raise SystemExit(0 if passed['Passed'] else 1)