Electroneum
device_io_hid.cpp
Go to the documentation of this file.
1 // Copyright (c) 2017-2019, The Monero Project
2 //
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification, are
6 // permitted provided that the following conditions are met:
7 //
8 // 1. Redistributions of source code must retain the above copyright notice, this list of
9 // conditions and the following disclaimer.
10 //
11 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 // of conditions and the following disclaimer in the documentation and/or other
13 // materials provided with the distribution.
14 //
15 // 3. Neither the name of the copyright holder nor the names of its contributors may be
16 // used to endorse or promote products derived from this software without specific
17 // prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 #if defined(HAVE_HIDAPI)
30 
31 #include <boost/scope_exit.hpp>
32 #include "log.hpp"
33 #include "device_io_hid.hpp"
34 
35 namespace hw {
36  namespace io {
37 
38  #undef ELECTRONEUM_DEFAULT_LOG_CATEGORY
39  #define ELECTRONEUM_DEFAULT_LOG_CATEGORY "device.io"
40 
41  #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg);
42 
43  #define MAX_BLOCK 64
44 
45  static std::string safe_hid_error(hid_device *hwdev) {
46  if (hwdev) {
47  return std::string((char*)hid_error(hwdev));
48  }
49  return std::string("NULL device");
50  }
51 
52  static std::string safe_hid_path(const hid_device_info *hwdev_info) {
53  if (hwdev_info && hwdev_info->path) {
54  return std::string(hwdev_info->path);
55  }
56  return std::string("NULL path");
57  }
58 
59  device_io_hid::device_io_hid(unsigned short c, unsigned char t, unsigned int ps, unsigned int to) :
60  channel(c),
61  tag(t),
62  packet_size(ps),
63  timeout(to),
64  usb_vid(0),
65  usb_pid(0),
66  usb_device(NULL) {
67  }
68 
69  device_io_hid::device_io_hid() : device_io_hid(DEFAULT_CHANNEL, DEFAULT_TAG, DEFAULT_PACKET_SIZE, DEFAULT_TIMEOUT) {
70  }
71 
72  void device_io_hid::io_hid_log(int read, unsigned char* buffer, int block_len) {
73  if (hid_verbose) {
74  char strbuffer[1024];
75  hw::buffer_to_str(strbuffer, sizeof(strbuffer), (char*)buffer, block_len);
76  MDEBUG( "HID " << (read?"<":">") <<" : "<<strbuffer);
77  }
78  }
79 
80  void device_io_hid::init() {
81  int r;
82  r = hid_init();
83  ASSERT_X(r>=0, "Unable to init hidapi library. Error "+std::to_string(r)+": "+safe_hid_error(this->usb_device));
84  }
85 
86  void device_io_hid::connect(void *params) {
87  hid_conn_params *p = (struct hid_conn_params*)params;
88  if (!this->connect(p->vid, p->pid, p->interface_number, p->usage_page)) {
89  ASSERT_X(false, "No device found");
90  }
91  }
92 
93  void device_io_hid::connect(const std::vector<hid_conn_params> &hcpV) {
94  for (auto p: hcpV) {
95  if (this->connect(p.vid, p.pid, p.interface_number, p.usage_page)) {
96  return;
97  }
98  }
99  ASSERT_X(false, "No device found");
100  }
101 
102  hid_device_info *device_io_hid::find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) {
103  bool select_any = !interface_number && !usage_page;
104 
105  MDEBUG( "Looking for " <<
106  (select_any ? "any HID Device" : "HID Device with") <<
107  (interface_number ? (" interface_number " + std::to_string(interface_number.value())) : "") <<
108  ((interface_number && usage_page) ? " or" : "") <<
109  (usage_page ? (" usage_page " + std::to_string(usage_page.value())) : ""));
110 
111  hid_device_info *result = nullptr;
112  for (; devices_list != nullptr; devices_list = devices_list->next) {
113  BOOST_SCOPE_EXIT(&devices_list, &result) {
114  MDEBUG( (result == devices_list ? "SELECTED" : "SKIPPED ") <<
115  " HID Device" <<
116  " path " << safe_hid_path(devices_list) <<
117  " interface_number " << devices_list->interface_number <<
118  " usage_page " << devices_list->usage_page);
119  }
120  BOOST_SCOPE_EXIT_END
121 
122  if (result != nullptr) {
123  continue;
124  }
125 
126  if (select_any) {
127  result = devices_list;
128  } else if (interface_number && devices_list->interface_number == interface_number.value()) {
129  result = devices_list;
130  } else if (usage_page && devices_list->usage_page == usage_page.value()) {
131  result = devices_list;
132  }
133  }
134 
135  return result;
136  }
137 
138  hid_device *device_io_hid::connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) {
139  hid_device_info *hwdev_info_list;
140  hid_device *hwdev;
141 
142  this->disconnect();
143 
144  hwdev_info_list = hid_enumerate(vid, pid);
145  if (!hwdev_info_list) {
146  MDEBUG("Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device));
147  return NULL;
148  }
149  hwdev = NULL;
150  if (hid_device_info *device = find_device(hwdev_info_list, interface_number, usage_page)) {
151  hwdev = hid_open_path(device->path);
152  }
153  hid_free_enumeration(hwdev_info_list);
154  ASSERT_X(hwdev, "Unable to open device "+std::to_string(pid)+":"+std::to_string(vid));
155  this->usb_vid = vid;
156  this->usb_pid = pid;
157  this->usb_device = hwdev;
158  return hwdev;
159  }
160 
161 
162  bool device_io_hid::connected() const {
163  return this->usb_device != NULL;
164  }
165 
166  int device_io_hid::exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input) {
167  unsigned char buffer[400];
168  unsigned char padding_buffer[MAX_BLOCK+1];
169  unsigned int result;
170  int hid_ret;
171  unsigned int sw_offset;
172  unsigned int remaining;
173  unsigned int offset = 0;
174 
175  ASSERT_X(this->usb_device,"No device opened");
176 
177  //Split command in several HID packet
178  memset(buffer, 0, sizeof(buffer));
179  result = this->wrapCommand(command, cmd_len, buffer, sizeof(buffer));
180  remaining = result;
181 
182  while (remaining > 0) {
183  int block_size = (remaining > MAX_BLOCK ? MAX_BLOCK : remaining);
184  memset(padding_buffer, 0, sizeof(padding_buffer));
185  memcpy(padding_buffer+1, buffer + offset, block_size);
186  io_hid_log(0, padding_buffer, block_size+1);
187  hid_ret = hid_write(this->usb_device, padding_buffer, block_size+1);
188  ASSERT_X(hid_ret>=0, "Unable to send hidapi command. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
189  offset += block_size;
190  remaining -= block_size;
191  }
192 
193  //get first response
194  memset(buffer, 0, sizeof(buffer));
195  if (!user_input) {
196  hid_ret = hid_read_timeout(this->usb_device, buffer, MAX_BLOCK, this->timeout);
197  } else {
198  hid_ret = hid_read(this->usb_device, buffer, MAX_BLOCK);
199  }
200  ASSERT_X(hid_ret>=0, "Unable to read hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
201  result = (unsigned int)hid_ret;
202  io_hid_log(1, buffer, result);
203  offset = MAX_BLOCK;
204  //parse first response and get others if any
205  for (;;) {
206  result = this->unwrapReponse(buffer, offset, response, max_resp_len);
207  if (result != 0) {
208  break;
209  }
210  hid_ret = hid_read_timeout(this->usb_device, buffer + offset, MAX_BLOCK, this->timeout);
211  ASSERT_X(hid_ret>=0, "Unable to receive hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
212  result = (unsigned int)hid_ret;
213  io_hid_log(1, buffer + offset, result);
214  offset += MAX_BLOCK;
215  }
216  return result;
217  }
218 
219  void device_io_hid::disconnect(void) {
220  if (this->usb_device) {
221  hid_close(this->usb_device);
222  }
223  this->usb_vid = 0;
224  this->usb_pid = 0;
225  this->usb_device = NULL;
226  }
227 
228  void device_io_hid::release() {
229  /* Do not exit, as the lib context is global*/
230  //hid_exit();
231  }
232 
233  unsigned int device_io_hid::wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len) {
234  unsigned int sequence_idx = 0;
235  unsigned int offset = 0;
236  unsigned int offset_out = 0;
237  unsigned int block_size;
238 
239  ASSERT_X(this->packet_size >= 3, "Invalid Packet size: "+std::to_string(this->packet_size)) ;
240  ASSERT_X(out_len >= 7, "out_len too short: "+std::to_string(out_len));
241 
242  out_len -= 7;
243  out[offset_out++] = ((this->channel >> 8) & 0xff);
244  out[offset_out++] = (this->channel & 0xff);
245  out[offset_out++] = this->tag;
246  out[offset_out++] = ((sequence_idx >> 8) & 0xff);
247  out[offset_out++] = (sequence_idx & 0xff);
248  sequence_idx++;
249  out[offset_out++] = ((command_len >> 8) & 0xff);
250  out[offset_out++] = (command_len & 0xff);
251  block_size = (command_len > this->packet_size - 7 ? this->packet_size - 7 : command_len);
252  ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len));
253  out_len -= block_size;
254  memcpy(out + offset_out, command + offset, block_size);
255  offset_out += block_size;
256  offset += block_size;
257  while (offset != command_len) {
258  ASSERT_X(out_len >= 5, "out_len too short: "+std::to_string(out_len));
259  out_len -= 5;
260  out[offset_out++] = ((this->channel >> 8) & 0xff);
261  out[offset_out++] = (this->channel & 0xff);
262  out[offset_out++] = this->tag;
263  out[offset_out++] = ((sequence_idx >> 8) & 0xff);
264  out[offset_out++] = (sequence_idx & 0xff);
265  sequence_idx++;
266  block_size = ((command_len - offset) > this->packet_size - 5 ? this->packet_size - 5 : command_len - offset);
267  ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len));
268  out_len -= block_size;
269  memcpy(out + offset_out, command + offset, block_size);
270  offset_out += block_size;
271  offset += block_size;
272  }
273  while ((offset_out % this->packet_size) != 0) {
274  ASSERT_X(out_len >= 1, "out_len too short: "+std::to_string(out_len));
275  out_len--;
276  out[offset_out++] = 0;
277  }
278  return offset_out;
279  }
280 
281  /*
282  * return 0 if more data are needed
283  * >0 if response is fully available
284  */
285  unsigned int device_io_hid::unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len) {
286  unsigned int sequence_idx = 0;
287  unsigned int offset = 0;
288  unsigned int offset_out = 0;
289  unsigned int response_len;
290  unsigned int block_size;
291  unsigned int val;
292 
293  //end?
294  if ((data == NULL) || (data_len < 7 + 5)) {
295  return 0;
296  }
297 
298  //check hid header
299  val = (data[offset]<<8) + data[offset+1];
300  offset += 2;
301  ASSERT_X(val == this->channel, "Wrong Channel");
302  val = data[offset];
303  offset++;
304  ASSERT_X(val == this->tag, "Wrong TAG");
305  val = (data[offset]<<8) + data[offset+1];
306  offset += 2;
307  ASSERT_X(val == sequence_idx, "Wrong sequence_idx");
308 
309  //fetch
310  response_len = (data[offset++] << 8);
311  response_len |= data[offset++];
312  ASSERT_X(out_len >= response_len, "Out Buffer too short");
313  if (data_len < (7 + response_len)) {
314  return 0;
315  }
316  block_size = (response_len > (this->packet_size - 7) ? this->packet_size - 7 : response_len);
317  memcpy(out + offset_out, data + offset, block_size);
318  offset += block_size;
319  offset_out += block_size;
320  while (offset_out != response_len) {
321  sequence_idx++;
322  if (offset == data_len) {
323  return 0;
324  }
325  val = (data[offset]<<8) + data[offset+1];
326  offset += 2;
327  ASSERT_X(val == this->channel, "Wrong Channel");
328  val = data[offset];
329  offset++;
330  ASSERT_X(val == this->tag, "Wrong TAG");
331  val = (data[offset]<<8) + data[offset+1];
332  offset += 2;
333  ASSERT_X(val == sequence_idx, "Wrong sequence_idx");
334 
335  block_size = ((response_len - offset_out) > this->packet_size - 5 ? this->packet_size - 5 : response_len - offset_out);
336  if (block_size > (data_len - offset)) {
337  return 0;
338  }
339  memcpy(out + offset_out, data + offset, block_size);
340  offset += block_size;
341  offset_out += block_size;
342  }
343  return offset_out;
344  }
345 
346 
347  }
348 }
349 
350 #endif //#if defined(HAVE_HIDAPI)
::std::string string
Definition: gtest-port.h:1097
epee::misc_utils::struct_init< response_t > response
STL namespace.
void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len)
Definition: log.cpp:38
#define MDEBUG(x)
Definition: misc_log_ex.h:76
Definition: device.cpp:38
const GenericPointer< typename T::ValueType > T2 value
Definition: pointer.h:1225
void * memcpy(void *a, const void *b, size_t c)
std::string to_string(t_connection_type type)