1#!/usr/bin/env python
2# Copyright 2016 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Implements raw HID interface on Linux using SysFS and device files."""
17from __future__ import division
18
19import os
20import struct
21
22from pyu2f import errors
23from pyu2f.hid import base
24
25REPORT_DESCRIPTOR_KEY_MASK = 0xfc
26LONG_ITEM_ENCODING = 0xfe
27OUTPUT_ITEM = 0x90
28INPUT_ITEM = 0x80
29COLLECTION_ITEM = 0xa0
30REPORT_COUNT = 0x94
31REPORT_SIZE = 0x74
32USAGE_PAGE = 0x04
33USAGE = 0x08
34
35
36def GetValueLength(rd, pos):
37  """Get value length for a key in rd.
38
39  For a key at position pos in the Report Descriptor rd, return the length
40  of the associated value.  This supports both short and long format
41  values.
42
43  Args:
44    rd: Report Descriptor
45    pos: The position of the key in rd.
46
47  Returns:
48    (key_size, data_len) where key_size is the number of bytes occupied by
49    the key and data_len is the length of the value associated by the key.
50  """
51  rd = bytearray(rd)
52  key = rd[pos]
53  if key == LONG_ITEM_ENCODING:
54    # If the key is tagged as a long item (0xfe), then the format is
55    # [key (1 byte)] [data len (1 byte)] [item tag (1 byte)] [data (n # bytes)].
56    # Thus, the entire key record is 3 bytes long.
57    if pos + 1 < len(rd):
58      return (3, rd[pos + 1])
59    else:
60      raise errors.HidError('Malformed report descriptor')
61
62  else:
63    # If the key is tagged as a short item, then the item tag and data len are
64    # packed into one byte.  The format is thus:
65    # [tag (high 4 bits)] [type (2 bits)] [size code (2 bits)] [data (n bytes)].
66    # The size code specifies 1,2, or 4 bytes (0x03 means 4 bytes).
67    code = key & 0x03
68    if code <= 0x02:
69      return (1, code)
70    elif code == 0x03:
71      return (1, 4)
72
73  raise errors.HidError('Cannot happen')
74
75
76def ReadLsbBytes(rd, offset, value_size):
77  """Reads value_size bytes from rd at offset, least signifcant byte first."""
78
79  encoding = None
80  if value_size == 1:
81    encoding = '<B'
82  elif value_size == 2:
83    encoding = '<H'
84  elif value_size == 4:
85    encoding = '<L'
86  else:
87    raise errors.HidError('Invalid value size specified')
88
89  ret, = struct.unpack(encoding, rd[offset:offset + value_size])
90  return ret
91
92
93class NoReportCountFound(Exception):
94  pass
95
96
97def ParseReportDescriptor(rd, desc):
98  """Parse the binary report descriptor.
99
100  Parse the binary report descriptor into a DeviceDescriptor object.
101
102  Args:
103    rd: The binary report descriptor
104    desc: The DeviceDescriptor object to update with the results
105        from parsing the descriptor.
106
107  Returns:
108    None
109  """
110  rd = bytearray(rd)
111
112  pos = 0
113  report_count = None
114  report_size = None
115  usage_page = None
116  usage = None
117
118  while pos < len(rd):
119    key = rd[pos]
120
121    # First step, determine the value encoding (either long or short).
122    key_size, value_length = GetValueLength(rd, pos)
123
124    if key & REPORT_DESCRIPTOR_KEY_MASK == INPUT_ITEM:
125      if report_count and report_size:
126        byte_length = (report_count * report_size) // 8
127        desc.internal_max_in_report_len = max(
128            desc.internal_max_in_report_len, byte_length)
129        report_count = None
130        report_size = None
131    elif key & REPORT_DESCRIPTOR_KEY_MASK == OUTPUT_ITEM:
132      if report_count and report_size:
133        byte_length = (report_count * report_size) // 8
134        desc.internal_max_out_report_len = max(
135            desc.internal_max_out_report_len, byte_length)
136        report_count = None
137        report_size = None
138    elif key & REPORT_DESCRIPTOR_KEY_MASK == COLLECTION_ITEM:
139      if usage_page:
140        desc.usage_page = usage_page
141      if usage:
142        desc.usage = usage
143    elif key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_COUNT:
144      if len(rd) >= pos + 1 + value_length:
145        report_count = ReadLsbBytes(rd, pos + 1, value_length)
146    elif key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE:
147      if len(rd) >= pos + 1 + value_length:
148        report_size = ReadLsbBytes(rd, pos + 1, value_length)
149    elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE:
150      if len(rd) >= pos + 1 + value_length:
151        usage_page = ReadLsbBytes(rd, pos + 1, value_length)
152    elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE:
153      if len(rd) >= pos + 1 + value_length:
154        usage = ReadLsbBytes(rd, pos + 1, value_length)
155
156    pos += value_length + key_size
157  return desc
158
159
160def ParseUevent(uevent, desc):
161  lines = uevent.split(b'\n')
162  for line in lines:
163    line = line.strip()
164    if not line:
165      continue
166    k, v = line.split(b'=')
167    if k == b'HID_NAME':
168      desc.product_string = v.decode('utf8')
169    elif k == b'HID_ID':
170      _, vid, pid = v.split(b':')
171      desc.vendor_id = int(vid, 16)
172      desc.product_id = int(pid, 16)
173
174
175class LinuxHidDevice(base.HidDevice):
176  """Implementation of HID device for linux.
177
178  Implementation of HID device interface for linux that uses block
179  devices to interact with the device and sysfs to enumerate/discover
180  device metadata.
181  """
182
183  @staticmethod
184  def Enumerate():
185    for hidraw in os.listdir('/sys/class/hidraw'):
186      rd_path = (
187          os.path.join(
188              '/sys/class/hidraw', hidraw,
189              'device/report_descriptor'))
190      uevent_path = os.path.join('/sys/class/hidraw', hidraw, 'device/uevent')
191      rd_file = open(rd_path, 'rb')
192      uevent_file = open(uevent_path, 'rb')
193      desc = base.DeviceDescriptor()
194      desc.path = os.path.join('/dev/', hidraw)
195      ParseReportDescriptor(rd_file.read(), desc)
196      ParseUevent(uevent_file.read(), desc)
197
198      rd_file.close()
199      uevent_file.close()
200      yield desc.ToPublicDict()
201
202  def __init__(self, path):
203    base.HidDevice.__init__(self, path)
204    self.dev = os.open(path, os.O_RDWR)
205    self.desc = base.DeviceDescriptor()
206    self.desc.path = path
207    rd_file = open(os.path.join('/sys/class/hidraw',
208                                os.path.basename(path),
209                                'device/report_descriptor'), 'rb')
210    ParseReportDescriptor(rd_file.read(), self.desc)
211    rd_file.close()
212
213  def GetInReportDataLength(self):
214    """See base class."""
215    return self.desc.internal_max_in_report_len
216
217  def GetOutReportDataLength(self):
218    """See base class."""
219    return self.desc.internal_max_out_report_len
220
221  def Write(self, packet):
222    """See base class."""
223    out = bytearray([0] + packet)  # Prepend the zero-byte (report ID)
224    os.write(self.dev, out)
225
226  def Read(self):
227    """See base class."""
228    raw_in = os.read(self.dev, self.GetInReportDataLength())
229    decoded_in = list(bytearray(raw_in))
230    return decoded_in
231