embassy-rs/embassy · error

if composite_with_iads is set, you must set device_class =…

Error message

if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01

What it means

When composite_with_iads is enabled, the USB-IF IAD ECN mandates magic device-level class codes: device_class=0xEF, device_sub_class=0x02, device_protocol=0x01. embassy-usb's Builder::new panics if composite_with_iads is set with any other device class triple, since the resulting descriptor would be non-compliant.

Solutions

  1. Set device_class: 0xEF, device_sub_class: 0x02, device_protocol: 0x01 whenever composite_with_iads is true.
  2. If you don't need IADs, set composite_with_iads to false and keep your per-interface classes.
  3. Centralize the config construction so the IAD flag and class codes are set together.

Example fix

// before
let config = Config { composite_with_iads: true, device_class: 0x00, device_sub_class: 0x00, device_protocol: 0x00, .. };
// after
let config = Config { composite_with_iads: true, device_class: 0xEF, device_sub_class: 0x02, device_protocol: 0x01, .. };
Defensive patterns

Strategy: validation

Validate before calling

fn check_composite(cfg: &Config) -> bool {
    !cfg.composite_with_iads
        || (cfg.device_class == 0xEF && cfg.device_sub_class == 0x02 && cfg.device_protocol == 0x01)
}
assert!(check_composite(&config));

Type guard

fn check_composite(cfg: &Config) -> bool {
    !cfg.composite_with_iads
        || (cfg.device_class == 0xEF && cfg.device_sub_class == 0x02 && cfg.device_protocol == 0x01)
}

Prevention

When it happens

Trigger: Calling Builder::new with Config { composite_with_iads: true, .. } while device_class/device_sub_class/device_protocol keep vendor-specific defaults (e.g. 0x00/0x00/0x00 or 0xFF/0x00/0x00).

Common situations: Enabling IAD-based composite mode (needed for CDC + other interfaces) but forgetting to update the three device descriptor fields; upgrading embassy-usb where this became a hard panic instead of silent misbehavior.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/795ca544ffb7d0e3. Report an issue: GitHub.

Appendix: source

Thrown at embassy-usb/src/builder.rs:197

impl<'d, D: Driver<'d>> Builder<'d, D> {
    /// Creates a builder for constructing a new [`UsbDevice`].
    ///
    /// `control_buf` is a buffer used for USB control request data. It should be sized
    /// large enough for the length of the largest control request (in or out)
    /// anticipated by any class added to the device.
    pub fn new(
        driver: D,
        config: Config<'d>,
        config_descriptor_buf: &'d mut [u8],
        bos_descriptor_buf: &'d mut [u8],
        msos_descriptor_buf: &'d mut [u8],
        control_buf: &'d mut [u8],
    ) -> Self {
        // Magic values specified in USB-IF ECN on IADs.
        if config.composite_with_iads
            && (config.device_class != 0xEF || config.device_sub_class != 0x02 || config.device_protocol != 0x01)
        {
            panic!(
                "if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01"
            );
        }

        assert!(
            config.max_power <= 500,
            "The maximum allowed value for `max_power` is 500mA"
        );

        match config.max_packet_size_0 {
            8 | 16 | 32 | 64 => {}
            _ => panic!("invalid max_packet_size_0, the allowed values are 8, 16, 32 or 64"),
        }

        let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf);
        let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf));

        config_descriptor.configuration(&config);

View on GitHub (pinned to 463a07b963)