AprilNEA/OpenLogi · error

sanitized Unifying receiver identity is not four bytes

Error message

sanitized Unifying receiver identity is not four bytes

What it means

`derive_replay_route` requires the sanitized ReceiverSerialNumber replacement for a Unifying route to be exactly 4 bytes (Unifying receiver serials are 4-byte values rendered as uppercase hex). Any other length means the sanitized audit data cannot form a valid route and the error aborts.

Solutions

  1. Re-record the fixture so a correct 4-byte serial replacement is generated
  2. Fix the audit's ReceiverSerialNumber replacement to exactly 4 bytes
  3. Use the openlogi-fixture synthetic identity policy to regenerate identities rather than editing by hand

Example fix

// before
"receiver_serial_number": "A1B2C3D4E5"   // 5 bytes
// after
"receiver_serial_number": "A1B2C3D4"     // 4 bytes
Defensive patterns

Strategy: validation

Validate before calling

fn valid_unifying_serial(v: &[u8]) -> bool { v.len() == 4 }
assert!(valid_unifying_serial(&audit.receiver_serial_number), "sanitized serial must be 4 bytes");

Type guard

fn valid_unifying_serial(v: &[u8]) -> bool {
    v.len() == 4
}

Prevention

When it happens

Trigger: Replaying a Unifying-routed fixture whose audit ReceiverSerialNumber replacement is missing or not 4 bytes — from hand-edited audits, an incompatible sanitizer output, or a corrupted fixture.

Common situations: Manually editing sanitized fixture identities; mixing fixtures across sanitizer/identity-policy versions; a fixture recorded for a Bolt device but replayed through a Unifying route derivation.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13). Data as JSON: /api/errors/e79caba745ffdd1d. Report an issue: GitHub.

Appendix: source

Thrown at crates/openlogi-cli/src/cmd/fixture/record_case/replay.rs:130

) -> Result<DeviceRoute> {
    match selected_route {
        DeviceRoute::Bolt { slot, .. } => {
            let value = unique_replacement(audit, SanitizedIdentityKind::ReceiverUniqueId)?;
            if value.len() != 16 || !value.is_ascii() {
                bail!("sanitized Bolt receiver identity is not 16-byte ASCII");
            }
            let receiver_uid = std::str::from_utf8(value)
                .map_err(|_| anyhow::anyhow!("sanitized Bolt receiver identity is not ASCII"))?
                .to_string();
            Ok(DeviceRoute::Bolt {
                receiver_uid,
                slot: *slot,
            })
        }
        DeviceRoute::Unifying { slot, .. } => {
            let value = unique_replacement(audit, SanitizedIdentityKind::ReceiverSerialNumber)?;
            if value.len() != 4 {
                bail!("sanitized Unifying receiver identity is not four bytes");
            }
            Ok(DeviceRoute::Unifying {
                receiver_uid: uppercase_hex(value),
                slot: *slot,
            })
        }
        DeviceRoute::Direct {
            vendor_id,
            product_id,
        } => Ok(DeviceRoute::Direct {
            vendor_id: *vendor_id,
            product_id: *product_id,
        }),
        DeviceRoute::RawHid { .. } => {
            bail!("raw HID routes are outside HID++ fixture case capture")
        }
    }
}

View on GitHub (pinned to e846e6f4b4)