Skip to content

Commit 65eb3ac

Browse files
authored
Merge pull request #8 from Zaknin/phase/28a4-pimax-usb-enumeration-evidence
Phase 28A4: add Pimax USB enumeration diagnostics
2 parents c3c97ca + d64f90c commit 65eb3ac

5 files changed

Lines changed: 965 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
using System.Text.Json;
2+
using Xunit;
3+
4+
public sealed class PimaxUsbEnumerationTests
5+
{
6+
[Fact]
7+
public void EmptyInventorySerializesRequiredSchemaAndFields()
8+
{
9+
var snapshot = new PimaxUsbEnumerationSnapshotCollector(new FakeInventorySource([])).Collect();
10+
var json = JsonSerializer.Serialize(snapshot, PimaxUsbEnumerationJson.Options);
11+
12+
Assert.Equal(PimaxUsbEnumerationSchema.Version, snapshot.SchemaVersion);
13+
Assert.Equal(0, snapshot.InventorySummary.TotalDevices);
14+
Assert.Empty(snapshot.CandidateDevices);
15+
Assert.Contains("\"schemaVersion\":\"pimax-usb-enumeration-v1\"", json);
16+
Assert.Contains("\"inventorySummary\"", json);
17+
Assert.Contains("\"candidateDevices\"", json);
18+
Assert.Contains("\"fullInventory\"", json);
19+
Assert.Contains("\"warnings\"", json);
20+
Assert.Contains("\"errors\"", json);
21+
}
22+
23+
[Fact]
24+
public void IdentitySanitizerIsDeterministicAndDoesNotExposeRawSerial()
25+
{
26+
const string first = @"USB\VID_34A4&PID_0012\P30100P201382100320";
27+
const string second = @"USB\VID_34A4&PID_0012\DIFFERENT";
28+
29+
var firstHash = PnpIdentitySanitizer.StableHash(first);
30+
var repeatedHash = PnpIdentitySanitizer.StableHash(first.ToLowerInvariant());
31+
var secondHash = PnpIdentitySanitizer.StableHash(second);
32+
33+
Assert.Equal(firstHash, repeatedHash);
34+
Assert.NotEqual(firstHash, secondHash);
35+
Assert.DoesNotContain("P30100P201382100320", firstHash, StringComparison.OrdinalIgnoreCase);
36+
Assert.StartsWith("sha256:", firstHash);
37+
Assert.Null(PnpIdentitySanitizer.StableHashOrNull(null));
38+
}
39+
40+
[Fact]
41+
public void KnownCrystalVidPidSelectsCandidateWithoutRawIdentifierLeak()
42+
{
43+
var raw = Raw(instanceId: @"USB\VID_34A4&PID_0012\P30100P201382100320", deviceClass: "USB");
44+
var snapshot = new PimaxUsbEnumerationSnapshotCollector(new FakeInventorySource([raw])).Collect();
45+
var candidate = Assert.Single(snapshot.CandidateDevices);
46+
var json = JsonSerializer.Serialize(snapshot, PimaxUsbEnumerationJson.Options);
47+
48+
Assert.Contains("knownCrystalVidPid", candidate.CandidateReasons);
49+
Assert.Contains("usbDeviceWithRelevantInterfaceClass", candidate.CandidateReasons);
50+
Assert.Equal("34A4", candidate.Vid);
51+
Assert.Equal("0012", candidate.Pid);
52+
Assert.DoesNotContain("P30100P201382100320", json, StringComparison.OrdinalIgnoreCase);
53+
}
54+
55+
[Fact]
56+
public void CandidateSelectionUsesNameInterfaceAndProblemEvidence()
57+
{
58+
var raw = Raw(
59+
instanceId: @"HID\VID_9999&PID_8888\FAKE123",
60+
deviceClass: "HIDClass",
61+
friendlyName: "Pimax Controller Interface",
62+
problemCode: 28,
63+
status: "Problem");
64+
65+
var candidate = Assert.Single(new PimaxUsbEnumerationSnapshotCollector(new FakeInventorySource([raw])).Collect().CandidateDevices);
66+
67+
Assert.Contains("knownPimaxName", candidate.CandidateReasons);
68+
Assert.Contains("relatedHidInterface", candidate.CandidateReasons);
69+
Assert.Contains("problemUsbOrPnpDevice", candidate.CandidateReasons);
70+
Assert.Equal(28, candidate.ProblemCode);
71+
}
72+
73+
[Fact]
74+
public void SummaryCountsPresentNonPresentProblemDuplicateRows()
75+
{
76+
var devices = new[]
77+
{
78+
Raw(instanceId: @"USB\VID_34A4&PID_0012\ONE", deviceClass: "USB"),
79+
Raw(instanceId: @"USB\VID_34A4&PID_0012\ONE", deviceClass: "USB"),
80+
Raw(instanceId: @"USB\VID_34A4&PID_0012\TWO", deviceClass: "USB", present: false, phantom: true, connected: false, status: "NonPresent"),
81+
Raw(instanceId: @"USB\VID_34A4&PID_0012\THREE", deviceClass: "USB", problemCode: 10, status: "Problem")
82+
};
83+
84+
var summary = new PimaxUsbEnumerationSnapshotCollector(new FakeInventorySource(devices)).Collect().InventorySummary;
85+
86+
Assert.Equal(4, summary.TotalDevices);
87+
Assert.Equal(3, summary.PresentDevices);
88+
Assert.Equal(1, summary.NonPresentDevices);
89+
Assert.Equal(1, summary.ProblemDevices);
90+
Assert.Equal(4, summary.CountsByEnumerator["USB"]);
91+
Assert.Equal(4, summary.CountsByVidPid["VID_34A4&PID_0012"]);
92+
}
93+
94+
[Fact]
95+
public void ParentContainerAndLocationPathsAreSanitizedButStable()
96+
{
97+
var raw = Raw(
98+
instanceId: @"USB\VID_34A4&PID_0012\CHILD",
99+
parentInstanceId: @"USB\ROOT_HUB30\ROOTSERIAL",
100+
containerId: "{11111111-2222-3333-4444-555555555555}",
101+
locationPaths: [@"PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(3)"]);
102+
103+
var record = Assert.Single(new PimaxUsbEnumerationSnapshotCollector(new FakeInventorySource([raw])).Collect().FullInventory);
104+
105+
Assert.NotNull(record.ParentStableId);
106+
Assert.NotNull(record.ContainerStableId);
107+
Assert.Single(record.LocationPathHashes);
108+
Assert.DoesNotContain("ROOTSERIAL", record.ParentStableId, StringComparison.OrdinalIgnoreCase);
109+
Assert.DoesNotContain("PCIROOT", record.LocationPathHashes[0], StringComparison.OrdinalIgnoreCase);
110+
}
111+
112+
[Fact]
113+
public void WarningsAndErrorsFlowIntoSnapshot()
114+
{
115+
var snapshot = new PimaxUsbEnumerationSnapshotCollector(
116+
new FakeInventorySource([], ["warning"], ["error"]))
117+
.Collect();
118+
119+
Assert.Equal(["warning"], snapshot.Warnings);
120+
Assert.Equal(["error"], snapshot.Errors);
121+
}
122+
123+
[Fact]
124+
public void ExistingConnectivitySchemaConstantRemainsUnchanged()
125+
{
126+
Assert.Equal("pimax-connectivity-v1", PimaxConnectivitySchema.Version);
127+
Assert.Equal("pimax-usb-enumeration-v1", PimaxUsbEnumerationSchema.Version);
128+
}
129+
130+
private static PimaxUsbRawDeviceRecord Raw(
131+
string instanceId,
132+
string? parentInstanceId = null,
133+
string? containerId = null,
134+
string enumeratorName = "USB",
135+
bool present = true,
136+
bool connected = true,
137+
bool phantom = false,
138+
string? deviceClass = "USB",
139+
string? friendlyName = "Fake Pimax Device",
140+
string? deviceDescription = "Fake Pimax Device",
141+
string? manufacturer = "Pimax",
142+
string? status = "Started",
143+
int? problemCode = null,
144+
string[]? hardwareIds = null,
145+
string[]? compatibleIds = null,
146+
string? vid = "34A4",
147+
string? pid = "0012",
148+
string[]? locationPaths = null)
149+
=> new(
150+
instanceId,
151+
parentInstanceId,
152+
containerId,
153+
enumeratorName,
154+
present,
155+
connected,
156+
phantom,
157+
deviceClass,
158+
"{FAKE-CLASS-GUID}",
159+
friendlyName,
160+
deviceDescription,
161+
manufacturer,
162+
"fake-service",
163+
null,
164+
null,
165+
null,
166+
status,
167+
problemCode,
168+
problemCode is > 0 ? "Problem" : status,
169+
hardwareIds ?? [@"USB\VID_34A4&PID_0012"],
170+
compatibleIds ?? [],
171+
vid,
172+
pid,
173+
null,
174+
null,
175+
"Port_#0001.Hub_#0002",
176+
locationPaths ?? [],
177+
[],
178+
"FakeSource");
179+
180+
private sealed class FakeInventorySource(
181+
PimaxUsbRawDeviceRecord[] devices,
182+
string[]? warnings = null,
183+
string[]? errors = null) : IPimaxUsbDeviceInventorySource
184+
{
185+
public PimaxUsbInventoryResult Collect()
186+
=> new(devices, warnings ?? [], errors ?? []);
187+
}
188+
}

0 commit comments

Comments
 (0)