aboutsummaryrefslogtreecommitdiff
path: root/nitrokey-sys/libnitrokey-v3.5/device.cc
diff options
context:
space:
mode:
Diffstat (limited to 'nitrokey-sys/libnitrokey-v3.5/device.cc')
-rw-r--r--nitrokey-sys/libnitrokey-v3.5/device.cc345
1 files changed, 345 insertions, 0 deletions
diff --git a/nitrokey-sys/libnitrokey-v3.5/device.cc b/nitrokey-sys/libnitrokey-v3.5/device.cc
new file mode 100644
index 0000000..bc42965
--- /dev/null
+++ b/nitrokey-sys/libnitrokey-v3.5/device.cc
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2015-2018 Nitrokey UG
+ *
+ * This file is part of libnitrokey.
+ *
+ * libnitrokey is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * libnitrokey is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0
+ */
+
+#include <chrono>
+#include <codecvt>
+#include <iostream>
+#include <locale>
+#include <thread>
+#include <cstddef>
+#include <stdexcept>
+#include "hidapi/hidapi.h"
+#include "libnitrokey/misc.h"
+#include "libnitrokey/device.h"
+#include "libnitrokey/log.h"
+#include <mutex>
+#include "DeviceCommunicationExceptions.h"
+#include "device.h"
+
+std::mutex mex_dev_com;
+
+using namespace nitrokey::device;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+using namespace std::chrono;
+
+const uint16_t nitrokey::device::NITROKEY_VID = 0x20a0;
+const uint16_t nitrokey::device::NITROKEY_PRO_PID = 0x4108;
+const uint16_t nitrokey::device::NITROKEY_STORAGE_PID = 0x4109;
+
+Option<DeviceModel> nitrokey::device::product_id_to_model(uint16_t product_id) {
+ switch (product_id) {
+ case NITROKEY_PRO_PID:
+ return DeviceModel::PRO;
+ case NITROKEY_STORAGE_PID:
+ return DeviceModel::STORAGE;
+ default:
+ return {};
+ }
+}
+
+std::atomic_int Device::instances_count{0};
+std::chrono::milliseconds Device::default_delay {0} ;
+
+std::ostream& nitrokey::device::operator<<(std::ostream& stream, DeviceModel model) {
+ switch (model) {
+ case DeviceModel::PRO:
+ stream << "Pro";
+ break;
+ case DeviceModel::STORAGE:
+ stream << "Storage";
+ break;
+ default:
+ stream << "Unknown";
+ break;
+ }
+ return stream;
+}
+
+Device::Device(const uint16_t vid, const uint16_t pid, const DeviceModel model,
+ const milliseconds send_receive_delay, const int retry_receiving_count,
+ const milliseconds retry_timeout)
+ :
+ last_command_status(0),
+ m_vid(vid),
+ m_pid(pid),
+ m_model(model),
+ m_retry_sending_count(1),
+ m_retry_receiving_count(retry_receiving_count),
+ m_retry_timeout(retry_timeout),
+ m_send_receive_delay(send_receive_delay),
+ mp_devhandle(nullptr)
+{
+ instances_count++;
+}
+
+bool Device::disconnect() {
+ //called in object's destructor
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ return _disconnect();
+}
+
+bool Device::_disconnect() {
+ LOG(std::string(__FUNCTION__) + std::string(m_model == DeviceModel::PRO ? "PRO" : "STORAGE"), Loglevel::DEBUG_L2);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+ if(mp_devhandle == nullptr) {
+ LOG(std::string("Disconnection: handle already freed: ") + std::to_string(mp_devhandle == nullptr) + " ("+m_path+")", Loglevel::DEBUG_L1);
+ return false;
+ }
+
+ hid_close(mp_devhandle);
+ mp_devhandle = nullptr;
+#ifndef __APPLE__
+ if (instances_count == 1){
+ LOG(std::string("Calling hid_exit"), Loglevel::DEBUG_L2);
+ hid_exit();
+ }
+#endif
+ return true;
+}
+
+bool Device::connect() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ return _connect();
+}
+
+bool Device::_connect() {
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+// hid_init(); // done automatically on hid_open
+ if (m_path.empty()){
+ mp_devhandle = hid_open(m_vid, m_pid, nullptr);
+ } else {
+ mp_devhandle = hid_open_path(m_path.c_str());
+ }
+ const bool success = mp_devhandle != nullptr;
+ LOG(std::string("Connection success: ") + std::to_string(success) + " ("+m_path+")", Loglevel::DEBUG_L1);
+ return success;
+}
+
+void Device::set_path(const std::string path){
+ m_path = path;
+}
+
+int Device::send(const void *packet) {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+ int send_feature_report = -1;
+
+ for (int i = 0; i < 3 && send_feature_report < 0; ++i) {
+ if (mp_devhandle == nullptr) {
+ LOG(std::string("Connection fail") , Loglevel::DEBUG_L2);
+ throw DeviceNotConnected("Attempted HID send on an invalid descriptor.");
+ }
+ send_feature_report = hid_send_feature_report(
+ mp_devhandle, (const unsigned char *)(packet), HID_REPORT_SIZE);
+ if (send_feature_report < 0) _reconnect();
+ //add thread sleep?
+ LOG(std::string("Sending attempt: ")+std::to_string(i+1) + " / 3" , Loglevel::DEBUG_L2);
+ }
+ return send_feature_report;
+}
+
+int Device::recv(void *packet) {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+ int status;
+ int retry_count = 0;
+
+ for (;;) {
+ if (mp_devhandle == nullptr){
+ LOG(std::string("Connection fail") , Loglevel::DEBUG_L2);
+ throw DeviceNotConnected("Attempted HID receive on an invalid descriptor.");
+ }
+
+ status = (hid_get_feature_report(mp_devhandle, (unsigned char *)(packet),
+ HID_REPORT_SIZE));
+
+ auto pwherr = hid_error(mp_devhandle);
+ std::wstring wherr = (pwherr != nullptr) ? pwherr : L"No error message";
+ std::string herr(wherr.begin(), wherr.end());
+ LOG(std::string("libhid error message: ") + herr,
+ Loglevel::DEBUG_L2);
+
+ if (status > 0) break; // success
+ if (retry_count++ >= m_retry_receiving_count) {
+ LOG(
+ "Maximum retry count reached: " + std::to_string(retry_count),
+ Loglevel::WARNING);
+ LOG(
+ std::string("Counter stats: ") + m_counters.get_as_string(),
+ Loglevel::DEBUG);
+ break;
+ }
+ _reconnect();
+ LOG("Retrying... " + std::to_string(retry_count),
+ Loglevel::DEBUG);
+ std::this_thread::sleep_for(m_retry_timeout);
+ }
+
+ return status;
+}
+
+std::vector<DeviceInfo> Device::enumerate(){
+ auto pInfo = hid_enumerate(NITROKEY_VID, 0);
+ auto pInfo_ = pInfo;
+ std::vector<DeviceInfo> res;
+ while (pInfo != nullptr){
+ auto deviceModel = product_id_to_model(pInfo->product_id);
+ if (deviceModel.has_value()) {
+ std::string path(pInfo->path);
+ std::wstring serialNumberW(pInfo->serial_number);
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ std::string serialNumber = converter.to_bytes(serialNumberW);
+ DeviceInfo info = { deviceModel.value(), path, serialNumber };
+ res.push_back(info);
+ }
+ pInfo = pInfo->next;
+ }
+
+ if (pInfo_ != nullptr){
+ hid_free_enumeration(pInfo_);
+ }
+
+ return res;
+}
+
+std::shared_ptr<Device> Device::create(DeviceModel model) {
+ switch (model) {
+ case DeviceModel::PRO:
+ return std::make_shared<Stick10>();
+ case DeviceModel::STORAGE:
+ return std::make_shared<Stick20>();
+ default:
+ return {};
+ }
+}
+
+bool Device::could_be_enumerated() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ if (mp_devhandle==nullptr){
+ return false;
+ }
+#ifndef __APPLE__
+ auto pInfo = hid_enumerate(m_vid, m_pid);
+ if (pInfo != nullptr){
+ hid_free_enumeration(pInfo);
+ return true;
+ }
+ return false;
+#else
+// alternative for OSX
+ unsigned char buf[1];
+ return hid_read_timeout(mp_devhandle, buf, sizeof(buf), 20) != -1;
+#endif
+}
+
+void Device::show_stats() {
+ auto s = m_counters.get_as_string();
+ LOG(s, Loglevel::DEBUG_L2);
+}
+
+void Device::_reconnect() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ ++m_counters.low_level_reconnect;
+ _disconnect();
+ _connect();
+}
+
+Device::~Device() {
+ show_stats();
+ disconnect();
+ instances_count--;
+}
+
+void Device::set_default_device_speed(int delay) {
+ default_delay = std::chrono::duration<int, std::milli>(delay);
+}
+
+
+void Device::set_receiving_delay(const std::chrono::milliseconds delay){
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ m_send_receive_delay = delay;
+}
+
+void Device::set_retry_delay(const std::chrono::milliseconds delay){
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ m_retry_timeout = delay;
+}
+
+Stick10::Stick10():
+ Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 5, 100ms)
+ {
+ setDefaultDelay();
+ }
+
+
+Stick20::Stick20():
+ Device(NITROKEY_VID, NITROKEY_STORAGE_PID, DeviceModel::STORAGE, 40ms, 55, 40ms)
+ {
+ setDefaultDelay();
+ }
+
+#include <sstream>
+#define p(x) ss << #x << " " << x << ", ";
+std::string Device::ErrorCounters::get_as_string() {
+ std::stringstream ss;
+ p(total_comm_runs);
+ p(communication_successful);
+ ss << "(";
+ p(command_successful_recv);
+ p(command_result_not_equal_0_recv);
+ ss << "), ";
+ p(sends_executed);
+ p(recv_executed);
+ p(successful_storage_commands);
+ p(total_retries);
+ ss << "(";
+ p(busy);
+ p(busy_progressbar);
+ p(CRC_other_than_awaited);
+ p(wrong_CRC);
+ ss << "), ";
+ p(low_level_reconnect);
+ p(sending_error);
+ p(receiving_error);
+ return ss.str();
+}
+
+void Device::setDefaultDelay() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+
+ auto count = default_delay.count();
+ if (count != 0){
+ LOG("Setting default delay to " + std::to_string(count), Loglevel::DEBUG_L2);
+ m_retry_timeout = default_delay;
+ m_send_receive_delay = default_delay;
+ }
+}
+
+#undef p