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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Desktop;
using Xunit;

namespace XFESystem.Core.Tests;

public sealed class DisplayTimingTests
{
    // Actual QEMU VGA MMIO BAR2 EDID captured via QMP pmemsave, not fabricated DTDs.
    // Generator: https://github.com/qemu/qemu/blob/v11.1.0/hw/display/edid-generate.c
    // Captures: build/CSharpBoot/DisplayChain-{1791939973026737100,1791940007931634500}/QemuEdid.bin
    private const string Qemu240Hex = "00FFFFFFFFFFFF0049143412000000002A180104A561367806EE91A3544C99260F5054210800E1C0D1C0D100A940B300950081808140000000F7000A004082002820000000000000000000FD00327D1EA0FF010A202020202020000000FC0051454D55204D6F6E69746F720A0000001000000000000000000000000000000278020309004460591F6100000010000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000000000000000000000000000000157013170300030014353E0488FF0E3F05BF0372006F084A000900090065000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090";
    private const string Qemu160Hex = "00FFFFFFFFFFFF0049143412000000002A180104A561367806EE91A3544C99260F5054210800E1C0D1C0D100A940B300950081808140000000F7000A004082002820000000000000000000FD00327D1EA0FF010A202020202020000000FC0051454D55204D6F6E69746F720A0000001000000000000000000000000000000278020309004460591F610000001000000000000000000000000000000000001000000000000000000000000000000000001000000000000000000000000000000000001000000000000000000000000000000000001000000000000000000000000000000000001000000000000000000000000000000000000000000000000015701317030003001423D40288FF0E3F05BF0372006F084A0009000900E3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090";

    [Theory]
    [InlineData(true, 240000)]
    [InlineData(false, 160000)]
    public void ActualQemu4KTimingComesFromDisplayIdExtension(bool high, int expectedMilliHz)
    {
        var result = DisplayTiming.Read(Convert.FromHexString(high ? Qemu240Hex : Qemu160Hex), 3840, 2160);
        Assert.True(result.IsValid); Assert.True(result.IsComplete);
        Assert.Equal(2, result.ValidatedExtensionBlocks);
        Assert.Equal(expectedMilliHz, result.AdvertisedRefreshMilliHz);
        Assert.Contains("DisplayID 1.3 Type I", result.Source);
        Assert.Equal(1, result.MatchedTimingCount);
        Assert.Equal(970, result.PhysicalWidthMillimeters);
        Assert.Equal(540, result.PhysicalHeightMillimeters);
    }

    [Fact]
    public void QemuHighRateDoesNotMasqueradeAsAnotherFramebufferMode()
    {
        var result = DisplayTiming.Read(Convert.FromHexString(Qemu240Hex), 4096, 2160);
        Assert.True(result.IsValid); Assert.False(result.HasAdvertisedTiming);
        Assert.Equal(0, result.AdvertisedRefreshMilliHz); Assert.Equal(0, result.MatchedTimingCount);
    }

    [Fact]
    public void MissingQemuExtensionDoesNotInvent240HzOrDiscardValidBaseMetadata()
    {
        var result = DisplayTiming.Read(Convert.FromHexString(Qemu240Hex).AsSpan(0, 128), 3840, 2160);
        Assert.True(result.IsValid); Assert.False(result.IsComplete);
        Assert.Equal(0, result.AdvertisedRefreshMilliHz); Assert.Equal(970, result.PhysicalWidthMillimeters);
        Assert.Contains("missing", result.FailureReason);
    }

    [Fact]
    public void HighestExactResolutionAcrossBaseAndCtaDtdIsSelected()
    {
        byte[] bytes = WithCta();
        SetDtd(bytes.AsSpan(54, 18), 1920, 1080, 280, 45, 14850, 600, 340);
        SetDtd(bytes.AsSpan(132, 18), 1920, 1080, 280, 45, 35640);
        Checksum(bytes.AsSpan(0, 128)); Checksum(bytes.AsSpan(128, 128));
        var result = DisplayTiming.Read(bytes, 1920, 1080);
        Assert.True(result.IsComplete); Assert.Equal(144000, result.AdvertisedRefreshMilliHz);
        Assert.Equal(2, result.MatchedTimingCount); Assert.Contains("CTA DTD", result.Source);
        Assert.Equal(600, result.PhysicalWidthMillimeters); Assert.Equal(340, result.PhysicalHeightMillimeters);
    }

    [Fact]
    public void CorruptExtensionCannotContributeTimingAndBaseRemainsUsable()
    {
        byte[] bytes = WithCta();
        SetDtd(bytes.AsSpan(54, 18), 1920, 1080, 280, 45, 14850);
        SetDtd(bytes.AsSpan(132, 18), 1920, 1080, 280, 45, 35640);
        Checksum(bytes.AsSpan(0, 128)); Checksum(bytes.AsSpan(128, 128)); bytes[255] ^= 1;
        var result = DisplayTiming.Read(bytes, 1920, 1080);
        Assert.True(result.IsValid); Assert.False(result.IsComplete);
        Assert.Equal(60000, result.AdvertisedRefreshMilliHz); Assert.Equal(0, result.ValidatedExtensionBlocks);
    }

    [Fact]
    public void RangeMaximumAloneIsNeverAConcreteTiming()
    {
        byte[] bytes = Base(); bytes[57] = 0xFD; bytes[59] = 30; bytes[60] = 240; Checksum(bytes);
        var result = DisplayTiming.Read(bytes, 3840, 2160);
        Assert.True(result.IsValid); Assert.Equal(0, result.AdvertisedRefreshMilliHz);
    }

    [Fact]
    public void MissingExtensionsPreserveActualBaseDtd()
    {
        byte[] bytes = Base(2);
        SetDtd(bytes.AsSpan(54, 18), 1920, 1080, 280, 45, 14850); Checksum(bytes);
        var result = DisplayTiming.Read(bytes, 1920, 1080);
        Assert.True(result.IsValid); Assert.False(result.IsComplete);
        Assert.Equal(60000, result.AdvertisedRefreshMilliHz); Assert.Equal(2, result.DeclaredExtensionBlocks);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(127)]
    public void ShortInputIsRejectedWithoutReadingOutsideTheSpan(int length) =>
        Assert.False(DisplayTiming.Read(new byte[length], 3840, 2160).IsValid);

    [Theory]
    [InlineData(0, 4)]
    [InlineData(1, 2)]
    [InlineData(1, 5)]
    public void UnsupportedBaseVersionIsRejected(int major, int minor)
    {
        byte[] bytes = Base(); bytes[18] = (byte)major; bytes[19] = (byte)minor; Checksum(bytes);
        Assert.False(DisplayTiming.Read(bytes, 1920, 1080).IsValid);
    }

    [Fact]
    public void BadBaseChecksumRejectsAllExtensionInformation()
    {
        byte[] bytes = Convert.FromHexString(Qemu240Hex); bytes[20] ^= 1;
        Assert.False(DisplayTiming.Read(bytes, 3840, 2160).IsValid);
    }

    [Fact]
    public void BadDisplayIdInternalChecksumIsRejectedEvenWithValidOuterChecksum()
    {
        byte[] bytes = Convert.FromHexString(Qemu240Hex); bytes[264] ^= 1; Checksum(bytes.AsSpan(256, 128));
        var result = DisplayTiming.Read(bytes, 3840, 2160);
        Assert.True(result.IsValid); Assert.False(result.IsComplete); Assert.False(result.HasAdvertisedTiming);
    }

    [Fact]
    public void MalformedDisplayIdLengthCannotEscapeItsBlock()
    {
        byte[] bytes = Convert.FromHexString(Qemu240Hex); bytes[258] = 255; Checksum(bytes.AsSpan(256, 128));
        var result = DisplayTiming.Read(bytes, 3840, 2160);
        Assert.True(result.IsValid); Assert.False(result.IsComplete); Assert.False(result.HasAdvertisedTiming);
    }

    [Fact]
    public void MalformedTypeOnePayloadLengthIsRejectedBeforeAnyTimingIsRead()
    {
        byte[] bytes = Convert.FromHexString(Qemu240Hex); bytes[263] = 19; DisplayIdChecksum(bytes.AsSpan(256, 128));
        var result = DisplayTiming.Read(bytes, 3840, 2160);
        Assert.False(result.IsComplete); Assert.False(result.HasAdvertisedTiming);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(4)]
    public void UnsupportedCtaRevisionCannotContributeDtd(int revision)
    {
        byte[] bytes = WithCta(); bytes[129] = (byte)revision;
        SetDtd(bytes.AsSpan(132, 18), 1920, 1080, 280, 45, 14850); Checksum(bytes.AsSpan(128, 128));
        var result = DisplayTiming.Read(bytes, 1920, 1080);
        Assert.False(result.IsComplete); Assert.False(result.HasAdvertisedTiming);
    }

    [Fact]
    public void MalformedCtaDataBlockDoesNotTurnItsPayloadIntoDtd()
    {
        byte[] bytes = WithCta(); bytes[130] = 5; bytes[132] = 0x5F; Checksum(bytes.AsSpan(128, 128));
        Assert.False(DisplayTiming.Read(bytes, 1920, 1080).IsComplete);
    }

    [Fact]
    public void InterlacedTimingIsNotReportedAsProgressiveFramebufferRefresh()
    {
        byte[] bytes = Base(); SetDtd(bytes.AsSpan(54, 18), 1920, 1080, 280, 45, 14850);
        bytes[71] = 0x80; Checksum(bytes);
        Assert.False(DisplayTiming.Read(bytes, 1920, 1080).HasAdvertisedTiming);
    }

    [Fact]
    public void OversizedExtensionDeclarationIsBoundedAndNotReportedComplete()
    {
        var result = DisplayTiming.Read(Base(255), 1920, 1080);
        Assert.True(result.IsValid); Assert.False(result.IsComplete);
        Assert.Equal(0, result.ValidatedExtensionBlocks); Assert.Equal(255, result.DeclaredExtensionBlocks);
    }

    [Fact]
    public void EdidAspectEncodingIsNotMisreportedAsPhysicalDimensions()
    {
        byte[] bytes = Base(); bytes[21] = 178; bytes[22] = 0; Checksum(bytes);
        var result = DisplayTiming.Read(bytes, 1920, 1080);
        Assert.Equal(0, result.PhysicalWidthMillimeters); Assert.Equal(0, result.PhysicalHeightMillimeters);
    }

    private static byte[] Base(byte extensions = 0)
    {
        byte[] bytes = new byte[128];
        new byte[] { 0, 255, 255, 255, 255, 255, 255, 0 }.CopyTo(bytes, 0);
        bytes[18] = 1; bytes[19] = 4; bytes[21] = 60; bytes[22] = 34; bytes[126] = extensions;
        Checksum(bytes); return bytes;
    }

    private static byte[] WithCta()
    {
        byte[] bytes = Base(1); Array.Resize(ref bytes, 256);
        bytes[128] = 2; bytes[129] = 3; bytes[130] = 4; Checksum(bytes.AsSpan(128, 128)); return bytes;
    }

    private static void SetDtd(Span<byte> dtd, int width, int height, int horizontalBlank, int verticalBlank,
        int clockUnits, int physicalWidth = 0, int physicalHeight = 0)
    {
        dtd.Clear(); dtd[0] = (byte)clockUnits; dtd[1] = (byte)(clockUnits >> 8);
        dtd[2] = (byte)width; dtd[3] = (byte)horizontalBlank;
        dtd[4] = (byte)((width >> 8) << 4 | horizontalBlank >> 8);
        dtd[5] = (byte)height; dtd[6] = (byte)verticalBlank;
        dtd[7] = (byte)((height >> 8) << 4 | verticalBlank >> 8);
        dtd[12] = (byte)physicalWidth; dtd[13] = (byte)physicalHeight;
        dtd[14] = (byte)((physicalWidth >> 8) << 4 | physicalHeight >> 8);
    }

    private static void Checksum(Span<byte> block)
    {
        block[127] = 0; int sum = 0;
        for (int index = 0; index < 127; index++) sum += block[index];
        block[127] = (byte)(-sum & 255);
    }

    private static void DisplayIdChecksum(Span<byte> block)
    {
        int checksumIndex = 5 + block[2]; block[checksumIndex] = 0; int sum = 0;
        for (int index = 1; index < checksumIndex; index++) sum += block[index];
        block[checksumIndex] = (byte)(-sum & 255); Checksum(block);
    }
}