///|
pub(all) struct DecodedValue {
field_index : Int
element_index : Int
bit_offset : Int
value : Int64
usage : Usage?
is_array : Bool
in_logical_range : Bool
is_null : Bool
} derive(Debug, Eq)
///|
pub extend DecodedValue with @debug.Debug::{to_repr}
///|
pub extend DecodedValue with Eq::{equal, not_equal}
///|
/// Values wider than 32 bits remain raw bytes with zero-filled trailing high bits.
pub(all) struct OpaqueValue {
field_index : Int
element_index : Int
bit_offset : Int
bit_size : Int
bytes : Bytes
} derive(Debug, Eq)
///|
pub extend OpaqueValue with @debug.Debug::{to_repr}
///|
pub extend OpaqueValue with Eq::{equal, not_equal}
///|
pub(all) struct DecodedReport {
kind : ReportKind
report_id : Int
values : Array[DecodedValue]
opaque_values : Array[OpaqueValue]
} derive(Debug, Eq)
///|
pub extend DecodedReport with @debug.Debug::{to_repr}
///|
pub extend DecodedReport with Eq::{equal, not_equal}
///|
/// Returns the exact wire length, including an ID prefix when used.
pub fn report_length(
layout : Layout,
kind : ReportKind,
report_id : Int,
) -> Result[Int, Diagnostic] {
if layout.fields.length() > 4096 {
return Err(diagnostic(0, "invalid_layout", "Layout exceeds 4096 fields"))
}
let mut bits = 0
let mut found = false
for field in layout.fields {
if field.kind != kind || field.report_id != report_id {
continue
}
found = true
if field.bit_size < 1 ||
field.bit_size > 65536 ||
((field.flags & 3) == 0 && field.bit_size > 32) ||
field.count < 1 ||
field.count > 65536 ||
field.bit_offset != bits {
return Err(
diagnostic(
field.descriptor_offset,
"invalid_layout",
"Layout dimensions or offsets are invalid",
),
)
}
if field.bit_size > (65536 - bits) / field.count {
return Err(
diagnostic(
field.descriptor_offset,
"invalid_layout",
"Report exceeds 65536 bits",
),
)
}
bits = bits + field.bit_size * field.count
if !valid_usage_spans(field.usage_spans) ||
!valid_index_spans(field.string_spans) ||
!valid_index_spans(field.designator_spans) ||
!valid_alternate_usages(field.usage_spans, field.alternate_usages) ||
field.flags < 0 ||
field.flags > 255 {
return Err(
diagnostic(
field.descriptor_offset,
"invalid_layout",
"Layout exceeds supported limits",
),
)
}
}
if !found {
Err(
diagnostic(0, "unknown_report", "No report with the selected kind and ID"),
)
} else if (layout.has_report_ids && (report_id < 1 || report_id > 255)) ||
(!layout.has_report_ids && report_id != 0) {
Err(diagnostic(0, "invalid_layout", "Report ID disagrees with layout mode"))
} else {
Ok((bits + 7) / 8 + (if layout.has_report_ids { 1 } else { 0 }))
}
}
///|
/// Decodes a wire report. Input length must exactly match the declared report.
/// Constant fields are omitted; array values index usages from Logical Minimum.
/// Out-of-range values are retained, with is_null set only for Null State fields.
pub fn decode_report(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
) -> Result[DecodedReport, Diagnostic] {
let report_id = if layout.has_report_ids {
if bytes.is_empty() {
return Err(diagnostic(0, "short_report", "Missing Report ID"))
}
bytes[0].to_int()
} else {
0
}
let length = match report_length(layout, kind, report_id) {
Ok(length) => length
Err(error) => return Err(error)
}
if bytes.length() != length {
return Err(
diagnostic(
bytes.length(),
"report_length",
"Wire report length does not match the descriptor",
),
)
}
let prefix_bits = if layout.has_report_ids { 8 } else { 0 }
let values : Array[DecodedValue] = []
let opaque_values : Array[OpaqueValue] = []
for field_index = 0
field_index < layout.fields.length()
field_index = field_index + 1 {
let field = layout.fields[field_index]
if field.kind != kind ||
field.report_id != report_id ||
(field.flags & 1) != 0 {
continue
}
for element_index = 0
element_index < field.count
element_index = element_index + 1 {
let offset = field.bit_offset + element_index * field.bit_size
if field.bit_size > 32 {
let packed = match
extract_bytes(bytes, prefix_bits + offset, field.bit_size) {
Ok(packed) => packed
Err(error) => return Err(error)
}
opaque_values.push({
field_index,
element_index,
bit_offset: offset,
bit_size: field.bit_size,
bytes: packed,
})
continue
}
let value = match
extract_bits(
bytes,
prefix_bits + offset,
field.bit_size,
signed=field.logical_min < 0,
) {
Ok(value) => value
Err(error) => return Err(error)
}
let in_logical_range = value >= field.logical_min &&
value <= field.logical_max
let is_array = (field.flags & 2) == 0
let usage = if is_array {
let index = value - field.logical_min
if in_logical_range &&
index >= 0 &&
index < usage_count(field.usage_spans).to_int64() {
field_usage(field, index.to_int())
} else {
None
}
} else {
field_usage(field, element_index)
}
values.push({
field_index,
element_index,
bit_offset: offset,
value,
usage,
is_array,
in_logical_range,
is_null: !in_logical_range && (field.flags & 64) != 0,
})
}
}
Ok({ kind, report_id, values, opaque_values, })
}