/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "HidDefs.h"
#include "HidParser.h"
#include "HidLog.h"
#include <iostream>
#include <iomanip>
namespace HidUtil {
void HidParser::reset() {
mGlobalStack = HidGlobalStack();
mLocal = HidLocal();
mTree = std::make_shared<HidTreeNode>();
mCurrent = mTree;
}
bool HidParser::parse(const std::vector<HidItem> &token) {
// Clean up internal states of the parser for a new stream of descriptor token
reset();
bool ret = true;
using namespace HidDef::TagType;
for (auto &i : token) {
switch (i.type) {
case MAIN:
ret = processMainTag(i);
break;
case GLOBAL:
ret = mGlobalStack.append(i);
break;
case LOCAL:
ret = mLocal.append(i);
break;
default:
LOG_E << "HidParser found illegal HidItem: " << i << LOG_ENDL;
ret = false;
}
// in case a parse failure, quit prematurely
if (!ret) {
break;
}
}
return ret;
}
bool HidParser::processMainTag(const HidItem &i) {
using namespace HidDef::MainTag;
using namespace HidDef::ReportFlag;
bool ret = true;
switch (i.tag) {
case COLLECTION: {
unsigned int collectionType;
if (!i.dataAsUnsigned(&collectionType)) {
LOG_E << "Cannot get collection type at offset " << i.offset << LOG_ENDL;
ret = false;
break;
}
unsigned int fullUsage =
mGlobalStack.top().usagePage.get(0) << 16 | mLocal.getUsage(0);
mCurrent = mCurrent->addChild(
std::make_shared<HidTreeNode>(mCurrent, collectionType, fullUsage));
break;
}
case END_COLLECTION:
mCurrent = mCurrent->getParent();
if (!mCurrent) {
// trigger parse failure so that mCurrent will not be accessed
LOG_E << "unmatched END_COLLECTION at " << i.offset << LOG_ENDL;
ret = false;
}
break;
case INPUT:
case OUTPUT:
case FEATURE: {
unsigned int reportType = i.tag;
unsigned int flag;
if (!i.dataAsUnsigned(&flag)) {
LOG_E << "Cannot get report flag at offset " << i.offset << LOG_ENDL;
ret = false;
break;
}
const HidGlobal &top = mGlobalStack.top();
// usage page, local min/max, report size and count have to be defined at report
// definition.
if (!(top.usagePage.isSet() && top.logicalMin.isSet() && top.logicalMax.isSet()
&& top.reportSize.isSet() && top.reportCount.isSet())) {
LOG_E << "Report defined at " << i.offset
<< " does not have all mandatory fields set" << LOG_ENDL;
ret = false;
break;
}
if (top.reportSize.get(0) > 32) {
LOG_E << "Report defined at " << i.offset
<< " has unsupported report size(> 32 bit)" << LOG_ENDL;
ret = false;
break;
}
HidReport report(reportType, flag, top, mLocal);
mReport.push_back(report);
std::shared_ptr<HidTreeNode> node(new HidReportNode(mCurrent, report));
mCurrent->addChild(node);
break;
}
default:
LOG_E << "unknown main tag, " << i << LOG_ENDL;
ret = false;
}
// locals is cleared after any main tag according to HID spec
mLocal.clear();
return ret;
}
bool HidParser::parse(const unsigned char *begin, size_t size) {
std::vector<HidItem> hidItemVector = HidItem::tokenize(begin, size);
return parse(hidItemVector);
}
void HidParser::filterTree() {
if (mTree != nullptr) {
filterTree(mTree);
}
}
void HidParser::filterTree(std::shared_ptr<HidTreeNode> &node) {
if (node->isReportCollection()) {
std::shared_ptr<HidReportNode> reportNode =
std::static_pointer_cast<HidReportNode>(node->getChildren().front());
if (reportNode != nullptr) {
reportNode->collapse(node->getFullUsage());
node = reportNode;
}
} else {
for (auto &i : node->getChildren()) {
filterTree(i);
}
}
}
HidParser::DigestVector HidParser::generateDigest(
const std::unordered_set<unsigned int> &interestedUsage) {
DigestVector digestVector;
digest(&digestVector, mTree, interestedUsage);
return digestVector;
}
void HidParser::digest(HidParser::DigestVector *digestVector,
const std::shared_ptr<HidTreeNode> &node,
const std::unordered_set<unsigned int> &interestedUsage) {
if (digestVector == nullptr) {
return;
}
if (node->isUsageCollection()
&& interestedUsage.find(node->getFullUsage()) != interestedUsage.end()) {
// this collection contains the usage interested
ReportSetGroup reportSetGroup;
// one layer deep search
for (auto &i : node->getChildren()) {
// skip all nodes that is not a report node
if (i->getNodeType() != HidTreeNode::TYPE_REPORT) {
continue;
}
const HidReport &report =
std::static_pointer_cast<HidReportNode>(i)->getReport();
unsigned int id = report.getReportId();;
if (reportSetGroup.find(id) == reportSetGroup.end()) {
// create an id group if it is not created
reportSetGroup.emplace(id, ReportSet());
}
ReportSet &reportGroup = reportSetGroup[id];
switch(report.getType()) {
using namespace HidDef::MainTag;
case FEATURE:
reportGroup[REPORT_TYPE_FEATURE].push_back(report);
break;
case INPUT:
reportGroup[REPORT_TYPE_INPUT].push_back(report);
break;
case OUTPUT:
reportGroup[REPORT_TYPE_OUTPUT].push_back(report);
break;
}
}
ReportDigest digest = {
.fullUsage = node->getFullUsage(),
.packets = convertGroupToPacket(reportSetGroup)
};
digestVector->emplace_back(digest);
} else {
for (const auto &child : node->getChildren()) {
if (child->getNodeType() == HidTreeNode::TYPE_NORMAL) {
// only follow into collection nodes
digest(digestVector, child, interestedUsage);
}
}
}
}
std::vector<HidParser::ReportPacket> HidParser::convertGroupToPacket(
const HidParser::ReportSetGroup &group) {
std::vector<ReportPacket> packets;
const std::vector<int> types = {REPORT_TYPE_FEATURE, REPORT_TYPE_INPUT, REPORT_TYPE_OUTPUT};
for (const auto &setPair : group) {
unsigned int id = setPair.first;
for (auto type : types) {
const auto &reports = setPair.second[type]; // feature
// template
ReportPacket packet = {
.type = type,
.id = id,
.bitSize = 0
};
for (const auto &r : reports) {
auto logical = r.getLogicalRange();
auto physical = r.getPhysicalRange();
int64_t offset = physical.first - logical.first;
double scale = static_cast<double>((physical.second - physical.first))
/ (logical.second - logical.first);
scale *= r.getExponentValue();
ReportItem digest = {
.usage = r.getFullUsage(),
.id = id,
.minRaw = logical.first,
.maxRaw = logical.second,
.a = scale,
.b = offset,
.bitOffset = packet.bitSize,
.bitSize = r.getSize(),
.count = r.getCount(),
.unit = r.getUnit(),
};
packet.reports.push_back(digest);
packet.bitSize += digest.bitSize * digest.count;
}
if (!packet.reports.empty()) {
packets.push_back(std::move(packet));
}
}
}
return packets;
}
static std::string reportTypeToString(int reportType) {
switch (reportType) {
case HidParser::REPORT_TYPE_INPUT:
return "INPUT";
case HidParser::REPORT_TYPE_OUTPUT:
return "OUTPUT";
case HidParser::REPORT_TYPE_FEATURE:
return "FEATURE";
default:
return "INVALID REPORT";
}
}
std::ostream& operator<<(std::ostream &os, const HidParser::DigestVector &digests) {
for (const auto &i : digests) {
os << "Usage: 0x" << std::hex << i.fullUsage << std::dec
<< ", " << i.packets.size() << " report packet:" << LOG_ENDL;
for (const auto &packet : i.packets) {
os << reportTypeToString(packet.type) << " id: " << packet.id
<< " size: " << packet.bitSize
<< "b(" << packet.getByteSize() << "B), "
<< packet.reports.size() << " entries" << LOG_ENDL;
for (const auto &report : packet.reports) {
double min, max;
report.decode(report.mask(report.minRaw), &min);
report.decode(report.mask(report.maxRaw), &max);
os << " " << report.bitOffset << " size: " << report.bitSize
<< ", count: " << report.count
<< ", usage: " << std::hex << std::setfill('0') << std::setw(8)
<< report.usage << std::dec
<< ", min: " << report.minRaw << ", max: " << report.maxRaw
<< ", minDecoded: " << min
<< ", maxDecoded: " << max
<< ", a: " << report.a << ", b: " << report.b
<< std::hex
<< ", minRawHex: 0x" << report.mask(report.minRaw)
<< ", maxRawHex: 0x" << report.mask(report.maxRaw)
<< ", rawMasked: 0x" << report.rawMask()
<< std::dec << LOG_ENDL;
}
}
os << LOG_ENDL;
}
os << LOG_ENDL;
return os;
}
} // namespace HidUtil