pub mod usage_page {
pub const GENERIC_DESKTOP: u16 = 0x01;
pub const KEYBOARD: u16 = 0x07;
pub const LED: u16 = 0x08;
pub const BUTTON: u16 = 0x09;
pub const CONSUMER: u16 = 0x0C;
}
pub mod usage {
pub const POINTER: u16 = 0x01;
pub const MOUSE: u16 = 0x02;
pub const JOYSTICK: u16 = 0x04;
pub const GAMEPAD: u16 = 0x05;
pub const KEYBOARD: u16 = 0x06;
pub const X: u16 = 0x30;
pub const Y: u16 = 0x31;
pub const Z: u16 = 0x32;
pub const RX: u16 = 0x33;
pub const RY: u16 = 0x34;
pub const RZ: u16 = 0x35;
pub const SLIDER: u16 = 0x36;
pub const DIAL: u16 = 0x37;
pub const WHEEL: u16 = 0x38;
pub const HAT_SWITCH: u16 = 0x39;
pub const DPAD_UP: u16 = 0x90;
pub const DPAD_DOWN: u16 = 0x91;
pub const DPAD_RIGHT: u16 = 0x92;
pub const DPAD_LEFT: u16 = 0x93;
}
pub mod flags {
pub const CONSTANT: u8 = 1 << 0;
pub const VARIABLE: u8 = 1 << 1;
pub const RELATIVE: u8 = 1 << 2;
pub const WRAP: u8 = 1 << 3;
pub const NULL_STATE: u8 = 1 << 6;
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ReportField {
pub report_id: u8,
pub usage_page: u16,
pub usage_min: u16,
pub usage_max: u16,
pub bit_offset: u32,
pub bit_size: u8,
pub count: u16,
pub logical_min: i32,
pub logical_max: i32,
pub flags: u8,
}
impl ReportField {
pub fn is_constant(&self) -> bool {
self.flags & flags::CONSTANT != 0
}
pub fn is_variable(&self) -> bool {
self.flags & flags::VARIABLE != 0
}
pub fn is_relative(&self) -> bool {
self.flags & flags::RELATIVE != 0
}
pub fn is_signed(&self) -> bool {
self.logical_min < 0
}
pub fn index_of_usage(&self, usage: u16) -> Option<usize> {
if usage >= self.usage_min && usage <= self.usage_max {
Some((usage - self.usage_min) as usize)
} else {
None
}
}
pub fn extract_u32(&self, report_payload: &[u8], index: usize) -> Option<u32> {
if index >= self.count as usize {
return None;
}
let bit_start = self.bit_offset as usize + index * (self.bit_size as usize);
extract_bits(report_payload, bit_start, self.bit_size as usize)
}
pub fn extract_i32(&self, report_payload: &[u8], index: usize) -> Option<i32> {
let raw = self.extract_u32(report_payload, index)?;
if self.is_signed() && self.bit_size < 32 {
let sign_bit = 1u32 << (self.bit_size - 1);
if raw & sign_bit != 0 {
return Some((raw | !((sign_bit << 1).wrapping_sub(1))) as i32);
}
}
Some(raw as i32)
}
pub fn extract_bool(&self, report_payload: &[u8], index: usize) -> Option<bool> {
self.extract_u32(report_payload, index).map(|v| v != 0)
}
}
fn extract_bits(data: &[u8], bit_offset: usize, bit_count: usize) -> Option<u32> {
if bit_count == 0 || bit_count > 32 {
return None;
}
let byte_start = bit_offset / 8;
let byte_end = (bit_offset + bit_count + 7) / 8;
if byte_end > data.len() {
return None;
}
let mut val = 0u64;
for i in 0..(byte_end - byte_start) {
val |= (data[byte_start + i] as u64) << (i * 8);
}
val >>= bit_offset % 8;
val &= (1u64 << bit_count) - 1;
Some(val as u32)
}
#[derive(Debug)]
pub struct ReportDescriptor<const N: usize> {
fields: [Option<ReportField>; N],
count: usize,
pub has_report_ids: bool,
}
impl<const N: usize> ReportDescriptor<N> {
pub fn parse(descriptor: &[u8]) -> Self {
let mut global = GlobalState::default();
let mut local = LocalState::default();
let mut stack = [GlobalState::DEFAULT; 4];
let mut stack_depth: usize = 0;
let mut offsets: [(u8, u32); 16] = [(0, 0); 16];
let mut offset_count: usize = 1;
let mut result = ReportDescriptor {
fields: [const { None }; N],
count: 0,
has_report_ids: false,
};
for item in ItemIter::new(descriptor) {
match (item.item_type, item.tag) {
(1, 0) => global.usage_page = item.data as u16,
(1, 1) => global.logical_min = item.data_signed,
(1, 2) => global.logical_max = item.data_signed,
(1, 7) => global.report_size = item.data as u8,
(1, 8) => {
global.report_id = item.data as u8;
result.has_report_ids = true;
let id = global.report_id;
if !offsets[..offset_count].iter().any(|(i, _)| *i == id) && offset_count < 16 {
offsets[offset_count] = (id, 0);
offset_count += 1;
}
}
(1, 9) => global.report_count = item.data as u16,
(1, 10) => {
if stack_depth < stack.len() {
stack[stack_depth] = global.clone();
stack_depth += 1;
}
}
(1, 11) => {
if stack_depth > 0 {
stack_depth -= 1;
global = stack[stack_depth].clone();
}
}
(2, 0) => {
let (page, u) = if item.size > 2 {
((item.data >> 16) as u16, (item.data & 0xFFFF) as u16)
} else {
(0, item.data as u16)
};
if local.usage_count < local.usages.len() {
local.usages[local.usage_count] = ((page as u32) << 16) | u as u32;
local.usage_count += 1;
}
}
(2, 1) => {
local.usage_min = item.data as u16;
local.has_usage_range = true;
}
(2, 2) => {
local.usage_max = item.data as u16;
local.has_usage_range = true;
}
(0, 8) => {
let item_flags = item.data as u8;
let rc = global.report_count as usize;
let rs = global.report_size;
if rc > 0 && rs > 0 {
let bit_offset = offsets[..offset_count]
.iter_mut()
.find(|(id, _)| *id == global.report_id)
.map(|(_, off)| {
let start = *off;
*off += rc as u32 * rs as u32;
start
})
.unwrap_or(0);
let expand = (item_flags & flags::VARIABLE != 0)
&& local.usage_count > 0
&& local.usage_count == rc
&& local.usage_count <= local.usages.len();
if expand {
for (i, &packed) in local.usages[..local.usage_count].iter().enumerate() {
if result.count >= N {
break;
}
let page = {
let p = (packed >> 16) as u16;
if p != 0 { p } else { global.usage_page }
};
let u = (packed & 0xFFFF) as u16;
result.fields[result.count] = Some(ReportField {
report_id: global.report_id,
usage_page: page,
usage_min: u,
usage_max: u,
bit_offset: bit_offset + i as u32 * rs as u32,
bit_size: rs,
count: 1,
logical_min: global.logical_min,
logical_max: global.logical_max,
flags: item_flags,
});
result.count += 1;
}
} else if result.count < N {
let (page, umin, umax) = if local.has_usage_range {
(global.usage_page, local.usage_min, local.usage_max)
} else if local.usage_count > 0 {
let first = local.usages[0];
let last = local.usages[local.usage_count - 1];
let p = {
let hp = (first >> 16) as u16;
if hp != 0 { hp } else { global.usage_page }
};
(p, (first & 0xFFFF) as u16, (last & 0xFFFF) as u16)
} else {
(global.usage_page, 0, 0)
};
result.fields[result.count] = Some(ReportField {
report_id: global.report_id,
usage_page: page,
usage_min: umin,
usage_max: umax,
bit_offset,
bit_size: rs,
count: rc as u16,
logical_min: global.logical_min,
logical_max: global.logical_max,
flags: item_flags,
});
result.count += 1;
}
}
local = LocalState::default();
}
(0, 9) | (0, 11) => {
local = LocalState::default();
}
(0, 10) | (0, 12) => {
local = LocalState::default();
}
_ => {}
}
}
result
}
pub fn fields(&self) -> impl Iterator<Item = &ReportField> {
self.fields[..self.count].iter().filter_map(|f| f.as_ref())
}
pub fn find(&self, report_id: u8, page: u16, usage: u16) -> Option<(&ReportField, usize)> {
self.fields().find_map(|f| {
if f.report_id == report_id && f.usage_page == page {
f.index_of_usage(usage).map(|idx| (f, idx))
} else {
None
}
})
}
pub fn extract_i32(&self, report: &[u8], report_id: u8, page: u16, usage: u16) -> Option<i32> {
let payload = self.payload(report, report_id)?;
let (field, idx) = self.find(report_id, page, usage)?;
field.extract_i32(payload, idx)
}
pub fn extract_u32(&self, report: &[u8], report_id: u8, page: u16, usage: u16) -> Option<u32> {
let payload = self.payload(report, report_id)?;
let (field, idx) = self.find(report_id, page, usage)?;
field.extract_u32(payload, idx)
}
pub fn extract_bool(&self, report: &[u8], report_id: u8, page: u16, usage: u16) -> Option<bool> {
let payload = self.payload(report, report_id)?;
let (field, idx) = self.find(report_id, page, usage)?;
field.extract_bool(payload, idx)
}
fn payload<'a>(&self, report: &'a [u8], report_id: u8) -> Option<&'a [u8]> {
if self.has_report_ids {
if report.first() != Some(&report_id) {
return None;
}
Some(&report[1..])
} else {
Some(report)
}
}
}
#[derive(Clone)]
struct GlobalState {
usage_page: u16,
logical_min: i32,
logical_max: i32,
report_size: u8,
report_id: u8,
report_count: u16,
}
impl GlobalState {
const DEFAULT: Self = Self {
usage_page: 0,
logical_min: 0,
logical_max: 0,
report_size: 0,
report_id: 0,
report_count: 0,
};
}
impl Default for GlobalState {
fn default() -> Self {
Self::DEFAULT
}
}
struct LocalState {
usages: [u32; 16],
usage_count: usize,
usage_min: u16,
usage_max: u16,
has_usage_range: bool,
}
impl Default for LocalState {
fn default() -> Self {
Self {
usages: [0; 16],
usage_count: 0,
usage_min: 0,
usage_max: 0,
has_usage_range: false,
}
}
}
struct Item {
item_type: u8, tag: u8,
data: u32, data_signed: i32, size: u8, }
struct ItemIter<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> ItemIter<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
}
impl<'a> Iterator for ItemIter<'a> {
type Item = Item;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.pos >= self.bytes.len() {
return None;
}
let prefix = self.bytes[self.pos];
self.pos += 1;
if prefix == 0xFE {
if self.pos >= self.bytes.len() {
return None;
}
let data_size = self.bytes[self.pos] as usize;
self.pos += 2 + data_size; continue;
}
let size: usize = match prefix & 0x03 {
0 => 0,
1 => 1,
2 => 2,
3 => 4,
_ => unreachable!(),
};
let item_type = (prefix >> 2) & 0x03;
let tag = (prefix >> 4) & 0x0F;
if self.pos + size > self.bytes.len() {
return None;
}
let (data, data_signed) = match size {
0 => (0u32, 0i32),
1 => (self.bytes[self.pos] as u32, self.bytes[self.pos] as i8 as i32),
2 => {
let v = u16::from_le_bytes([self.bytes[self.pos], self.bytes[self.pos + 1]]);
(v as u32, v as i16 as i32)
}
4 => {
let v = u32::from_le_bytes([
self.bytes[self.pos],
self.bytes[self.pos + 1],
self.bytes[self.pos + 2],
self.bytes[self.pos + 3],
]);
(v, v as i32)
}
_ => unreachable!(),
};
self.pos += size;
return Some(Item {
item_type,
tag,
data,
data_signed,
size: size as u8,
});
}
}
}