Guard Centurion feature lookups against HID++ 2.0 enum collisions

IntEnum members with the same int value hash equal and compare equal,
so a dict lookup for SupportedFeature.DEVICE_NAME (0x0005) finds an
entry stored as CenturionCoreFeature.MULTI_HOST_CONTROL (same 0x0005).
The index is right for the Centurion feature but wrong for the HID++
2.0 feature the caller intended.

Concrete impact: `solaar show` on wired G522 called get_kind()
-> feature_request(DEVICE_NAME, 0x20), which resolved to
MULTI_HOST_CONTROL.function_2 via the collision, and the device
returned OUT_OF_RANGE -> FeatureCallError crashed `solaar show`.

Fix: in Device.feature_request, after resolving the index, compare the
type of the stored inverse entry against the type of the requested
feature. Mismatched types mean the device actually has the Centurion
variant, not the HID++ 2.0 feature — return None instead of issuing a
mis-targeted request.
This commit is contained in:
Ken Sanislo 2026-04-16 23:36:47 -07:00
parent e7ee34f132
commit f469e86b9c
1 changed files with 12 additions and 0 deletions

View File

@ -647,6 +647,18 @@ class Device:
# Ensure sub-device features are discovered before routing decision
if self.features is not None:
self.features._check()
# Guard against Centurion/HID++ 2.0 feature ID collisions. IntEnum
# members with the same int value hash equal, so a dict lookup for
# SupportedFeature.DEVICE_NAME (0x0005) succeeds even when the
# device actually has CenturionCoreFeature.MULTI_HOST_CONTROL at
# that slot. If the type of the stored enum differs from what the
# caller asked for, treat the feature as unsupported.
if self.features is not None:
idx = self.features.get(feature)
if idx is not None:
stored = self.features.inverse.get(idx)
if stored is not None and type(stored) is not type(feature):
return None
if feature in getattr(self, "_centurion_sub_features", ()):
sub_idx = self.features.get(feature)
if sub_idx is not None: