embassy-rs/embassy · error
called `capability` not between `bos` and `end_bos`.
Error message
called `capability` not between `bos` and `end_bos`.
What it means
`capability()` appends a BOS device-capability descriptor and increments the bNumDeviceCaps counter whose location was recorded by `bos()`. If `capability()` is invoked when no BOS container is open, the counter mark is None and the writer panics rather than produce a malformed BOS descriptor.
Solutions
- Call `capability(...)` only between `writer.bos()` and `writer.end_bos()`.
- Move capability writes into the BOS-descriptor path of your class implementation.
- Ensure `bos()`/`end_bos()` are called exactly once as an enclosing pair.
- Refactor custom classes so capability registration goes through the proper hook that opens the BOS writer.
Example fix
// before
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) {
w.capability(0x06, &cap_data)?; // outside BOS
}
// after
fn get_bos_descriptors(&self, w: &mut BosWriter) {
w.bos();
w.capability(0x06, &cap_data)?;
w.end_bos();
Ok(())
} Defensive patterns
Strategy: validation
Validate before calling
fn get_bos_descriptors(&self, w: &mut BosWriter) -> Result<(), DescriptorError> {
w.bos(); // opens the BOS container
w.capability(CAP_TYPE_WEBUSB, &CAP_DATA)?; // only valid here
w.end_bos();
Ok(())
} Prevention
- Call capability() strictly between bos() and end_bos()
- Put capability descriptors in the BOS callback, never in configuration/device callbacks
- Keep bos/end_bos paired exactly once per descriptor build
- Compare against the WebUSB/WinUSB embassy-usb examples for correct placement
When it happens
Trigger: Calling `DescriptorWriter::capability()` outside the span between `bos()` and `end_bos()` calls on the same writer.
Common situations: Adding USB capabilities (e.g. WebUSB, Microsoft OS 2.0) in the wrong descriptor callback; forgetting the `end_bos()`/`bos()` pair when restructuring custom descriptor code; calling capability during configuration-descriptor building instead of BOS building.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- you can only call `interface/interface_alt` after…
- you can only call `endpoint` after…
- AHB frequency is too low
- USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but…
- USB clock should be 48Mhz but is
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/c1f20888e189d3ca.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-usb/src/descriptor.rs:432
0x00, 0x00, // wTotalLength
0x00, // bNumDeviceCaps
],
&[],
);
self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]);
}
/// Writes capability descriptor to a BOS
///
/// # Arguments
///
/// * `capability_type` - Type of a capability
/// * `data` - Binary data of the descriptor
pub fn capability(&mut self, capability_type: u8, data: &[u8]) {
match self.num_caps_mark {
Some(mark) => self.writer.buf[mark] += 1,
None => panic!("called `capability` not between `bos` and `end_bos`."),
}
let mut start = self.writer.position;
let blen = data.len();
assert!(
(start + blen + 3) <= self.writer.buf.len() && (blen + 3) <= 255,
"Descriptor buffer full"
);
self.writer.buf[start] = (blen + 3) as u8;
self.writer.buf[start + 1] = descriptor_type::CAPABILITY;
self.writer.buf[start + 2] = capability_type;
start += 3;
self.writer.buf[start..start + blen].copy_from_slice(data);
self.writer.position = start + blen;
}View on GitHub (pinned to 463a07b963)