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 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 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 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); } }