blob: 4284bae736e2e1c31b112b8e04a264f0444140f7 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <hid-parser/report.h>
#include <hid-parser/units.h>
#include <type_traits>
namespace hid {
namespace {
// This sign extends the |n_bits| of |data| and returns it as a full
// int32_t value. |n_bits| must be between [0, 31].
// Example: If the user had a 2's complement 5 bit number 0b11111 it
// would represent -1. To sign extend this to an int32_t the function
// would be called as SignExtendFromBits(0b11111, 5)
constexpr int32_t SignExtendFromNBits(uint32_t data, int32_t n_bits) {
// Expression taken and simplified from:
// http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend
// Zero out information about n_bits.
data = data & ((1U << n_bits) - 1);
// Do the sign extend.
int32_t msb = 1U << (n_bits - 1);
return static_cast<int32_t>((data ^ msb) - msb);
}
inline bool FieldFits(size_t report_len, const Attributes& attr) {
return (attr.offset + attr.bit_sz) <= (report_len * 8);
}
// Extracts bits from a byte and returns them.
// |begin| is the starting bit: it starts at 0 and counts from LSB to MSB.
// The bits are placed at the beginning of return value.
// Make sure when this is called that (|begin| + |count|) <= 8.
// Example: ExtractBitsFromByte(1b00010100, 2, 3) returns 1b101.
inline uint8_t ExtractBitsFromByte(uint8_t val, uint32_t begin, uint8_t count) {
uint8_t mask = static_cast<uint8_t>((0xFF >> (8 - count)) << begin);
return static_cast<uint8_t>((val & mask) >> begin);
}
} // namespace
#define MIN(a, b) (a) < (b) ? (a) : (b)
template <typename T>
bool ExtractUint(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
T* value_out) {
static_assert(std::is_pod<T>::value, "not POD");
if (attr.bit_sz > sizeof(T) * 8) {
return false;
}
if (!FieldFits(report_len, attr)) {
return false;
}
T val = 0;
const uint32_t start_bit = attr.offset;
const uint32_t end_bit = start_bit + attr.bit_sz;
uint32_t index_bit = attr.offset;
while (index_bit < end_bit) {
uint8_t bits_till_byte_end = static_cast<uint8_t>(8u - (index_bit % 8u));
uint8_t bit_count = static_cast<uint8_t>(MIN(bits_till_byte_end, end_bit - index_bit));
uint8_t extracted_bits =
ExtractBitsFromByte(report[index_bit / 8u], index_bit % 8u, bit_count);
val = static_cast<T>(val | (extracted_bits << (index_bit - start_bit)));
index_bit += bit_count;
}
*value_out = val;
return true;
}
#undef MIN
bool ExtractUint(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
uint8_t* value_out) {
return ExtractUint<uint8_t>(report, report_len, attr, value_out);
}
bool ExtractUint(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
uint16_t* value_out) {
return ExtractUint<uint16_t>(report, report_len, attr, value_out);
}
bool ExtractUint(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
uint32_t* value_out) {
return ExtractUint<uint32_t>(report, report_len, attr, value_out);
}
bool ExtractAsUnit(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
double* value_out) {
if (value_out == nullptr) {
return false;
}
uint32_t uint_out;
bool ret = ExtractUint(report, report_len, attr, &uint_out);
if (!ret) {
return false;
}
// If the minimum value is less than zero, then the maximum
// value and the value itself are an unsigned number. Otherwise they
// are signed numbers.
const int64_t logc_max =
(attr.logc_mm.min < 0) ? attr.logc_mm.max : static_cast<uint32_t>(attr.logc_mm.max);
int64_t phys_max =
(attr.phys_mm.min < 0) ? attr.phys_mm.max : static_cast<uint32_t>(attr.phys_mm.max);
double val = (attr.logc_mm.min < 0)
? static_cast<double>(SignExtendFromNBits(uint_out, attr.bit_sz))
: uint_out;
if (val < static_cast<double>(attr.logc_mm.min) || val > static_cast<double>(attr.logc_mm.max)) {
return false;
}
int64_t phys_min = attr.phys_mm.min;
if (phys_max == 0 && phys_min == 0) {
phys_min = attr.logc_mm.min;
phys_max = logc_max;
}
double resolution =
static_cast<double>(logc_max - attr.logc_mm.min) / static_cast<double>(phys_max - phys_min);
*value_out = val / resolution;
return true;
}
bool ExtractWithUnit(const uint8_t* report, size_t report_len, const hid::Attributes& attr,
const Unit& unit_out, double* value_out) {
double val = 0;
if (!ExtractAsUnit(report, report_len, attr, &val)) {
return false;
}
return unit::ConvertUnits(attr.unit, val, unit_out, value_out);
}
} // namespace hid