use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use uuid::{uuid, Uuid};
use super::*;
use crate::error::{self, Error, ErrorKind};
const WEBUSB_GUID: Uuid = uuid!("{3408b638-09a9-47a0-8bfd-a0768815b665}");
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
#[allow(missing_docs)]
#[non_exhaustive]
#[serde(rename_all = "kebab-case")]
pub enum BosType {
WirelessUsb = 0x01,
Usb2Extension = 0x02,
SuperSpeed = 0x03,
ContainerId = 0x04,
PlatformCapability = 0x05,
SuperSpeedPlus = 0x0a,
Billboard = 0x0d,
BillboardAltMode = 0x0f,
ConfigurationSummary = 0x10,
Unknown(u8),
}
impl From<u8> for BosType {
fn from(value: u8) -> Self {
match value {
0x01 => BosType::WirelessUsb,
0x02 => BosType::Usb2Extension,
0x03 => BosType::SuperSpeed,
0x04 => BosType::ContainerId,
0x05 => BosType::PlatformCapability,
0x0a => BosType::SuperSpeedPlus,
0x0d => BosType::Billboard,
0x0f => BosType::BillboardAltMode,
0x10 => BosType::ConfigurationSummary,
_ => BosType::Unknown(value),
}
}
}
impl From<BosType> for u8 {
fn from(value: BosType) -> Self {
match value {
BosType::WirelessUsb => 0x01,
BosType::Usb2Extension => 0x02,
BosType::SuperSpeed => 0x03,
BosType::ContainerId => 0x04,
BosType::PlatformCapability => 0x05,
BosType::SuperSpeedPlus => 0x0a,
BosType::Billboard => 0x0d,
BosType::BillboardAltMode => 0x0f,
BosType::ConfigurationSummary => 0x10,
BosType::Unknown(v) => v,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
#[serde(rename_all = "kebab-case")]
pub enum BosCapability {
Generic(GenericCapability),
Usb2Extension(ExtensionCapability),
SuperSpeed(SuperSpeedCapability),
SuperSpeedPlus(SuperSpeedPlusCapability),
Billboard(BillboardCapability),
BillboardAltMode(BillboardAltModeCapability),
ConfigurationSummary(ConfigurationSummaryCapability),
ContainerId(ContainerIdCapability),
Platform(PlatformDeviceCompatibility),
WebUsbPlatform(WebUsbPlatformCapability),
}
impl TryFrom<&[u8]> for BosCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 3 {
return Err(Error::new_descriptor_len("BosCapability", 3, value.len()));
}
match value[2].into() {
BosType::Unknown(_) => Err(Error::new(
ErrorKind::InvalidArg,
"BOS capability descriptor has unknown capability type",
)),
BosType::Usb2Extension => Ok(BosCapability::Usb2Extension(
ExtensionCapability::try_from(value)?,
)),
BosType::SuperSpeed => Ok(BosCapability::SuperSpeed(SuperSpeedCapability::try_from(
value,
)?)),
BosType::SuperSpeedPlus => Ok(BosCapability::SuperSpeedPlus(
SuperSpeedPlusCapability::try_from(value)?,
)),
BosType::Billboard => Ok(BosCapability::Billboard(BillboardCapability::try_from(
value,
)?)),
BosType::BillboardAltMode => Ok(BosCapability::BillboardAltMode(
BillboardAltModeCapability::try_from(value)?,
)),
BosType::ConfigurationSummary => Ok(BosCapability::ConfigurationSummary(
ConfigurationSummaryCapability::try_from(value)?,
)),
BosType::ContainerId => Ok(BosCapability::ContainerId(
ContainerIdCapability::try_from(value)?,
)),
BosType::PlatformCapability => {
let pdc = PlatformDeviceCompatibility::try_from(value)?;
if pdc.guid == WEBUSB_GUID {
Ok(BosCapability::WebUsbPlatform(
WebUsbPlatformCapability::try_from(value)?,
))
} else {
Ok(BosCapability::Platform(pdc))
}
}
_ => Ok(BosCapability::Generic(GenericCapability::try_from(value)?)),
}
}
}
impl From<BosCapability> for Vec<u8> {
fn from(bcd: BosCapability) -> Self {
match bcd {
BosCapability::Generic(gcd) => Vec::<u8>::from(gcd),
BosCapability::Usb2Extension(ebd) => Vec::<u8>::from(ebd),
BosCapability::SuperSpeed(ssc) => Vec::<u8>::from(ssc),
BosCapability::SuperSpeedPlus(sspc) => Vec::<u8>::from(sspc),
BosCapability::Billboard(bc) => Vec::<u8>::from(bc),
BosCapability::BillboardAltMode(bac) => Vec::<u8>::from(bac),
BosCapability::ConfigurationSummary(ucs) => Vec::<u8>::from(ucs),
BosCapability::ContainerId(cic) => Vec::<u8>::from(cic),
BosCapability::Platform(pdc) => Vec::<u8>::from(pdc),
BosCapability::WebUsbPlatform(wpc) => Vec::<u8>::from(wpc),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct BinaryObjectStoreDescriptor {
pub length: u8,
pub descriptor_type: u8,
pub total_length: u16,
pub num_device_capabilities: u8,
pub capabilities: Vec<BosCapability>,
}
impl TryFrom<&[u8]> for BinaryObjectStoreDescriptor {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 5 {
return Err(Error::new_descriptor_len(
"BinaryObjectStoreDescriptor",
5,
value.len(),
));
}
let length = value[0];
let descriptor_type = value[1];
let total_length = u16::from_le_bytes([value[2], value[3]]);
let num_device_capabilities = value[4];
if value.len() < total_length as usize {
return Err(Error::new_descriptor_len(
"BinaryObjectStoreDescriptor total_length",
total_length as usize,
value.len(),
));
}
if total_length <= 5 && value[4] > 0 {
return Err(Error::new(
ErrorKind::InvalidDescriptor,
"Binary Object Store descriptor reported num_device_capabilities but no data",
));
}
let mut capabilities = Vec::new();
let mut offset = 5;
while offset < total_length as usize {
let cd_len = value[offset] as usize;
if value.len() < offset + cd_len {
log::warn!("BOS capability has invalid length, breaking");
break;
}
match BosCapability::try_from(&value[offset..offset + cd_len]) {
Ok(c) => capabilities.push(c),
Err(e) => log::warn!("Failed to parse BOS capability: {e:?}"),
}
offset += cd_len;
}
Ok(BinaryObjectStoreDescriptor {
length,
descriptor_type,
total_length,
num_device_capabilities,
capabilities,
})
}
}
impl From<BinaryObjectStoreDescriptor> for Vec<u8> {
fn from(bosd: BinaryObjectStoreDescriptor) -> Self {
let mut ret = Vec::new();
ret.push(bosd.length);
ret.push(bosd.descriptor_type);
ret.extend(bosd.total_length.to_le_bytes());
ret.push(bosd.num_device_capabilities);
for cap in bosd.capabilities {
ret.extend(Vec::<u8>::from(cap));
}
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct GenericCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub data: Vec<u8>,
}
impl TryFrom<&[u8]> for GenericCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 3 {
return Err(Error::new_descriptor_len(
"GenericCapability",
3,
value.len(),
));
}
Ok(GenericCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
data: value[3..].to_vec(),
})
}
}
impl From<GenericCapability> for Vec<u8> {
fn from(gbd: GenericCapability) -> Self {
let mut ret = Vec::new();
ret.push(gbd.length);
ret.push(gbd.descriptor_type);
ret.push(u8::from(gbd.capability_type));
ret.extend(gbd.data);
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct PlatformDeviceCompatibility {
pub length: u8,
pub descriptor_type: u8,
pub compatibility_type: u8,
pub reserved: u8,
pub guid: Uuid,
pub data: Vec<u8>,
}
impl TryFrom<&[u8]> for PlatformDeviceCompatibility {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 20 {
return Err(Error::new_descriptor_len(
"PlatformDeviceCompatibility",
20,
value.len(),
));
}
Ok(PlatformDeviceCompatibility {
length: value[0],
descriptor_type: value[1],
compatibility_type: value[2],
reserved: value[3],
guid: Uuid::from_slice_le(&value[4..20]).map_err(|_| {
Error::new(
ErrorKind::InvalidDescriptor,
"Platform Device Compatibility descriptor has invalid GUID",
)
})?,
data: value[20..].to_vec(),
})
}
}
impl From<PlatformDeviceCompatibility> for Vec<u8> {
fn from(pdc: PlatformDeviceCompatibility) -> Self {
let mut ret = vec![
pdc.length,
pdc.descriptor_type,
pdc.compatibility_type,
pdc.reserved,
];
ret.extend(pdc.guid.to_bytes_le());
ret.extend(pdc.data);
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct WebUsbPlatformCapability {
pub platform: PlatformDeviceCompatibility,
pub version: Version,
pub vendor_code: u8,
pub landing_page_index: u8,
pub url: Option<String>,
}
impl TryFrom<&[u8]> for WebUsbPlatformCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 24 {
return Err(Error::new_descriptor_len(
"WebUsbPlatformCapability",
24,
value.len(),
));
}
let platform = PlatformDeviceCompatibility::try_from(value)?;
Ok(WebUsbPlatformCapability {
platform,
version: Version::from_bcd(u16::from_le_bytes([value[20], value[21]])),
vendor_code: value[22],
landing_page_index: value[23],
url: None,
})
}
}
impl From<WebUsbPlatformCapability> for Vec<u8> {
fn from(wpc: WebUsbPlatformCapability) -> Self {
wpc.platform.into()
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct ExtensionCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub attributes: u32,
}
impl TryFrom<&[u8]> for ExtensionCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 7 {
return Err(Error::new_descriptor_len(
"ExtensionCapability",
7,
value.len(),
));
}
Ok(ExtensionCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
attributes: u32::from_le_bytes([value[3], value[4], value[5], value[6]]),
})
}
}
impl From<ExtensionCapability> for Vec<u8> {
fn from(ebd: ExtensionCapability) -> Self {
let mut ret = Vec::new();
ret.push(ebd.length);
ret.push(ebd.descriptor_type);
ret.push(u8::from(ebd.capability_type));
ret.extend(ebd.attributes.to_le_bytes());
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct SuperSpeedCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub attributes: u8,
pub speed_supported: u16,
pub functionality_supported: u8,
pub u1_device_exit_latency: u8,
pub u2_device_exit_latency: u16,
}
impl TryFrom<&[u8]> for SuperSpeedCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 10 {
return Err(Error::new_descriptor_len(
"SuperSpeedCapability",
10,
value.len(),
));
}
Ok(SuperSpeedCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
attributes: value[3],
speed_supported: u16::from_le_bytes([value[4], value[5]]),
functionality_supported: value[6],
u1_device_exit_latency: value[7],
u2_device_exit_latency: u16::from_le_bytes([value[8], value[9]]),
})
}
}
impl From<SuperSpeedCapability> for Vec<u8> {
fn from(ssc: SuperSpeedCapability) -> Self {
let mut ret = vec![
ssc.length,
ssc.descriptor_type,
u8::from(ssc.capability_type),
ssc.attributes,
];
ret.extend(ssc.speed_supported.to_le_bytes());
ret.push(ssc.functionality_supported);
ret.push(ssc.u1_device_exit_latency);
ret.extend(ssc.u2_device_exit_latency.to_le_bytes());
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct SuperSpeedPlusCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub attributes: u32,
pub functionality_supported: u16,
pub sublink_attributes: Vec<u32>,
}
impl TryFrom<&[u8]> for SuperSpeedPlusCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 12 {
return Err(Error::new_descriptor_len(
"SuperSpeedPlusCapability",
12,
value.len(),
));
}
let sublink_speed_attr_count = (value[4] & 0x1f) as usize + 1;
let mut sublink_attributes = Vec::with_capacity(sublink_speed_attr_count);
if value.len() < 12 + sublink_speed_attr_count * 4 {
return Err(Error::new_descriptor_len(
"SuperSpeedPlusCapability sublink_attributes",
12 + sublink_speed_attr_count * 4,
value.len(),
));
}
for chunk in value[12..12 + sublink_speed_attr_count].chunks_exact(4) {
sublink_attributes.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
Ok(SuperSpeedPlusCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
attributes: u32::from_le_bytes([value[4], value[5], value[6], value[7]]),
functionality_supported: u16::from_le_bytes([value[8], value[9]]),
sublink_attributes,
})
}
}
impl From<SuperSpeedPlusCapability> for Vec<u8> {
fn from(sspc: SuperSpeedPlusCapability) -> Self {
let mut ret = Vec::new();
ret.push(sspc.length);
ret.push(sspc.descriptor_type);
ret.push(u8::from(sspc.capability_type));
ret.extend(sspc.attributes.to_le_bytes());
ret.extend(sspc.functionality_supported.to_le_bytes());
for attr in sspc.sublink_attributes {
ret.extend(attr.to_le_bytes());
}
ret
}
}
impl SuperSpeedPlusCapability {
pub fn sublink_speed_attribute_count(&self) -> usize {
self.attributes as usize & (0x1f + 1)
}
pub fn sublink_speed_id_count(&self) -> usize {
((self.attributes as usize) >> 5) & (0xf + 1)
}
pub fn functional_speed_attribute_id(&self) -> usize {
(self.functionality_supported as usize) & 0x0f
}
pub fn functional_rx_lanes(&self) -> usize {
((self.functionality_supported as usize) >> 8) & 0x0f
}
pub fn functional_tx_lanes(&self) -> usize {
((self.functionality_supported as usize) >> 12) & 0x0f
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct BillboardCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub additional_info_url_index: u8,
pub additional_info_url: Option<String>,
pub number_of_alternate_modes: u8,
pub preferred_alternate_mode: u8,
pub vconn_power: u16,
pub configured: [u8; 32],
pub version: Version,
pub additional_failure_info: u8,
pub reserved: u8,
pub alternate_modes: Vec<AlternateMode>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct AlternateMode {
pub svid: u16,
pub alternate_mode: u8,
pub alternate_mode_string_index: u8,
pub alternate_mode_string: Option<String>,
}
impl TryFrom<&[u8]> for BillboardCapability {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() < 48 {
return Err(Error::new_descriptor_len(
"BillboardCapability",
48,
value.len(),
));
}
let number_of_alternate_modes = value[4];
if number_of_alternate_modes > 0x34 {
return Err(Error::new(
ErrorKind::InvalidDescriptor,
"Invalid number of alternate modes in Billboard Capability descriptor",
));
}
if value.len() < (44 + number_of_alternate_modes as usize * 4) {
return Err(Error::new(
ErrorKind::InvalidDescriptor,
"Billboard Capability descriptor too short for alternate modes",
));
}
let mut alternate_modes = Vec::with_capacity(number_of_alternate_modes as usize);
let mut i = 44;
for _ in 0..number_of_alternate_modes {
alternate_modes.push(AlternateMode {
svid: u16::from_le_bytes([value[i], value[i + 1]]),
alternate_mode: value[i + 2],
alternate_mode_string_index: value[i + 3],
alternate_mode_string: None,
});
i += 4;
}
let version = if value[41] == 0 {
Version::from_bcd(u16::from_le_bytes([value[40], 1]))
} else {
Version::from_bcd(u16::from_le_bytes([value[40], value[41]]))
};
Ok(BillboardCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
additional_info_url_index: value[3],
additional_info_url: None,
number_of_alternate_modes,
preferred_alternate_mode: value[5],
vconn_power: u16::from_le_bytes([value[6], value[7]]),
configured: value[8..40].try_into().expect("bmConfigured slice error"),
version,
additional_failure_info: value[42],
reserved: value[43],
alternate_modes,
})
}
}
impl From<BillboardCapability> for Vec<u8> {
fn from(bc: BillboardCapability) -> Self {
let mut ret = vec![
bc.length,
bc.descriptor_type,
u8::from(bc.capability_type),
bc.additional_info_url_index,
bc.number_of_alternate_modes,
bc.preferred_alternate_mode,
];
ret.extend_from_slice(&bc.vconn_power.to_le_bytes());
ret.extend_from_slice(&bc.configured);
ret.extend_from_slice(&(u16::from(bc.version)).to_le_bytes());
ret.push(bc.additional_failure_info);
ret.push(bc.reserved);
for alt_mode in bc.alternate_modes {
ret.extend_from_slice(&alt_mode.svid.to_le_bytes());
ret.push(alt_mode.alternate_mode);
ret.push(alt_mode.alternate_mode_string_index);
}
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct BillboardAltModeCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub index: u8,
pub alternate_mode_vdo: u32,
}
impl TryFrom<&[u8]> for BillboardAltModeCapability {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != 8 {
return Err(Error::new_descriptor_len(
"BillboardAltModeCapability",
8,
value.len(),
));
}
Ok(BillboardAltModeCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
index: value[3],
alternate_mode_vdo: u32::from_le_bytes([value[4], value[5], value[6], value[7]]),
})
}
}
impl From<BillboardAltModeCapability> for Vec<u8> {
fn from(bac: BillboardAltModeCapability) -> Self {
let mut ret = vec![
bac.length,
bac.descriptor_type,
u8::from(bac.capability_type),
bac.index,
];
ret.extend_from_slice(&bac.alternate_mode_vdo.to_le_bytes());
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct ContainerIdCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub reserved: u8,
pub container_id: Uuid,
}
impl TryFrom<&[u8]> for ContainerIdCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 20 {
return Err(Error::new_descriptor_len(
"ContainerIdCapability",
20,
value.len(),
));
}
Ok(ContainerIdCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
reserved: value[3],
container_id: Uuid::from_slice_le(&value[4..20]).map_err(|_| {
Error::new(
ErrorKind::InvalidDescriptor,
"Container ID BOS descriptor has invalid GUID",
)
})?,
})
}
}
impl From<ContainerIdCapability> for Vec<u8> {
fn from(cic: ContainerIdCapability) -> Self {
let mut ret = vec![
cic.length,
cic.descriptor_type,
u8::from(cic.capability_type),
cic.reserved,
];
ret.extend(cic.container_id.to_bytes_le());
ret
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct ConfigurationSummaryCapability {
pub length: u8,
pub descriptor_type: u8,
pub capability_type: BosType,
pub version: Version,
pub class: u8,
pub sub_class: u8,
pub protocol: u8,
pub configuration_count: u8,
pub configurations: u8,
pub configured: Vec<u8>,
}
impl TryFrom<&[u8]> for ConfigurationSummaryCapability {
type Error = Error;
fn try_from(value: &[u8]) -> error::Result<Self> {
if value.len() < 10 {
return Err(Error::new_descriptor_len(
"ConfigurationSummaryCapability",
10,
value.len(),
));
}
let configured_count = value[7];
if value.len() < 10 + configured_count as usize {
return Err(Error::new_descriptor_len(
"ConfigurationSummaryCapability configured_count",
10 + configured_count as usize,
value.len(),
));
}
Ok(ConfigurationSummaryCapability {
length: value[0],
descriptor_type: value[1],
capability_type: value[2].into(),
version: Version::from_bcd(u16::from_le_bytes([value[3], value[4]])),
class: value[5],
sub_class: value[6],
protocol: value[7],
configuration_count: configured_count,
configurations: value[8],
configured: value[9..].to_vec(),
})
}
}
impl From<ConfigurationSummaryCapability> for Vec<u8> {
fn from(ucs: ConfigurationSummaryCapability) -> Self {
let mut ret = Vec::new();
ret.push(ucs.length);
ret.push(ucs.descriptor_type);
ret.push(u8::from(ucs.capability_type));
ret.extend_from_slice(&u16::from(ucs.version).to_le_bytes());
ret.push(ucs.class);
ret.push(ucs.sub_class);
ret.push(ucs.protocol);
ret.push(ucs.configuration_count);
ret.push(ucs.configurations);
ret.extend(ucs.configured);
ret
}
}