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