xref: /libbtbb/python/pcaptools/btaptap (revision 48fe012ac445727d6a0bf0074d6f218d5616862f)
1*48fe012aSMike Ryan#!/usr/bin/env python3
2e25b118aSDominic Spill#
3e25b118aSDominic Spill# Copyright 2009 Joshua Wright, Michael Ossmann
4e25b118aSDominic Spill#
5e25b118aSDominic Spill# This file is part of gr-bluetooth
6e25b118aSDominic Spill#
7e25b118aSDominic Spill# gr-bluetooth is free software; you can redistribute it and/or modify
8e25b118aSDominic Spill# it under the terms of the GNU General Public License as published by
9e25b118aSDominic Spill# the Free Software Foundation; either version 2, or (at your option)
10e25b118aSDominic Spill# any later version.
11e25b118aSDominic Spill#
12e25b118aSDominic Spill# gr-bluetooth is distributed in the hope that it will be useful,
13e25b118aSDominic Spill# but WITHOUT ANY WARRANTY; without even the implied warranty of
14e25b118aSDominic Spill# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15e25b118aSDominic Spill# GNU General Public License for more details.
16e25b118aSDominic Spill#
17e25b118aSDominic Spill# You should have received a copy of the GNU General Public License
18e25b118aSDominic Spill# along with gr-bluetooth; see the file COPYING.  If not, write to
19e25b118aSDominic Spill# the Free Software Foundation, Inc., 51 Franklin Street,
20e25b118aSDominic Spill# Boston, MA 02110-1301, USA.
21e25b118aSDominic Spill
22e25b118aSDominic Spillimport sys
23e25b118aSDominic Spillimport struct
24e25b118aSDominic Spillimport time
25e25b118aSDominic Spillfrom pcapdump.pcapdump import *
26e25b118aSDominic Spill
27e25b118aSDominic SpillDLT_EN10MB = 1
28e25b118aSDominic SpillDLT_BLUETOOTH_HCI_H4 = 187
295cf4c9cbSDominic SpillELLISYS_CSV_HDR = "\"Depth\",\"Time\",\"Name\",\"Data\"\x0d\x0a"
305cf4c9cbSDominic SpillELLISYS_HID_INPUT = "HID Input 1"
31e25b118aSDominic SpillUSBHID_MAP = {
32e25b118aSDominic Spill    0x04 : "a",
33e25b118aSDominic Spill    0x05 : "b",
34e25b118aSDominic Spill    0x06 : "c",
35e25b118aSDominic Spill    0x07 : "d",
36e25b118aSDominic Spill    0x08 : "e",
37e25b118aSDominic Spill    0x09 : "f",
38e25b118aSDominic Spill    0x0A : "g",
39e25b118aSDominic Spill    0x0B : "h",
40e25b118aSDominic Spill    0x0C : "i",
41e25b118aSDominic Spill    0x0D : "j",
42e25b118aSDominic Spill    0x0E : "k",
43e25b118aSDominic Spill    0x0F : "l",
44e25b118aSDominic Spill    0x10 : "m",
45e25b118aSDominic Spill    0x11 : "n",
46e25b118aSDominic Spill    0x12 : "o",
47e25b118aSDominic Spill    0x13 : "p",
48e25b118aSDominic Spill    0x14 : "q",
49e25b118aSDominic Spill    0x15 : "r",
50e25b118aSDominic Spill    0x16 : "s",
51e25b118aSDominic Spill    0x17 : "t",
52e25b118aSDominic Spill    0x18 : "u",
53e25b118aSDominic Spill    0x19 : "v",
54e25b118aSDominic Spill    0x1A : "w",
55e25b118aSDominic Spill    0x1B : "x",
56e25b118aSDominic Spill    0x1C : "y",
57e25b118aSDominic Spill    0x1D : "z",
58e25b118aSDominic Spill    0x1E : "1",
59e25b118aSDominic Spill    0x1F : "2",
60e25b118aSDominic Spill    0x20 : "3",
61e25b118aSDominic Spill    0x21 : "4",
62e25b118aSDominic Spill    0x22 : "5",
63e25b118aSDominic Spill    0x23 : "6",
64e25b118aSDominic Spill    0x24 : "7",
65e25b118aSDominic Spill    0x25 : "8",
66e25b118aSDominic Spill    0x26 : "9",
67e25b118aSDominic Spill    0x27 : "0",
68e25b118aSDominic Spill    0x28 : "[Return]\n",
69e25b118aSDominic Spill    0x29 : "[Esc]",
70e25b118aSDominic Spill    0x2A : "[Backspace]",
71e25b118aSDominic Spill    0x2B : "[Tab]\t",
72e25b118aSDominic Spill    0x2C : " ",
73e25b118aSDominic Spill    0x2D : "-",
74e25b118aSDominic Spill    0x2E : "=",
75e25b118aSDominic Spill    0x2F : "[",
76e25b118aSDominic Spill    0x30 : "]",
77e25b118aSDominic Spill    0x31 : "\\",
78e25b118aSDominic Spill    0x32 : "#",
79e25b118aSDominic Spill    0x33 : ";",
80e25b118aSDominic Spill    0x34 : "'",
81e25b118aSDominic Spill    0x35 : "[Grave Accent]",
82e25b118aSDominic Spill    0x36 : ",",
83e25b118aSDominic Spill    0x37 : ".",
84e25b118aSDominic Spill    0x38 : "/",
85e25b118aSDominic Spill    0x39 : "[Caps Lock]",
86e25b118aSDominic Spill    0x3A : "[F1]",
87e25b118aSDominic Spill    0x3B : "[F2]",
88e25b118aSDominic Spill    0x3C : "[F3]",
89e25b118aSDominic Spill    0x3D : "[F4]",
90e25b118aSDominic Spill    0x3E : "[F5]",
91e25b118aSDominic Spill    0x3F : "[F6]",
92e25b118aSDominic Spill    0x40 : "[F7]",
93e25b118aSDominic Spill    0x41 : "[F8]",
94e25b118aSDominic Spill    0x42 : "[F9]",
95e25b118aSDominic Spill    0x43 : "[F10]",
96e25b118aSDominic Spill    0x44 : "[F11]",
97e25b118aSDominic Spill    0x45 : "[F12]",
98e25b118aSDominic Spill    0x46 : "[PrintScreen]",
99e25b118aSDominic Spill    0x47 : "[Scroll]",
100e25b118aSDominic Spill    0x48 : "[Pause]",
101e25b118aSDominic Spill    0x49 : "[Insert]",
102e25b118aSDominic Spill    0x4A : "[Home]",
103e25b118aSDominic Spill    0x4B : "[PageUp]",
104e25b118aSDominic Spill    0x4C : "[Delete]",
105e25b118aSDominic Spill    0x4D : "[End]",
106e25b118aSDominic Spill    0x4E : "[PageDown]",
107e25b118aSDominic Spill    0x4F : "[RightArrow]",
108e25b118aSDominic Spill    0x50 : "[LeftArrow]",
109e25b118aSDominic Spill    0x51 : "[DownArrow]",
110e25b118aSDominic Spill    0x52 : "[UpArrow]",
111e25b118aSDominic Spill    0x53 : "[Keypad Num Lock and Clear]",
112e25b118aSDominic Spill    0x54 : "[Keypad /]",
113e25b118aSDominic Spill    0x55 : "[Keypad *]",
114e25b118aSDominic Spill    0x56 : "[Keypad -]",
115e25b118aSDominic Spill    0x57 : "[Keypad +]",
116e25b118aSDominic Spill    0x58 : "[Keypad Enter]\n",
117e25b118aSDominic Spill    0x59 : "[Keypad 1 and End]",
118e25b118aSDominic Spill    0x5A : "[Keypad 2 and Down Arrow]",
119e25b118aSDominic Spill    0x5B : "[Keypad 3 and PageDn]",
120e25b118aSDominic Spill    0x5C : "[Keypad 4 and Left Arrow]",
121e25b118aSDominic Spill    0x5D : "[Keypad 5]",
122e25b118aSDominic Spill    0x5E : "[Keypad 6 and Right Arrow]",
123e25b118aSDominic Spill    0x5F : "[Keypad 7 and Home]",
124e25b118aSDominic Spill    0x60 : "[Keypad 8 and Up Arrow]",
125e25b118aSDominic Spill    0x61 : "[Keypad 9 and PageUp]",
126e25b118aSDominic Spill    0x62 : "[Keypad 0 and Insert]",
127e25b118aSDominic Spill    0x63 : "[Keypad . and Delete]",
128e25b118aSDominic Spill    0x64 : "\\",
129e25b118aSDominic Spill    0x65 : "[WinKey]",
130e25b118aSDominic Spill    0x66 : "[Power9]",
131e25b118aSDominic Spill    0x67 : "[Keypad =]",
132e25b118aSDominic Spill    0x68 : "[F13]",
133e25b118aSDominic Spill    0x69 : "[F14]",
134e25b118aSDominic Spill    0x6A : "[F15]",
135e25b118aSDominic Spill    0x6B : "[F16]",
136e25b118aSDominic Spill    0x6C : "[F17]",
137e25b118aSDominic Spill    0x6D : "[F18]",
138e25b118aSDominic Spill    0x6E : "[F19]",
139e25b118aSDominic Spill    0x6F : "[F20]",
140e25b118aSDominic Spill    0x70 : "[F21]",
141e25b118aSDominic Spill    0x71 : "[F22]",
142e25b118aSDominic Spill    0x72 : "[F23]",
143e25b118aSDominic Spill    0x73 : "[F24]",
144e25b118aSDominic Spill    0x74 : "[Execute]",
145e25b118aSDominic Spill    0x75 : "[Help]",
146e25b118aSDominic Spill    0x76 : "[Menu]",
147e25b118aSDominic Spill    0x77 : "[Select]",
148e25b118aSDominic Spill    0x78 : "[Stop]",
149e25b118aSDominic Spill    0x79 : "[Again]",
150e25b118aSDominic Spill    0x7A : "[Undo]",
151e25b118aSDominic Spill    0x7B : "[Cut]",
152e25b118aSDominic Spill    0x7C : "[Copy]",
153e25b118aSDominic Spill    0x7D : "[Paste]",
154e25b118aSDominic Spill    0x7E : "[Find]",
155e25b118aSDominic Spill    0x7F : "[Mute]",
156e25b118aSDominic Spill    0x80 : "[Volume Up]",
157e25b118aSDominic Spill    0x81 : "[Volume Down]",
158e25b118aSDominic Spill    0x82 : "[Locking Caps Lock]",
159e25b118aSDominic Spill    0x83 : "[Locking Num Lock]",
160e25b118aSDominic Spill    0x84 : "[Locking Scroll Lock]",
161e25b118aSDominic Spill    0x85 : "[Keypad Comma]",
162e25b118aSDominic Spill    0x86 : "[Keypad Equal]",
163e25b118aSDominic Spill    0x87 : "[International1]",
164e25b118aSDominic Spill    0x88 : "[International2]",
165e25b118aSDominic Spill    0x89 : "[International3]",
166e25b118aSDominic Spill    0x8A : "[International4]",
167e25b118aSDominic Spill    0x8B : "[International5]",
168e25b118aSDominic Spill    0x8C : "[International6]",
169e25b118aSDominic Spill    0x8D : "[International7]",
170e25b118aSDominic Spill    0x8E : "[International8]",
171e25b118aSDominic Spill    0x8F : "[International9]",
172e25b118aSDominic Spill    0x90 : "[LANG1]",
173e25b118aSDominic Spill    0x91 : "[LANG2]",
174e25b118aSDominic Spill    0x92 : "[LANG3]",
175e25b118aSDominic Spill    0x93 : "[LANG4]",
176e25b118aSDominic Spill    0x94 : "[LANG5]",
177e25b118aSDominic Spill    0x95 : "[LANG6]",
178e25b118aSDominic Spill    0x96 : "[LANG7]",
179e25b118aSDominic Spill    0x97 : "[LANG8]",
180e25b118aSDominic Spill    0x98 : "[LANG9]",
181e25b118aSDominic Spill    0x99 : "[Alternate Erase]",
182e25b118aSDominic Spill    0x9A : "[SysReq/Attention]",
183e25b118aSDominic Spill    0x9B : "[Cancel]",
184e25b118aSDominic Spill    0x9C : "[Clear]",
185e25b118aSDominic Spill    0x9D : "[Prior]",
186e25b118aSDominic Spill    0x9E : "[Return]\n",
187e25b118aSDominic Spill    0x9F : "[Separator]",
188e25b118aSDominic Spill    0xA0 : "[Out]",
189e25b118aSDominic Spill    0xA1 : "[Oper]",
190e25b118aSDominic Spill    0xA2 : "[Clear/Again]",
191e25b118aSDominic Spill    0xA3 : "[CrSel/Props]",
192e25b118aSDominic Spill    0xA4 : "[ExSel]",
193e25b118aSDominic Spill    0xB0 : "[Keypad 00]",
194e25b118aSDominic Spill    0xB1 : "[Keypad 000]",
195e25b118aSDominic Spill    0xB2 : "[Thousands Separator]",
196e25b118aSDominic Spill    0xB3 : "[Decimal Separator]",
197e25b118aSDominic Spill    0xB4 : "[Currency Unit]",
198e25b118aSDominic Spill    0xB5 : "[Currency Sub-unit]",
199e25b118aSDominic Spill    0xB6 : "[Keypad (]",
200e25b118aSDominic Spill    0xB7 : "[Keypad )]",
201e25b118aSDominic Spill    0xB8 : "[Keypad {]",
202e25b118aSDominic Spill    0xB9 : "[Keypad }]",
203e25b118aSDominic Spill    0xBA : "[Keypad Tab]\t",
204e25b118aSDominic Spill    0xBB : "[Keypad Backspace]",
205e25b118aSDominic Spill    0xBC : "[Keypad A]",
206e25b118aSDominic Spill    0xBD : "[Keypad B]",
207e25b118aSDominic Spill    0xBE : "[Keypad C]",
208e25b118aSDominic Spill    0xBF : "[Keypad D]",
209e25b118aSDominic Spill    0xC0 : "[Keypad E]",
210e25b118aSDominic Spill    0xC1 : "[Keypad F]",
211e25b118aSDominic Spill    0xC2 : "[Keypad XOR]",
212e25b118aSDominic Spill    0xC3 : "[Keypad ^]",
213e25b118aSDominic Spill    0xC4 : "[Keypad %]",
214e25b118aSDominic Spill    0xC5 : "[Keypad <]",
215e25b118aSDominic Spill    0xC6 : "[Keypad >]",
216e25b118aSDominic Spill    0xC7 : "[Keypad &]",
217e25b118aSDominic Spill    0xC8 : "[Keypad &&]",
218e25b118aSDominic Spill    0xC9 : "[Keypad |]",
219e25b118aSDominic Spill    0xCA : "[Keypad ||]",
220e25b118aSDominic Spill    0xCB : "[Keypad :]",
221e25b118aSDominic Spill    0xCC : "[Keypad #]",
222e25b118aSDominic Spill    0xCD : "[Keypad Space]",
223e25b118aSDominic Spill    0xCE : "[Keypad @]",
224e25b118aSDominic Spill    0xCF : "[Keypad !]",
225e25b118aSDominic Spill    0xD0 : "[Keypad Memory Store]",
226e25b118aSDominic Spill    0xD1 : "[Keypad Memory Recall]",
227e25b118aSDominic Spill    0xD2 : "[Keypad Memory Clear]",
228e25b118aSDominic Spill    0xD3 : "[Keypad Memory Add]",
229e25b118aSDominic Spill    0xD4 : "[Keypad Memory Subtract]",
230e25b118aSDominic Spill    0xD5 : "[Keypad Memory Multiply]",
231e25b118aSDominic Spill    0xD6 : "[Keypad Memory Divide]",
232e25b118aSDominic Spill    0xD7 : "[Keypad +/-]",
233e25b118aSDominic Spill    0xD8 : "[Keypad Clear]",
234e25b118aSDominic Spill    0xD9 : "[Keypad Clear Entry]",
235e25b118aSDominic Spill    0xDA : "[Keypad Binary]",
236e25b118aSDominic Spill    0xDB : "[Keypad Octal]",
237e25b118aSDominic Spill    0xDC : "[Keypad Decimal]",
238e25b118aSDominic Spill    0xDD : "[Keypad Hexadecimal]",
239e25b118aSDominic Spill    0xE0 : "[LeftControl]",
240e25b118aSDominic Spill    0xE1 : "[LeftShift]",
241e25b118aSDominic Spill    0xE2 : "[LeftAlt]",
242e25b118aSDominic Spill    0xE3 : "[LeftWinKey]",
243e25b118aSDominic Spill    0xE4 : "[RightControl]",
244e25b118aSDominic Spill    0xE5 : "[RightShift]",
245e25b118aSDominic Spill    0xE6 : "[RightAlt]",
246e25b118aSDominic Spill    0xE7 : "[RightWinKey]"
247e25b118aSDominic Spill}
248e25b118aSDominic Spill
249e25b118aSDominic Spill# some keycodes represent different things when Shift is held down
250e25b118aSDominic SpillUSBHID_SHIFT_MAP = {
251e25b118aSDominic Spill    0x04 : "A",
252e25b118aSDominic Spill    0x05 : "B",
253e25b118aSDominic Spill    0x06 : "C",
254e25b118aSDominic Spill    0x07 : "D",
255e25b118aSDominic Spill    0x08 : "E",
256e25b118aSDominic Spill    0x09 : "F",
257e25b118aSDominic Spill    0x0A : "G",
258e25b118aSDominic Spill    0x0B : "H",
259e25b118aSDominic Spill    0x0C : "I",
260e25b118aSDominic Spill    0x0D : "J",
261e25b118aSDominic Spill    0x0E : "K",
262e25b118aSDominic Spill    0x0F : "L",
263e25b118aSDominic Spill    0x10 : "M",
264e25b118aSDominic Spill    0x11 : "N",
265e25b118aSDominic Spill    0x12 : "O",
266e25b118aSDominic Spill    0x13 : "P",
267e25b118aSDominic Spill    0x14 : "Q",
268e25b118aSDominic Spill    0x15 : "R",
269e25b118aSDominic Spill    0x16 : "S",
270e25b118aSDominic Spill    0x17 : "T",
271e25b118aSDominic Spill    0x18 : "U",
272e25b118aSDominic Spill    0x19 : "V",
273e25b118aSDominic Spill    0x1A : "W",
274e25b118aSDominic Spill    0x1B : "X",
275e25b118aSDominic Spill    0x1C : "Y",
276e25b118aSDominic Spill    0x1D : "Z",
277e25b118aSDominic Spill    0x1E : "!",
278e25b118aSDominic Spill    0x1F : "@",
279e25b118aSDominic Spill    0x20 : "#",
280e25b118aSDominic Spill    0x21 : "$",
281e25b118aSDominic Spill    0x22 : "%",
282e25b118aSDominic Spill    0x23 : "^",
283e25b118aSDominic Spill    0x24 : "&",
284e25b118aSDominic Spill    0x25 : "*",
285e25b118aSDominic Spill    0x26 : "(",
286e25b118aSDominic Spill    0x27 : ")",
287e25b118aSDominic Spill    0x2D : "_",
288e25b118aSDominic Spill    0x2E : "+",
289e25b118aSDominic Spill    0x2F : "{",
290e25b118aSDominic Spill    0x30 : "}",
291e25b118aSDominic Spill    0x31 : "|",
292e25b118aSDominic Spill    0x32 : "~",
293e25b118aSDominic Spill    0x33 : ":",
294e25b118aSDominic Spill    0x34 : "\"",
295e25b118aSDominic Spill    0x35 : "~",
296e25b118aSDominic Spill    0x36 : "<",
297e25b118aSDominic Spill    0x37 : ">",
298e25b118aSDominic Spill    0x38 : "?",
299e25b118aSDominic Spill    0x64 : "|"
300e25b118aSDominic Spill}
301e25b118aSDominic Spill
302e25b118aSDominic Spill# global variable to track currently depressed keys
303e25b118aSDominic Spillactive_keys = []
304e25b118aSDominic Spill
305e25b118aSDominic Spilldef hid2ascii(scancode, shift):
306e25b118aSDominic Spill    '''
307e25b118aSDominic Spill    Convert the specified scancode value to the ASCII equivalent using the
308e25b118aSDominic Spill    USBHID_MAP list.
309e25b118aSDominic Spill    '''
310e25b118aSDominic Spill    if shift:
311e25b118aSDominic Spill        try:
312e25b118aSDominic Spill            code = USBHID_SHIFT_MAP[scancode]
313e25b118aSDominic Spill            return code
314e25b118aSDominic Spill        except KeyError:
315e25b118aSDominic Spill            pass
316e25b118aSDominic Spill    try:
317e25b118aSDominic Spill        code = USBHID_MAP[scancode]
318e25b118aSDominic Spill    except KeyError:
319e25b118aSDominic Spill        return "[Reserved]"
320e25b118aSDominic Spill    return code
321e25b118aSDominic Spill
322e25b118aSDominic Spilldef usage():
323*48fe012aSMike Ryan    print("Usage: btaptap [-r pcapfile.pcap | -e ellisysfile.csv] [-c count] [-h]\n", file=sys.stderr)
324e25b118aSDominic Spill    sys.exit(0)
325e25b118aSDominic Spill
326e25b118aSDominic Spilldef parse_l2cap_keydata(l2cappkt):
327e25b118aSDominic Spill    global active_keys
328e25b118aSDominic Spill
329e25b118aSDominic Spill    TRANS_HDR_IN_DATA = 0xA1
330e25b118aSDominic Spill    REPORT_ID_KEYBOARD = 0x01
331e25b118aSDominic Spill    CTRL = 1
332e25b118aSDominic Spill    SHIFT = 2
333e25b118aSDominic Spill    ALT = 4
334e25b118aSDominic Spill    GUI = 8
335e25b118aSDominic Spill
336e25b118aSDominic Spill    # Keyboard keystrokes are only seen in L2CAP packets at least 10 bytes long
337e25b118aSDominic Spill    l2clen = (ord(l2cappkt[1]) << 8) | ord(l2cappkt[0])
338e25b118aSDominic Spill    if l2clen < 10:
339e25b118aSDominic Spill        return
340e25b118aSDominic Spill
341e25b118aSDominic Spill    # Keyboard keystrokes are only carried by Channel ID >= 0x40
342e25b118aSDominic Spill    cid = (ord(l2cappkt[3]) << 8) | ord(l2cappkt[2])
343e25b118aSDominic Spill    if cid < 0x40:
344e25b118aSDominic Spill        return
345e25b118aSDominic Spill    # Ideally we would check for the particular CID for the HID_INTERRUPT
346e25b118aSDominic Spill    # channel, but we don't handle the negotiation (and may not have even
347e25b118aSDominic Spill    # seen it).
348e25b118aSDominic Spill
349e25b118aSDominic Spill    # Transaction Header should indicate input data
350e25b118aSDominic Spill    thdr = ord(l2cappkt[4])
351e25b118aSDominic Spill    if thdr != TRANS_HDR_IN_DATA:
352e25b118aSDominic Spill        return
353e25b118aSDominic Spill
354e25b118aSDominic Spill    # Report ID should indicate this is a keyboard
355e25b118aSDominic Spill    rid = ord(l2cappkt[5])
356e25b118aSDominic Spill    if rid != REPORT_ID_KEYBOARD:
357e25b118aSDominic Spill        return
358e25b118aSDominic Spill
359e25b118aSDominic Spill    # This byte describes modifier key status (one bit per key)
360e25b118aSDominic Spill    mod = ord(l2cappkt[6])
361e25b118aSDominic Spill
362e25b118aSDominic Spill    # We don't care whether left or right modifier keys are pressed, so we
363e25b118aSDominic Spill    # combine the status bits.
364e25b118aSDominic Spill    leftmod = mod & 0x0f
365e25b118aSDominic Spill    rightmod = (mod & 0xf0) >> 4
366e25b118aSDominic Spill    mod = leftmod | rightmod
367e25b118aSDominic Spill
368e25b118aSDominic Spill    # up to six keys can be reported at once
369e25b118aSDominic Spill    keycodes = []
3705cf4c9cbSDominic Spill    #for byte in range(8,14):
3715cf4c9cbSDominic Spill    for byte in range(8,11):
372e25b118aSDominic Spill        keystroke = ord(l2cappkt[byte])
373e25b118aSDominic Spill        if keystroke != 0x00:
374e25b118aSDominic Spill            keycodes.append(keystroke)
375e25b118aSDominic Spill
376e25b118aSDominic Spill    for keystroke in keycodes:
377e25b118aSDominic Spill        # don't repeat keys that are still held down
378e25b118aSDominic Spill        if active_keys.count(keystroke) == 0:
379e25b118aSDominic Spill
380e25b118aSDominic Spill            if (mod & CTRL):
381e25b118aSDominic Spill                sys.stdout.write("CTRL^")
382e25b118aSDominic Spill            if (mod & ALT):
383e25b118aSDominic Spill                sys.stdout.write("ALT^")
384e25b118aSDominic Spill            if (mod & GUI): # e.g. Windows key
385e25b118aSDominic Spill                sys.stdout.write("GUI^")
386e25b118aSDominic Spill
387e25b118aSDominic Spill            sys.stdout.write(hid2ascii(keystroke, mod & SHIFT))
388e25b118aSDominic Spill
389e25b118aSDominic Spill    active_keys = keycodes
390e25b118aSDominic Spill
3915cf4c9cbSDominic Spilldef parse_ellisys_export(exportfile):
3925cf4c9cbSDominic Spill    try:
3935cf4c9cbSDominic Spill        cap = open(exportfile, "r")
3945cf4c9cbSDominic Spill    except (OSError, IOError) as e:
395*48fe012aSMike Ryan        print("Unable to open Ellisys capture file.", file=sys.stderr)
3965cf4c9cbSDominic Spill        return
3975cf4c9cbSDominic Spill
3985cf4c9cbSDominic Spill    # Check to make sure the CSV file header matches out expectations
3995cf4c9cbSDominic Spill    hdr=cap.readline()
4005cf4c9cbSDominic Spill    if (hdr != ELLISYS_CSV_HDR):
401*48fe012aSMike Ryan        print("Invalid CSV file (does not match Ellisys export format)", file=sys.stderr)
4025cf4c9cbSDominic Spill        return
4035cf4c9cbSDominic Spill
4045cf4c9cbSDominic Spill    for packetline in cap.xreadlines():
4055cf4c9cbSDominic Spill        try:
4065cf4c9cbSDominic Spill            (edepth, etime, ename, edata) = packetline.replace('"', '').strip().split(",")
4075cf4c9cbSDominic Spill        except ValueError:
4085cf4c9cbSDominic Spill            continue
4095cf4c9cbSDominic Spill
4105cf4c9cbSDominic Spill        # We are only interedted in HID Input data
4115cf4c9cbSDominic Spill        if (ename != ELLISYS_HID_INPUT): continue
4125cf4c9cbSDominic Spill
4135cf4c9cbSDominic Spill        parse_ellisys_keydata(edata)
4145cf4c9cbSDominic Spill
4155cf4c9cbSDominic Spilldef parse_ellisys_keydata(payload):
4165cf4c9cbSDominic Spill    TRANS_HDR_IN_DATA = "\xA1"
4175cf4c9cbSDominic Spill    DEST_CHANNEL_ID = "\x06\x03"
4185cf4c9cbSDominic Spill
4195cf4c9cbSDominic Spill    # Convert space-separated hex into string
4205cf4c9cbSDominic Spill    payload = payload.replace(' ','').decode("hex")
4215cf4c9cbSDominic Spill
4225cf4c9cbSDominic Spill    # The Ellisys CSV file format doesn't give us the L2CAP data, so we fake it here by adding
4235cf4c9cbSDominic Spill    # a 2-byte length field, destination CID, and transaction header
4245cf4c9cbSDominic Spill    packet = chr(len(payload)+1) + "\x00" + DEST_CHANNEL_ID + TRANS_HDR_IN_DATA + payload
4255cf4c9cbSDominic Spill    parse_l2cap_keydata(packet)
4265cf4c9cbSDominic Spill
427e25b118aSDominic Spilldef parse_bb_keydata(packet):
428e25b118aSDominic Spill
429e25b118aSDominic Spill    BTBBHDR_TYPE_MASK = 0x78
430e25b118aSDominic Spill    BTBBHDR_TYPE_SHIFT = 3
431e25b118aSDominic Spill    BTBBHDR_TYPE_DM1 = 3
432e25b118aSDominic Spill    BTBBPAYLOADHDR_LLID_MASK = 0x03
433e25b118aSDominic Spill    BTBBPAYLOADHDR_LLID_SHIFT = 0
434e25b118aSDominic Spill    BTBBPAYLOADHDR_LEN_MASK = 0xF8
435e25b118aSDominic Spill    BTBBPAYLOADHDR_LEN_SHIFT = 3
436e25b118aSDominic Spill    LLID_L2CAP = 2
437e25b118aSDominic Spill
438e25b118aSDominic Spill    # Keyboard keystrokes are only seen in frames at least 40 bytes long
439e25b118aSDominic Spill    if len(packet) < 40:
440e25b118aSDominic Spill        return
441e25b118aSDominic Spill
442e25b118aSDominic Spill    # Keyboard keystrokes are only seen in DM1 frames
443e25b118aSDominic Spill    btbbhdr = packet[20:23]
444e25b118aSDominic Spill    type = (ord(btbbhdr[0]) & BTBBHDR_TYPE_MASK) >> BTBBHDR_TYPE_SHIFT
445e25b118aSDominic Spill    if type != BTBBHDR_TYPE_DM1:
446e25b118aSDominic Spill        return
447e25b118aSDominic Spill
448e25b118aSDominic Spill    # Keyboard keystrokes are only seen in L2CAP packets 14 bytes long
449e25b118aSDominic Spill    btbbpayloadhdr = ord(packet[23])
450e25b118aSDominic Spill    llid = btbbpayloadhdr & (BTBBPAYLOADHDR_LLID_MASK) >> BTBBPAYLOADHDR_LLID_SHIFT
451e25b118aSDominic Spill    l2clen = (btbbpayloadhdr & BTBBPAYLOADHDR_LEN_MASK) >> BTBBPAYLOADHDR_LEN_SHIFT
452*48fe012aSMike Ryan    #print("Debug btbbpayloadhdr 0x%02x, llid %d, l2clen %d"%(btbbpayloadhdr, llid, l2clen))
453e25b118aSDominic Spill    if llid != LLID_L2CAP or l2clen < 14:
454e25b118aSDominic Spill        return
455e25b118aSDominic Spill
456e25b118aSDominic Spill    parse_l2cap_keydata(packet[24:38])
457e25b118aSDominic Spill
458e25b118aSDominic Spilldef parse_hci_keydata(packet):
459e25b118aSDominic Spill
460e25b118aSDominic Spill    HCI_TYPE_ACL_DATA = 2
461e25b118aSDominic Spill
462e25b118aSDominic Spill    # Keyboard keystrokes are only seen in frames at least 19 bytes long
463e25b118aSDominic Spill    if len(packet) < 19:
464e25b118aSDominic Spill        return
465e25b118aSDominic Spill
466e25b118aSDominic Spill    # Keyboard keystrokes are only seen in ACL Data frames
467e25b118aSDominic Spill    type = ord(packet[0])
468e25b118aSDominic Spill    if type != HCI_TYPE_ACL_DATA:
469e25b118aSDominic Spill        return
470e25b118aSDominic Spill
471e25b118aSDominic Spill    parse_l2cap_keydata(packet[5:])
472e25b118aSDominic Spill
473e25b118aSDominic Spillif __name__ == '__main__':
474e25b118aSDominic Spill
475e25b118aSDominic Spill    arg_pcapfile = None
4765cf4c9cbSDominic Spill    arg_ellisysfile = None
477e25b118aSDominic Spill    arg_count = -1
478e25b118aSDominic Spill    packetcount = 0
479e25b118aSDominic Spill
480e25b118aSDominic Spill    while len(sys.argv) > 1:
481e25b118aSDominic Spill        op = sys.argv.pop(1)
482e25b118aSDominic Spill        if op == '-r':
483e25b118aSDominic Spill            arg_pcapfile = sys.argv.pop(1)
484e25b118aSDominic Spill        if op == '-c':
485e25b118aSDominic Spill            arg_count = int(sys.argv.pop(1))
4865cf4c9cbSDominic Spill        if op == '-e':
4875cf4c9cbSDominic Spill            arg_ellisysfile = sys.argv.pop(1)
488e25b118aSDominic Spill        if op == '-h':
489e25b118aSDominic Spill            usage()
490e25b118aSDominic Spill            sys.exit(0)
491e25b118aSDominic Spill
4925cf4c9cbSDominic Spill    if (arg_ellisysfile == None and arg_pcapfile == None):
493*48fe012aSMike Ryan        print("Must specify a libpcap capture or an Ellisys CSV file", file=sys.stderr)
494e25b118aSDominic Spill        usage()
4955cf4c9cbSDominic Spill        sys.exit(0)
496e25b118aSDominic Spill
4975cf4c9cbSDominic Spill    if arg_pcapfile != None:
498e25b118aSDominic Spill        cap = PcapReader(arg_pcapfile)
499e25b118aSDominic Spill
500e25b118aSDominic Spill        while arg_count != packetcount:
501e25b118aSDominic Spill            try:
502e25b118aSDominic Spill                (pheader, packet) = cap.pnext()
503e25b118aSDominic Spill                pkttime = pheader[0]
504e25b118aSDominic Spill                packetcount+=1
505e25b118aSDominic Spill
506e25b118aSDominic Spill                if cap.datalink() == DLT_EN10MB:
507e25b118aSDominic Spill                    parse_bb_keydata(packet)
508e25b118aSDominic Spill                elif cap.datalink() == DLT_BLUETOOTH_HCI_H4:
509e25b118aSDominic Spill                    parse_hci_keydata(packet)
510e25b118aSDominic Spill                else:
511*48fe012aSMike Ryan                    print("Unsupported libpcap data link layer: %d\n" % cap.datalink(), file=sys.stderr)
512e25b118aSDominic Spill            except TypeError: # raised when pnext returns Null (end of capture)
513e25b118aSDominic Spill                break
514e25b118aSDominic Spill
515e25b118aSDominic Spill        cap.close()
5165cf4c9cbSDominic Spill
5175cf4c9cbSDominic Spill    if arg_ellisysfile != None:
5185cf4c9cbSDominic Spill        parse_ellisys_export(arg_ellisysfile)
5195cf4c9cbSDominic Spill
520e25b118aSDominic Spill    print
521