///|
/// Integer values use the field's signedness; Raw values are packed LSB-first.
/// A Buffered Bytes Main item takes one Raw value of Size * Count / 8 bytes.
pub(all) enum ReportValue {
Integer(Int64)
Raw(Bytes)
} derive(Debug, Eq)
///|
pub extend ReportValue with @debug.Debug::{to_repr}
///|
pub extend ReportValue with Eq::{equal, not_equal}
///|
fn validate_wire(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
) -> Result[Int, Diagnostic] {
let 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, 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",
),
)
}
Ok(id)
}
///|
fn integer_fits(value : Int64, width : Int, signed : Bool) -> Bool {
let minimum = if signed { -(1L << (width - 1)) } else { 0L }
let maximum = if signed {
(1L << (width - 1)) - 1L
} else {
(1L << width) - 1L
}
value >= minimum && value <= maximum
}
///|
fn raw_fits(bytes : Bytes, width : Int) -> Bool {
if bytes.length() != (width + 7) / 8 {
return false
}
let remainder = width % 8
remainder == 0 || bytes[bytes.length() - 1].to_int() >> remainder == 0
}
///|
fn put_integer(
target : FixedArray[Byte],
offset : Int,
width : Int,
value : Int64,
) -> Unit {
let start_byte = offset / 8
let start_bit = offset % 8
for bit = 0; bit < width; bit = bit + 1 {
let position = start_bit + bit
let mask = 1 << (position % 8)
let previous = target[start_byte + position / 8].to_int()
let next = if ((value >> bit) & 1L) != 0 {
previous | mask
} else {
previous & (255 ^ mask)
}
target[start_byte + position / 8] = next.to_byte()
}
}
///|
fn put_raw(
target : FixedArray[Byte],
offset : Int,
width : Int,
bytes : Bytes,
) -> Unit {
let start_byte = offset / 8
let start_bit = offset % 8
for bit = 0; bit < width; bit = bit + 1 {
let position = start_bit + bit
let mask = 1 << (position % 8)
let previous = target[start_byte + position / 8].to_int()
let next = if ((bytes[bit / 8].to_int() >> (bit % 8)) & 1) != 0 {
previous | mask
} else {
previous & (255 ^ mask)
}
target[start_byte + position / 8] = next.to_byte()
}
}
///|
fn writable_value(
field : Field,
value : ReportValue,
allow_out_of_range : Bool,
) -> Result[Unit, Diagnostic] {
if (field.flags & 256) != 0 {
return match value {
Raw(bytes) if bytes.length() == field_value_bits(field) / 8 => Ok(())
Raw(_) =>
Err(
diagnostic(
field.descriptor_offset,
"value_range",
"Buffered Bytes must match the complete fixed-size stream length",
),
)
Integer(_) =>
Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Buffered Bytes require one Raw byte stream",
),
)
}
}
match value {
Integer(value) => {
if field.bit_size > 32 {
return Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Fields wider than 32 bits require Raw bytes",
),
)
}
if !integer_fits(value, field.bit_size, field.logical_min < 0) {
return Err(
diagnostic(
field.descriptor_offset,
"value_range",
"Value does not fit the field's width and signedness",
),
)
}
if !allow_out_of_range &&
(field.flags & 64) == 0 &&
(value < field.logical_min || value > field.logical_max) {
return Err(
diagnostic(
field.descriptor_offset,
"logical_range",
"Value is outside the field's logical range",
),
)
}
}
Raw(bytes) => {
if field.bit_size <= 32 {
return Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Fields of 1..32 bits require Integer values",
),
)
}
if !raw_fits(bytes, field.bit_size) {
return Err(
diagnostic(
field.descriptor_offset,
"value_range",
"Raw bytes must match the field width with zero trailing high bits",
),
)
}
}
}
Ok(())
}
///|
fn put_value(
target : FixedArray[Byte],
offset : Int,
width : Int,
value : ReportValue,
) -> Unit {
match value {
Integer(value) => put_integer(target, offset, width, value)
Raw(bytes) => put_raw(target, offset, width, bytes)
}
}
///|
/// Encodes every non-Constant element in descriptor order, then element order.
/// Each Buffered Bytes Main item consumes one Raw value for the entire stream.
/// Constant and unused final bits are zero. Null State accepts representable
/// out-of-range values; allow_out_of_range permits replay of other invalid data.
pub fn encode_report(
layout : Layout,
kind : ReportKind,
report_id : Int,
values : Array[ReportValue],
allow_out_of_range? : Bool = false,
) -> Result[Bytes, Diagnostic] {
let length = match report_length(layout, kind, report_id) {
Ok(length) => length
Err(error) => return Err(error)
}
let mut expected = 0
for field in layout.fields {
if field.kind == kind &&
field.report_id == report_id &&
(field.flags & 1) == 0 {
expected = expected + field_value_count(field)
}
}
if values.length() != expected {
return Err(
diagnostic(
0,
"value_count",
"Expected \{expected} non-Constant values, received \{values.length()}",
),
)
}
let target : FixedArray[Byte] = FixedArray::make(length, 0)
let prefix = if layout.has_report_ids { 8 } else { 0 }
if layout.has_report_ids {
target[0] = report_id.to_byte()
}
let mut index = 0
for field in layout.fields {
if field.kind != kind ||
field.report_id != report_id ||
(field.flags & 1) != 0 {
continue
}
let width = field_value_bits(field)
for element = 0; element < field_value_count(field); element = element + 1 {
let value = values[index]
match writable_value(field, value, allow_out_of_range) {
Ok(_) => ()
Err(error) => return Err(error)
}
put_value(
target,
prefix + field.bit_offset + element * field.bit_size,
width,
value,
)
index = index + 1
}
}
Ok(Bytes::from_array(target.exact_view()))
}
///|
fn selected_field(
layout : Layout,
kind : ReportKind,
id : Int,
field_index : Int,
element_index : Int,
) -> Result[Field, Diagnostic] {
if field_index < 0 || field_index >= layout.fields.length() {
return Err(
diagnostic(0, "field_index", "Field index is outside the layout"),
)
}
let field = layout.fields[field_index]
if field.kind != kind || field.report_id != id {
return Err(
diagnostic(
field.descriptor_offset,
"field_report",
"Field belongs to another report kind or ID",
),
)
}
if element_index < 0 || element_index >= field_value_count(field) {
return Err(
diagnostic(
field.descriptor_offset,
"element_index",
"Element index is outside the field's data values (Buffered Bytes use index 0)",
),
)
}
if (field.flags & 1) != 0 {
return Err(
diagnostic(
field.descriptor_offset,
"constant_field",
"Constant fields are not data values",
),
)
}
Ok(field)
}
///|
/// Reads one data element using global layout field and within-field indices.
/// Buffered Bytes use element_index=0 and return the complete Raw stream.
pub fn read_field(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
field_index : Int,
element_index : Int,
) -> Result[ReportValue, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let field = match
selected_field(layout, kind, id, field_index, element_index) {
Ok(field) => field
Err(error) => return Err(error)
}
let offset = (if layout.has_report_ids { 8 } else { 0 }) +
field.bit_offset +
element_index * field.bit_size
let width = field_value_bits(field)
if (field.flags & 256) != 0 || field.bit_size > 32 {
match extract_bytes(bytes, offset, width) {
Ok(raw) => Ok(Raw(raw))
Err(error) => Err(error)
}
} else {
match
extract_bits(bytes, offset, field.bit_size, signed=field.logical_min < 0) {
Ok(value) => Ok(Integer(value))
Err(error) => Err(error)
}
}
}
///|
/// Returns a new report with one element replaced, preserving all other bits.
pub fn write_field(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
field_index : Int,
element_index : Int,
value : ReportValue,
allow_out_of_range? : Bool = false,
) -> Result[Bytes, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let field = match
selected_field(layout, kind, id, field_index, element_index) {
Ok(field) => field
Err(error) => return Err(error)
}
match writable_value(field, value, allow_out_of_range) {
Ok(_) => ()
Err(error) => return Err(error)
}
let target = FixedArray::makei(bytes.length(), i => bytes[i])
let offset = (if layout.has_report_ids { 8 } else { 0 }) +
field.bit_offset +
element_index * field.bit_size
put_value(target, offset, field_value_bits(field), value)
Ok(Bytes::from_array(target.exact_view()))
}
///|
/// Finds Variable elements or complete Buffered Bytes streams by Usage.
/// Buffered Bytes associate their first Usage with element_index=0.
/// Numeric Arrays use selectors.
/// Repeated Usages intentionally return multiple references.
pub fn find_fields(
layout : Layout,
kind : ReportKind,
report_id : Int,
usage : Usage,
) -> Result[Array[FieldRef], Diagnostic] {
match report_length(layout, kind, report_id) {
Ok(_) => ()
Err(error) => return Err(error)
}
let refs : Array[FieldRef] = []
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 &&
((field.flags & 2) != 0 || (field.flags & 256) != 0) {
for element_index = 0
element_index < field_value_count(field)
element_index = element_index + 1 {
if field_usage(field, element_index) == Some(usage) {
refs.push({ field_index, element_index, })
}
}
}
}
Ok(refs)
}
///|
pub(all) struct FieldRef {
field_index : Int
element_index : Int
} derive(Debug, Eq)
///|
pub extend FieldRef with @debug.Debug::{to_repr}
///|
pub extend FieldRef with Eq::{equal, not_equal}
///|
/// Converts an Array Usage to its logical selector, without expanding ranges.
pub fn array_selector(
field : Field,
usage : Usage,
) -> Result[Int64, Diagnostic] {
if (field.flags & 259) != 0 {
return Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Usage selectors require a Data Array field",
),
)
}
if !valid_usage_spans(field.usage_spans) ||
field.flags < 0 ||
field.flags > 255 ||
field.bit_size < 1 ||
field.bit_size > 32 ||
field.logical_max < field.logical_min ||
field.logical_min < -2147483648L ||
field.logical_min > 2147483647L ||
!integer_fits(field.logical_min, field.bit_size, field.logical_min < 0) ||
!integer_fits(field.logical_max, field.bit_size, field.logical_min < 0) {
return Err(
diagnostic(
field.descriptor_offset,
"invalid_layout",
"Invalid Array dimensions, flags, Usage spans or logical range",
),
)
}
let mut index = 0L
for span in field.usage_spans {
if span.page == usage.page && usage.id >= span.min && usage.id <= span.max {
let value = field.logical_min + index + (usage.id - span.min).to_int64()
if value > field.logical_max {
return Err(
diagnostic(
field.descriptor_offset,
"logical_range",
"Usage selector is outside the logical range",
),
)
}
return Ok(value)
}
index = index + (span.max - span.min + 1).to_int64()
}
Err(
diagnostic(
field.descriptor_offset,
"unknown_usage",
"Usage is absent from the Array field",
),
)
}