diff --git a/src/libhidpp/hidpp/Device.cpp b/src/libhidpp/hidpp/Device.cpp index f1ff86f..1771ac4 100644 --- a/src/libhidpp/hidpp/Device.cpp +++ b/src/libhidpp/hidpp/Device.cpp @@ -26,6 +26,7 @@ #include #include +#include using namespace HIDPP; @@ -76,7 +77,9 @@ Device::Device (Dispatcher *dispatcher, DeviceIndex device_index): // Check protocol version static constexpr unsigned int software_id = 1; - Report request (Report::Short, _device_index, HIDPP20::IRoot::index, HIDPP20::IRoot::Ping, software_id); + auto type = _dispatcher->reportInfo ().findReport (); + assert (type); + Report request (*type, _device_index, HIDPP20::IRoot::index, HIDPP20::IRoot::Ping, software_id); auto response = _dispatcher->sendCommand (std::move (request)); try { auto report = response->get (is_wireless ? 2000 : 500); // use longer timeout for wireless devices that can be sleeping. diff --git a/src/libhidpp/hidpp/Dispatcher.cpp b/src/libhidpp/hidpp/Dispatcher.cpp index be00349..712c58d 100644 --- a/src/libhidpp/hidpp/Dispatcher.cpp +++ b/src/libhidpp/hidpp/Dispatcher.cpp @@ -18,6 +18,8 @@ #include "Dispatcher.h" +#include + using namespace HIDPP; const char *Dispatcher::NoHIDPPReportException::what () const noexcept @@ -58,3 +60,96 @@ void Dispatcher::processEvent (const Report &report) it = _listeners.erase (it); } } + +static bool hasReport(const HID::ReportCollection &collection, HID::ReportID::Type type, uint8_t id, HID::Usage usage, unsigned int count) +{ + using namespace HID; + auto it = collection.reports.find (ReportID {type, id}); + if (it == collection.reports.end ()) + return false; + const auto &fields = it->second; + if (fields.size () != 1) + return false; + const auto &field = fields.front (); + if (auto usages = std::get_if> (&field.usages)) { + return field.flags.Data () && field.flags.Array () && + field.count == count && field.size == 8 && + usages->size () == 1 && usages->front () == usage; + } + else + return false; +} + +void Dispatcher::checkReportDescriptor (const HID::ReportDescriptor &rdesc) +{ + enum { + Unknown, + Legacy, + Modern, + } scheme = Unknown; + _report_info.flags = 0; + uint8_t expected_reports = 0; + for (const auto &collection: rdesc.collections) { + auto collection_usage_msb = static_cast (collection.usage.usage >> 8); + auto collection_usage_lsb = static_cast (collection.usage.usage); + if (collection.usage.usage_page == 0xFF43) { // Modern scheme usage page + if (scheme == Unknown) { + scheme = Modern; + expected_reports = collection_usage_msb; + } + else if (scheme != Modern) { + Log::warning () << "Ignoring HID++ collection with unexpected usage page." << std::endl; + continue; + } + else if (expected_reports != collection_usage_msb) { + Log::warning () << "Ignoring HID++ collection with mismatched usage report flags." << std::endl; + continue; + } + } + else if (collection.usage.usage_page == 0xFF00) { // Legacy scheme usage page + if (scheme == Unknown) + scheme = Legacy; + else if (scheme != Legacy) { + Log::warning () << "Ignoring HID++ collection with unexpected usage page." << std::endl; + continue; + } + else if (collection_usage_msb != 0) { + Log::warning () << "Ignoring HID++ collection with invalid usage." << std::endl; + continue; + } + } + else // Not a HID++ collection + continue; + for (auto [type, flag]: { + std::make_tuple (Report::Short, ReportInfo::HasShortReport), + std::make_tuple (Report::Long, ReportInfo::HasLongReport), + std::make_tuple (Report::VeryLong, ReportInfo::HasVeryLongReport) }) { + if (collection_usage_lsb == flag) { + auto usage = HID::Usage (collection.usage.usage_page, flag); + bool has_input = hasReport ( + collection, + HID::ReportID::Type::Input, + static_cast (type), + usage, + Report::reportLength (type)-1); + if (!has_input) + Log::warning () << "Missing input report for report " << type << std::endl; + bool has_output = hasReport ( + collection, + HID::ReportID::Type::Output, + static_cast (type), + usage, + Report::reportLength(type)-1); + if (!has_output) + Log::warning () << "Missing output report for report " << type << std::endl; + if (has_input && has_output) + _report_info.flags |= flag; + } + } + } + if (scheme == Modern && expected_reports != _report_info.flags) + Log::warning () << "Expected HID++ reports were not found." << std::endl; + if (_report_info.flags == 0) + throw Dispatcher::NoHIDPPReportException (); +} + diff --git a/src/libhidpp/hidpp/Dispatcher.h b/src/libhidpp/hidpp/Dispatcher.h index 4b251df..4d0d9d8 100644 --- a/src/libhidpp/hidpp/Dispatcher.h +++ b/src/libhidpp/hidpp/Dispatcher.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace HIDPP { @@ -119,11 +120,39 @@ class Dispatcher */ virtual void unregisterEventHandler (listener_iterator it); + struct ReportInfo { + enum Flags { // flags are also the usage for collections and reports + HasShortReport = 1<<0, + HasLongReport = 1<<1, + HasVeryLongReport = 1<<2, + }; + int flags; + + bool hasReport (Report::Type type) const noexcept { + switch (type) { + case Report::Short: return flags & HasShortReport; + case Report::Long: return flags & HasLongReport; + case Report::VeryLong: return flags & HasVeryLongReport; + default: return false; + } + } + + std::optional findReport (std::size_t minimum_parameter_length = 0) const noexcept { + for (auto type: { Report::Short, Report::Long, Report::VeryLong }) + if (hasReport (type) && minimum_parameter_length <= Report::parameterLength (type)) + return type; + return std::nullopt; + } + }; + ReportInfo reportInfo () const noexcept { return _report_info; } + protected: void processEvent (const Report &); + void checkReportDescriptor (const HID::ReportDescriptor &report_desc); private: listener_container _listeners; + ReportInfo _report_info; }; } diff --git a/src/libhidpp/hidpp/DispatcherThread.cpp b/src/libhidpp/hidpp/DispatcherThread.cpp index c12dd3b..a4af431 100644 --- a/src/libhidpp/hidpp/DispatcherThread.cpp +++ b/src/libhidpp/hidpp/DispatcherThread.cpp @@ -66,9 +66,7 @@ DispatcherThread::DispatcherThread (const char *path): _dev (path), _stopped (false) { - const HID::ReportDescriptor &rdesc = _dev.getReportDescriptor (); - if (!checkReportDescriptor (rdesc)) - throw Dispatcher::NoHIDPPReportException (); + checkReportDescriptor (_dev.getReportDescriptor ()); } DispatcherThread::~DispatcherThread () @@ -151,7 +149,7 @@ void DispatcherThread::run () { while (!_stopped) { try { - std::vector raw_report (Report::MaxDataLength+1); + std::vector raw_report (MaxReportLength); if (0 != _dev.readReport (raw_report)) processReport (std::move (raw_report)); } diff --git a/src/libhidpp/hidpp/Report.cpp b/src/libhidpp/hidpp/Report.cpp index fa04863..32a83b0 100644 --- a/src/libhidpp/hidpp/Report.cpp +++ b/src/libhidpp/hidpp/Report.cpp @@ -25,68 +25,6 @@ using namespace HIDPP; -static bool hasReport(const HID::ReportCollection &collection, HID::ReportID::Type type, uint8_t id, HID::Usage usage, unsigned int count) -{ - using namespace HID; - auto it = collection.reports.find (ReportID {type, id}); - if (it == collection.reports.end ()) - return false; - const auto &fields = it->second; - if (fields.size () != 1) - return false; - const auto &field = fields.front (); - if (auto usages = std::get_if> (&field.usages)) { - return field.flags.Data () && field.flags.Array () && - field.count == count && field.size == 8 && - usages->size () == 1 && usages->front () == usage; - } - else - return false; -} - -bool HIDPP::checkReportDescriptor (const HID::ReportDescriptor &rdesc) -{ - static constexpr auto ShortReportUsage = HID::Usage (0xFF000001); - static constexpr auto LongReportUsage = HID::Usage (0xFF000002); - static constexpr unsigned int ShortReportCount = 6; - static constexpr unsigned int LongReportCount = 19; - bool has_short_input = false; - bool has_long_input = false; - bool has_short_output = false; - bool has_long_output = false; - for (const auto &collection: rdesc.collections) { - if (collection.usage == ShortReportUsage) { - has_short_input = hasReport ( - collection, - HID::ReportID::Type::Input, - Report::Short, - ShortReportUsage, - ShortReportCount); - has_short_output = hasReport ( - collection, - HID::ReportID::Type::Output, - Report::Short, - ShortReportUsage, - ShortReportCount); - } - else if (collection.usage == LongReportUsage) { - has_long_input = hasReport ( - collection, - HID::ReportID::Type::Input, - Report::Long, - LongReportUsage, - LongReportCount); - has_long_output = hasReport ( - collection, - HID::ReportID::Type::Output, - Report::Long, - LongReportUsage, - LongReportCount); - } - } - return has_short_input && has_long_input && has_short_output && has_long_output; -} - Report::InvalidReportID::InvalidReportID () { } @@ -114,38 +52,24 @@ static constexpr unsigned int Address = 3; static constexpr unsigned int Parameters = 4; } -Report::Report (uint8_t type, const uint8_t *data, std::size_t length) +Report::Report (uint8_t report_id, const uint8_t *data, std::size_t length) { - switch (static_cast (type)) { - case Short: - _data.resize (HeaderLength+ShortParamLength); - break; - case Long: - _data.resize (HeaderLength+LongParamLength); - break; - default: + + auto expected_len = reportLength (static_cast (report_id)); + if (expected_len == 0) throw InvalidReportID (); - } - if (length != _data.size ()-1) + _data.resize (expected_len); + if (length != expected_len-1) throw InvalidReportLength (); - - _data[Offset::Type] = type; + _data[Offset::Type] = report_id; std::copy_n (data, length, &_data[1]); } Report::Report (std::vector &&data) { - std::size_t expected_len; - switch (static_cast (data[0])) { - case Short: - expected_len = HeaderLength+ShortParamLength; - break; - case Long: - expected_len = HeaderLength+LongParamLength; - break; - default: + auto expected_len = reportLength (static_cast (data[0])); + if (expected_len == 0) throw InvalidReportID (); - } if (data.size () != expected_len) throw InvalidReportLength (); _data = std::move (data); @@ -156,14 +80,7 @@ Report::Report (Type type, uint8_t sub_id, uint8_t address) { - switch (type) { - case Short: - _data.resize (HeaderLength+ShortParamLength, 0); - break; - case Long: - _data.resize (HeaderLength+LongParamLength, 0); - break; - } + _data.resize (reportLength (type), 0); _data[Offset::Type] = type; _data[Offset::DeviceIndex] = device_index; _data[Offset::SubID] = sub_id; @@ -176,18 +93,16 @@ Report::Report (HIDPP::DeviceIndex device_index, std::vector::const_iterator param_begin, std::vector::const_iterator param_end) { - switch (std::distance (param_begin, param_end)) { - case ShortParamLength: - _data.resize (HeaderLength+ShortParamLength); - _data[Offset::Type] = Short; - break; - case LongParamLength: - _data.resize (HeaderLength+LongParamLength); - _data[Offset::Type] = Long; - break; - default: - throw InvalidReportLength (); + std::size_t param_len = std::distance (param_begin, param_end); + for (auto type: { Short, Long, VeryLong }) { + if (param_len == parameterLength (type)) { + _data.resize (reportLength (type)); + _data[Offset::Type] = type; + break; + } } + if (_data.empty ()) + throw InvalidReportLength (); _data[Offset::DeviceIndex] = device_index; _data[Offset::SubID] = sub_id; _data[Offset::Address] = address; @@ -200,14 +115,7 @@ Report::Report (Type type, unsigned int function, unsigned int sw_id) { - switch (type) { - case Short: - _data.resize (HeaderLength+ShortParamLength, 0); - break; - case Long: - _data.resize (HeaderLength+LongParamLength, 0); - break; - } + _data.resize (reportLength (type), 0); _data[Offset::Type] = type; _data[Offset::DeviceIndex] = device_index; _data[Offset::SubID] = feature_index; @@ -221,18 +129,16 @@ Report::Report (DeviceIndex device_index, std::vector::const_iterator param_begin, std::vector::const_iterator param_end) { - switch (std::distance (param_begin, param_end)) { - case ShortParamLength: - _data.resize (HeaderLength+ShortParamLength); - _data[Offset::Type] = Short; - break; - case LongParamLength: - _data.resize (HeaderLength+LongParamLength); - _data[Offset::Type] = Long; - break; - default: - throw InvalidReportLength (); + std::size_t param_len = std::distance (param_begin, param_end); + for (auto type: { Short, Long, VeryLong }) { + if (param_len == parameterLength (type)) { + _data.resize (reportLength (type)); + _data[Offset::Type] = type; + break; + } } + if (_data.empty ()) + throw InvalidReportLength (); _data[Offset::DeviceIndex] = device_index; _data[Offset::SubID] = feature_index; _data[Offset::Address] = (function & 0x0f) << 4 | (sw_id & 0x0f); @@ -304,18 +210,6 @@ std::size_t Report::parameterLength () const return parameterLength (static_cast (_data[Offset::Type])); } -std::size_t Report::parameterLength (Type type) -{ - switch (type) { - case Short: - return ShortParamLength; - case Long: - return LongParamLength; - default: - throw std::logic_error ("Invalid type"); - } -} - std::vector::iterator Report::parameterBegin () { return _data.begin () + Offset::Parameters; diff --git a/src/libhidpp/hidpp/Report.h b/src/libhidpp/hidpp/Report.h index 50aa385..c6e5b67 100644 --- a/src/libhidpp/hidpp/Report.h +++ b/src/libhidpp/hidpp/Report.h @@ -28,8 +28,6 @@ namespace HIDPP { -bool checkReportDescriptor (const HID::ReportDescriptor &report_desc); - /** * Contains a HID++ report. * @@ -53,20 +51,27 @@ bool checkReportDescriptor (const HID::ReportDescriptor &report_desc); */ class Report { + static constexpr std::size_t HeaderLength = 4; public: - /** - * The type of the report (or report ID). - * - * The only difference between report types is the length of its - * parameters. - * - * \sa ShortParamLength LongParamLength - */ enum Type: uint8_t { - Short = 0x10, ///< Short reports use 3 byte parameters. - Long = 0x11, ///< Long report use 16 byte parameters. + Short = 0x10, + Long = 0x11, + VeryLong = 0x12, }; + static inline constexpr std::size_t reportLength (Type type) noexcept { + switch (type) { + case Type::Short: return 7; + case Type::Long: return 20; + case Type::VeryLong: return 64; + default: return 0; + } + } + + inline static constexpr std::size_t parameterLength (Type type) noexcept { + return reportLength (type) - HeaderLength; + } + /** * Exception for reports with invalid report ID. */ @@ -87,11 +92,6 @@ class Report virtual const char *what () const noexcept; }; - /** - * Maximum length of a HID++ report. - */ - static constexpr std::size_t MaxDataLength = 19; - /** * Build the report by copying the raw data. * @@ -265,10 +265,6 @@ class Report * Get the parameter length of the report. */ std::size_t parameterLength () const; - /** - * Get the parameter length for \p type. - */ - static std::size_t parameterLength (Type type); /** Begin iterator for parameters. */ std::vector::iterator parameterBegin (); @@ -285,10 +281,14 @@ class Report const std::vector &rawReport () const; private: - static constexpr std::size_t HeaderLength = 4; std::vector _data; }; +inline constexpr auto MaxReportLength = Report::reportLength (Report::VeryLong); +inline constexpr auto ShortParamLength = Report::parameterLength (Report::Short); +inline constexpr auto LongParamLength = Report::parameterLength (Report::Long); +inline constexpr auto VeryLongParamLength = Report::parameterLength (Report::VeryLong); + } #endif diff --git a/src/libhidpp/hidpp/SimpleDispatcher.cpp b/src/libhidpp/hidpp/SimpleDispatcher.cpp index bd3a28f..07f36ef 100644 --- a/src/libhidpp/hidpp/SimpleDispatcher.cpp +++ b/src/libhidpp/hidpp/SimpleDispatcher.cpp @@ -29,9 +29,7 @@ using namespace HIDPP; SimpleDispatcher::SimpleDispatcher (const char *path): _dev (path) { - const HID::ReportDescriptor &rdesc = _dev.getReportDescriptor (); - if (!checkReportDescriptor (rdesc)) - throw Dispatcher::NoHIDPPReportException (); + checkReportDescriptor (_dev.getReportDescriptor ()); } SimpleDispatcher::~SimpleDispatcher () @@ -96,7 +94,7 @@ void SimpleDispatcher::stop () Report SimpleDispatcher::getReport (int timeout) { while (true) { - std::vector raw_report (Report::MaxDataLength+1); + std::vector raw_report (MaxReportLength); if (0 == _dev.readReport (raw_report, timeout)) throw Dispatcher::TimeoutError (); try { diff --git a/src/libhidpp/hidpp/defs.h b/src/libhidpp/hidpp/defs.h index 3389c03..4cd708e 100644 --- a/src/libhidpp/hidpp/defs.h +++ b/src/libhidpp/hidpp/defs.h @@ -29,15 +29,6 @@ namespace HIDPP * @{ */ - /** - * Short HID++ report parameter length. - */ - constexpr std::size_t ShortParamLength = 3; - /** - * Long HID++ report parameter length. - */ - constexpr std::size_t LongParamLength = 16; - /** * HID++ device index. * diff --git a/src/libhidpp/hidpp20/Device.cpp b/src/libhidpp/hidpp20/Device.cpp index 1c6d8b8..cf0874c 100644 --- a/src/libhidpp/hidpp20/Device.cpp +++ b/src/libhidpp/hidpp20/Device.cpp @@ -51,15 +51,10 @@ std::vector Device::callFunction (uint8_t feature_index, debug.printBytes ("Parameters:", param_begin, param_end); std::size_t len = std::distance (param_begin, param_end); - HIDPP::Report::Type type; - if (len <= HIDPP::ShortParamLength) - type = HIDPP::Report::Short; - else if (len <= HIDPP::LongParamLength) - type = HIDPP::Report::Long; - else { + auto type = dispatcher ()->reportInfo ().findReport (len); + if (!type) throw std::logic_error ("Parameters too long"); - } - HIDPP::Report request (type, deviceIndex (), feature_index, function, softwareID); + HIDPP::Report request (*type, deviceIndex (), feature_index, function, softwareID); std::copy (param_begin, param_end, request.parameterBegin ()); auto response = dispatcher ()->sendCommand (std::move (request))->get ();