1#!/usr/local/bin/python3.8
2
3"""
4#    Display PC/SC functions arguments
5#    Copyright (C) 2011-2021  Ludovic Rousseau
6"""
7#
8#    This program is free software: you can redistribute it and/or modify
9#    it under the terms of the GNU General Public License as published by
10#    the Free Software Foundation, either version 3 of the License, or
11#    (at your option) any later version.
12#
13#    This program is distributed in the hope that it will be useful,
14#    but WITHOUT ANY WARRANTY; without even the implied warranty of
15#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16#    GNU General Public License for more details.
17#
18#    You should have received a copy of the GNU General Public License
19#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21import os
22import signal
23import time
24try:
25    # for Python3
26    from queue import Queue
27except ImportError:
28    # for Python2
29    from Queue import Queue
30from threading import Thread
31from operator import attrgetter
32
33
34def hexdump(data_buffer, width=16):
35    def quotechars(data_buffer):
36        return ''.join(['.', chr(c)][c > 31 and c < 127] for c in data_buffer)
37
38    result = []
39    offset = 0
40    while data_buffer:
41        line = data_buffer[:width]
42        data_buffer = data_buffer[width:]
43        hex_dump = " ".join("%02X" % c for c in line)
44        ascii_dump = quotechars(line)
45        if len(line) < width:
46            hex_dump += "   " * (width - len(line))
47        result.append("%04X %s %s" % (offset, hex_dump, ascii_dump))
48        offset += width
49    return result
50
51
52def _parse_rv(line):
53    """ parse the return value line """
54    if line == "":
55        raise Exception("Empty line (application exited?)")
56
57    (direction, sec, usec, function, code, rv) = line.split('|')
58    if direction != '<':
59        raise Exception("Wrong line:", line)
60
61    sec = int(sec)
62    usec = int(usec)
63
64    return (code, rv, sec, usec)
65
66
67class SpyExit(Exception):
68    pass
69
70class StatRecord(object):
71    """ Record to store statistics """
72
73    def __init__(self, name):
74        self.name = name
75        self.executions = list()
76        self.total_time = 0
77        self.occurences = 0
78
79    def __repr__(self):
80        return self.name + ": " + repr(self.executions)
81
82
83class PCSCspy(object):
84    """ PC/SC spy """
85
86    color_red = "\x1b[01;31m"
87    color_green = "\x1b[32m"
88    color_blue = "\x1b[34m"
89    color_magenta = "\x1b[35m"
90    color_normal = "\x1b[0m"
91
92    def get_line(self):
93        line = self.queue.get()
94        if line == "EXIT":
95            raise SpyExit()
96        return line
97
98    def _log_rv(self):
99        """ log the return value """
100        line = self.get_line()
101        (code, rv, sec, usec) = _parse_rv(line)
102        delta_sec = sec - self.sec
103        delta_usec = usec - self.usec
104        if delta_usec < 0:
105            delta_sec -= 1
106            delta_usec += 1000000
107        if self.diffable:
108            time = " [??.??]"
109        else:
110            time = " [%d.%06d]" % (delta_sec, delta_usec)
111        self.execution_time = delta_sec + delta_usec / 1000000.
112
113        rvs = {
114            0x00000000: "SCARD_S_SUCCESS",
115            0x80100001: "SCARD_F_INTERNAL_ERROR",
116            0x80100002: "SCARD_E_CANCELLED",
117            0x80100003: "SCARD_E_INVALID_HANDLE",
118            0x80100004: "SCARD_E_INVALID_PARAMETER",
119            0x80100005: "SCARD_E_INVALID_TARGET",
120            0x80100006: "SCARD_E_NO_MEMORY",
121            0x80100007: "SCARD_F_WAITED_TOO_LONG",
122            0x80100008: "SCARD_E_INSUFFICIENT_BUFFER",
123            0x80100009: "SCARD_E_UNKNOWN_READER",
124            0x8010000A: "SCARD_E_TIMEOUT",
125            0x8010000B: "SCARD_E_SHARING_VIOLATION",
126            0x8010000C: "SCARD_E_NO_SMARTCARD",
127            0x8010000D: "SCARD_E_UNKNOWN_CARD",
128            0x8010000E: "SCARD_E_CANT_DISPOSE",
129            0x8010000F: "SCARD_E_PROTO_MISMATCH",
130            0x80100010: "SCARD_E_NOT_READY",
131            0x80100011: "SCARD_E_INVALID_VALUE",
132            0x80100012: "SCARD_E_SYSTEM_CANCELLED",
133            0x80100013: "SCARD_F_COMM_ERROR",
134            0x80100014: "SCARD_F_UNKNOWN_ERROR",
135            0x80100015: "SCARD_E_INVALID_ATR",
136            0x80100016: "SCARD_E_NOT_TRANSACTED",
137            0x80100017: "SCARD_E_READER_UNAVAILABLE",
138            0x80100018: "SCARD_P_SHUTDOWN",
139            0x80100019: "SCARD_E_PCI_TOO_SMALL",
140            0x8010001A: "SCARD_E_READER_UNSUPPORTED",
141            0x8010001B: "SCARD_E_DUPLICATE_READER",
142            0x8010001C: "SCARD_E_CARD_UNSUPPORTED",
143            0x8010001D: "SCARD_E_NO_SERVICE",
144            0x8010001E: "SCARD_E_SERVICE_STOPPED",
145            0x8010001F: "SCARD_E_UNSUPPORTED_FEATURE",
146            0x80100020: "SCARD_E_ICC_INSTALLATION",
147            0x80100021: "SCARD_E_ICC_CREATEORDER",
148            0x80100023: "SCARD_E_DIR_NOT_FOUND",
149            0x80100024: "SCARD_E_FILE_NOT_FOUND",
150            0x80100025: "SCARD_E_NO_DIR",
151            0x80100026: "SCARD_E_NO_FILE",
152            0x80100027: "SCARD_E_NO_ACCESS",
153            0x80100028: "SCARD_E_WRITE_TOO_MANY",
154            0x80100029: "SCARD_E_BAD_SEEK",
155            0x8010002A: "SCARD_E_INVALID_CHV",
156            0x8010002B: "SCARD_E_UNKNOWN_RES_MNG",
157            0x8010002C: "SCARD_E_NO_SUCH_CERTIFICATE",
158            0x8010002D: "SCARD_E_CERTIFICATE_UNAVAILABLE",
159            0x8010002E: "SCARD_E_NO_READERS_AVAILABLE",
160            0x8010002F: "SCARD_E_COMM_DATA_LOST",
161            0x80100030: "SCARD_E_NO_KEY_CONTAINER",
162            0x80100031: "SCARD_E_SERVER_TOO_BUSY",
163            0x80100065: "SCARD_W_UNSUPPORTED_CARD",
164            0x80100066: "SCARD_W_UNRESPONSIVE_CARD",
165            0x80100067: "SCARD_W_UNPOWERED_CARD",
166            0x80100068: "SCARD_W_RESET_CARD",
167            0x80100069: "SCARD_W_REMOVED_CARD",
168            0x8010006A: "SCARD_W_SECURITY_VIOLATION",
169            0x8010006B: "SCARD_W_WRONG_CHV",
170            0x8010006C: "SCARD_W_CHV_BLOCKED",
171            0x8010006D: "SCARD_W_EOF",
172            0x8010006E: "SCARD_W_CANCELLED_BY_USER",
173            0x8010006F: "SCARD_W_CARD_NOT_AUTHENTICATED",
174            }
175        rv_text = rvs[int(rv, 16)]
176        data = " => " + code + " (" + rv_text + " [" + rv + "]) "
177        if "0x00000000" != rv:
178            if self.color:
179                print(self.indent + PCSCspy.color_red + data +
180                        PCSCspy.color_normal + time)
181            else:
182                print(self.indent + data + time)
183        else:
184            print(self.indent + data + time)
185
186        return rv_text
187
188    def log_in(self, line):
189        """ generic log for IN line """
190        if self.color:
191            print(self.indent + PCSCspy.color_green + " i " + line +
192                    PCSCspy.color_normal)
193        else:
194            print(self.indent + " i " + line)
195
196    def log_out(self, line):
197        """ generic log for OUT line """
198        if self.color:
199            print(self.indent + PCSCspy.color_magenta + " o " + line +
200                    PCSCspy.color_normal)
201        else:
202            print(self.indent + " o " + line)
203
204    def log_in_multi(self, lines, padding=""):
205        """ generic log for IN lines """
206        for line in lines:
207            self.log_in(padding + line)
208
209    def log_out_multi(self, lines, padding=""):
210        """ generic log for OUT lines """
211        for line in lines:
212            self.log_out(padding + line)
213
214    def log_in_hCard(self):
215        """ log hCard IN parameter """
216        hCard = self.get_line()
217        if self.diffable:
218            self.log_in("hCard: 0x????")
219        else:
220            self.log_in("hCard: %s" % hCard)
221
222    def log_in_hContext(self):
223        """ log hContext IN parameter """
224        hContext = self.get_line()
225        if self.diffable:
226            self.log_in("hContext: 0x????")
227        else:
228            self.log_in("hContext: %s" % hContext)
229
230    def log_in_disposition(self):
231        """ log dwDisposition IN parameter """
232        dwDisposition = self.get_line()
233        dispositions = {0: 'SCARD_LEAVE_CARD',
234                        1: 'SCARD_RESET_CARD',
235                        2: 'SCARD_UNPOWER_CARD',
236                        3: 'SCARD_EJECT_CARD'}
237        try:
238            disposition = dispositions[int(dwDisposition, 16)]
239        except KeyError:
240            disposition = "UNKNOWN"
241        self.log_in("dwDisposition: %s (%s)" % (disposition,
242                                                dwDisposition))
243
244    def log_in_attrid(self):
245        """ log dwAttrId IN parameter """
246        dwAttrId = self.get_line()
247        attrids = {0x00010100: 'SCARD_ATTR_VENDOR_NAME',
248                   0x00010102: 'SCARD_ATTR_VENDOR_IFD_VERSION',
249                   0x00010103: 'SCARD_ATTR_VENDOR_IFD_SERIAL_NO',
250                   0x0007A007: 'SCARD_ATTR_MAXINPUT',
251                   0x00090300: 'SCARD_ATTR_ICC_PRESENCE',
252                   0x00090301: 'SCARD_ATTR_ICC_INTERFACE_STATUS',
253                   0x00090303: 'SCARD_ATTR_ATR_STRING',
254                   0x7FFF0003: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_A',
255                   0x7FFF0004: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_A',
256                   0x7FFF0005: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_W',
257                   0x7FFF0006: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_W'}
258        try:
259            attrid = attrids[int(dwAttrId, 16)]
260        except KeyError:
261            attrid = "UNKNOWN"
262        self.log_in("dwAttrId: %s (%s)" % (attrid, dwAttrId))
263
264    def log_in_dwShareMode(self):
265        """ log dwShareMode IN parameter """
266        dwShareMode = self.get_line()
267        sharemodes = {1: 'SCARD_SHARE_EXCLUSIVE',
268                      2: 'SCARD_SHARE_SHARED',
269                      3: 'SCARD_SHARE_DIRECT'}
270        try:
271            sharemode = sharemodes[int(dwShareMode, 16)]
272        except KeyError:
273            sharemode = "UNKNOWN"
274        self.log_in("dwShareMode: %s (%s)" % (sharemode, dwShareMode))
275
276    def log_in_dwPreferredProtocols(self):
277        """ log dwPreferredProtocols IN parameter """
278        dwPreferredProtocols = self.get_line()
279        PreferredProtocols = list()
280        protocol = int(dwPreferredProtocols, 16)
281        if protocol & 1:
282            PreferredProtocols.append("T=0")
283        if protocol & 2:
284            PreferredProtocols.append("T=1")
285        if protocol & 4:
286            PreferredProtocols.append("RAW")
287        if protocol & 8:
288            PreferredProtocols.append("T=15")
289        self.log_in("dwPreferredProtocols: %s (%s)" % (dwPreferredProtocols,
290            ", ".join(PreferredProtocols)))
291
292    def log_out_dwActiveProtocol(self):
293        """ log dwActiveProtocol OUT parameter """
294        dwActiveProtocol = self.get_line()
295        protocol = int(dwActiveProtocol, 16)
296        if protocol & 1:
297            protocol = "T=0"
298        elif protocol & 2:
299            protocol = "T=1"
300        elif protocol & 4:
301            protocol = "RAW"
302        elif protocol & 8:
303            protocol = "T=15"
304        else:
305            protocol = "UNKNOWN"
306        self.log_out("dwActiveProtocol: %s (%s)" % (protocol,
307            dwActiveProtocol))
308
309    def log_out_hContext(self):
310        """ log hContext OUT parameter """
311        hContext = self.get_line()
312        if self.diffable:
313            self.log_out("hContext: 0x????")
314        else:
315            self.log_out("hContext: %s" % hContext)
316
317    def _get_state(self, dwState):
318        """ parse dwCurrentState and dwEventState """
319        SCardStates = {0: 'SCARD_STATE_UNAWARE',
320                       1: 'SCARD_STATE_IGNORE',
321                       2: 'SCARD_STATE_CHANGED',
322                       4: 'SCARD_STATE_UNKNOWN',
323                       8: 'SCARD_STATE_UNAVAILABLE',
324                       16: 'SCARD_STATE_EMPTY',
325                       32: 'SCARD_STATE_PRESENT',
326                       64: 'SCARD_STATE_ATRMATCH',
327                       128: 'SCARD_STATE_EXCLUSIVE',
328                       256: 'SCARD_STATE_INUSE',
329                       512: 'SCARD_STATE_MUTE',
330                       1024: 'SCARD_STATE_UNPOWERED'}
331
332        state = list()
333        for bit in SCardStates.keys():
334            if dwState & bit:
335                state.append(SCardStates[bit])
336        return ", ".join(state)
337
338    def log_dwCurrentState(self, log):
339        """ log dwCurrentState IN/OUT parameter """
340        dwCurrentState = self.get_line()
341        state = self._get_state(int(dwCurrentState, 16))
342        log(" dwCurrentState: %s (%s)" % (state, dwCurrentState))
343
344    def log_dwEventState(self, log):
345        """ log dwEventState IN/OUT parameter """
346        dwEventState = self.get_line()
347        state = self._get_state(int(dwEventState, 16))
348        log(" dwEventState: %s (%s)" % (state, dwEventState))
349
350    def log_dwControlCode(self):
351        """ log SCardControl() dwControlCode """
352        dwControlCode = self.get_line()
353
354        try:
355            code = self.ControlCodes[int(dwControlCode, 16)]
356        except KeyError:
357            code = "UNKNOWN"
358        self.log_in("dwControlCode: %s (%s)" % (code, dwControlCode))
359
360        return int(dwControlCode, 16)
361
362    def log_in2(self, header):
363        """ generic log IN parameter """
364        data = self.get_line()
365        if data.startswith("0x"):
366            decimal = int(data, 16)
367            self.log_in("%s %s (%d)" % (header, data, decimal))
368        else:
369            self.log_in("%s %s" % (header, data))
370        return data
371
372    def log_out2(self, header):
373        """ generic log OUT parameter """
374        data = self.get_line()
375        if data.startswith("0x"):
376            decimal = int(data, 16)
377            self.log_out("%s %s (%d)" % (header, data, decimal))
378        else:
379            self.log_out("%s %s" % (header, data))
380        return data
381
382    def log_out_n_str(self, size_name, field_name):
383        """ log multi-lines entries """
384        data = self.get_line()
385        self.log_out("%s %s" % (size_name, data))
386        size = int(data, 16)
387        data_read = 0
388        if 0 == size:
389            data = self.get_line()
390            self.log_out("%s %s" % (field_name, data))
391        else:
392            while data_read < size:
393                data = self.get_line()
394                self.log_out("%s %s" % (field_name, data))
395                if data == 'NULL':
396                    break
397                data_read += len(data) + 1
398
399    def log_name(self, name):
400        """ log function name """
401        if self.color:
402            print(self.indent + PCSCspy.color_blue + name +
403                    PCSCspy.color_normal)
404        else:
405            print(self.indent + name)
406
407    def _log_readers(self, readers, direction):
408        """ log SCARD_READERSTATE structure """
409        log = self.log_in2
410        raw_log = self.log_in
411        if (direction == "out"):
412            log = self.log_out2
413            raw_log = self.log_out
414        for index in range(readers):
415            log("szReader:")
416            self.log_dwCurrentState(raw_log)
417            self.log_dwEventState(raw_log)
418            log(" Atr length:")
419            log(" Atr:")
420
421    def log_buffer(self, field, direction):
422        log = self.log_in
423        log_multi = self.log_in_multi
424        if direction == "out":
425            log = self.log_out
426            log_multi = self.log_out_multi
427
428        hex_buffer = self.get_line()
429        log(field)
430        if hex_buffer == "NULL":
431            log(" NULL")
432        elif hex_buffer != "":
433            int_buffer = [int(x, 16) for x in hex_buffer.split(" ")]
434            formated_buffer = hexdump(int_buffer)
435            log_multi(formated_buffer, " ")
436
437        return hex_buffer
438
439    def _SCardEstablishContext(self):
440        """ SCardEstablishContext """
441        self.log_name("SCardEstablishContext")
442        dwScope = self.get_line()
443        scopes = {0: 'SCARD_SCOPE_USER',
444                  1: 'SCARD_SCOPE_TERMINAL',
445                  2: 'SCARD_SCOPE_SYSTEM'}
446        self.log_in("dwScope: %s (%s)" % (scopes[int(dwScope, 16)], dwScope))
447        self.log_out_hContext()
448        self._log_rv()
449
450    def _SCardIsValidContext(self):
451        """ SCardIsValidContext """
452        self.log_name("SCardIsValidContext")
453        self.log_in_hContext()
454        self._log_rv()
455
456    def _SCardReleaseContext(self):
457        """ SCardReleaseContext """
458        self.log_name("SCardReleaseContext")
459        self.log_in_hContext()
460        self._log_rv()
461
462    def _SCardListReaders(self):
463        """ SCardListReaders """
464        self.log_name("SCardListReaders")
465        self.log_in_hContext()
466        self.log_in2("mszGroups:")
467        self.log_out_n_str("pcchReaders:", "mszReaders:")
468        self._log_rv()
469
470    def _SCardListReaderGroups(self):
471        """ SCardListReaderGroups """
472        self.log_name("SCardListReaderGroups")
473        self.log_in_hContext()
474        self.log_in2("pcchGroups:")
475        self.log_out_n_str("pcchGroups:", "mszGroups:")
476        self._log_rv()
477
478    def _SCardGetStatusChange(self):
479        """ SCardGetStatusChange """
480        self.log_name("SCardGetStatusChange")
481        self.log_in_hContext()
482        self.log_in2("dwTimeout:")
483        readers = int(self.get_line(), 16)
484        self.log_in("cReaders: %d" % readers)
485        self._log_readers(readers, direction="in")
486        self._log_readers(readers, direction="out")
487        self._log_rv()
488
489    def _SCardFreeMemory(self):
490        """ SCardFreeMemory """
491        self.log_name("SCardFreeMemory")
492        self.log_in_hContext()
493        self.log_in2("pvMem:")
494        self._log_rv()
495
496    def _SCardConnect(self):
497        """ SCardConnect """
498        self.log_name("SCardConnect")
499        self.log_in_hContext()
500        self.log_in2("szReader")
501        self.log_in_dwShareMode()
502        self.log_in_dwPreferredProtocols()
503        self.log_in2("phCard")
504        self.log_in2("pdwActiveProtocol")
505        self.log_out2("phCard")
506        self.log_out_dwActiveProtocol()
507        self._log_rv()
508
509    def _SCardTransmit(self):
510        """ SCardTransmit """
511        self.log_name("SCardTransmit")
512        self.log_in_hCard()
513        self.log_in2("bSendLength")
514        self.log_buffer("bSendBuffer", "in")
515        self.log_out2("bRecvLength")
516        self.log_buffer("bRecvBuffer", "out")
517        self._log_rv()
518
519    def _SCardControl(self):
520        """ SCardControl """
521        self.log_name("SCarControl")
522        self.log_in_hCard()
523        dwControlCode = self.log_dwControlCode()
524        bSendLength = self.log_in2("bSendLength")
525        bSendBuffer = self.log_buffer("bSendBuffer", "in")
526        bRecvLength = self.log_out2("bRecvLength")
527        bRecvBuffer = self.log_buffer("bRecvBuffer", "out")
528
529        rv_text = self._log_rv()
530
531        # do not parse the received buffer in case of error
532        if rv_text != "SCARD_S_SUCCESS":
533            return
534
535        def hex2int(data, lengh):
536            return [int(x, 16) for x in data.split(" ")]
537
538        if dwControlCode == self.CM_IOCTL_GET_FEATURE_REQUEST:
539            print("  parsing CM_IOCTL_GET_FEATURE_REQUEST results:")
540            bRecvLength = int(bRecvLength, 16)
541
542            bRecvBuffer = hex2int(bRecvBuffer, bRecvLength)
543
544            # parse GET_FEATURE_REQUEST results
545            while bRecvBuffer:
546                tag = bRecvBuffer[0]
547                length = bRecvBuffer[1]
548                value = bRecvBuffer[2:2 + length]
549                value_int = value[3] + 256 * (value[2] + 256
550                    * (value[1] + 256 * value[0]))
551                try:
552                    self.ControlCodes[value_int] = self.features[tag]
553                    self.__dict__[self.features[tag]] = value_int
554                except KeyError:
555                    self.ControlCodes[value_int] = "UNKNOWN"
556
557                print("  Tag %s is 0x%X" % (self.ControlCodes[value_int],
558                    value_int))
559
560                bRecvBuffer = bRecvBuffer[2 + length:]
561
562        elif dwControlCode == self.FEATURE_GET_TLV_PROPERTIES:
563            print("  parsing FEATURE_GET_TLV_PROPERTIES results:")
564            bRecvLength = int(bRecvLength, 16)
565
566            bRecvBuffer = hex2int(bRecvBuffer, bRecvLength)
567
568            tlv_properties = {
569                1: "PCSCv2_PART10_PROPERTY_wLcdLayout",
570                2: "PCSCv2_PART10_PROPERTY_bEntryValidationCondition",
571                3: "PCSCv2_PART10_PROPERTY_bTimeOut2",
572                4: "PCSCv2_PART10_PROPERTY_wLcdMaxCharacters",
573                5: "PCSCv2_PART10_PROPERTY_wLcdMaxLines",
574                6: "PCSCv2_PART10_PROPERTY_bMinPINSize",
575                7: "PCSCv2_PART10_PROPERTY_bMaxPINSize",
576                8: "PCSCv2_PART10_PROPERTY_sFirmwareID",
577                9: "PCSCv2_PART10_PROPERTY_bPPDUSupport"}
578
579            # parse GET_TLV_PROPERTIES results
580            while bRecvBuffer:
581                tag = bRecvBuffer[0]
582                length = bRecvBuffer[1]
583                value = bRecvBuffer[2:2 + length]
584
585                try:
586                    tag_text = tlv_properties[tag]
587                except KeyError:
588                    tag_text = "UNKNOWN"
589
590                print("  Tag:", tag_text)
591                print("   Length: ", length)
592                print("   Value:", value)
593
594                bRecvBuffer = bRecvBuffer[2 + length:]
595
596        elif dwControlCode == self.FEATURE_IFD_PIN_PROPERTIES:
597            print("  parsing FEATURE_IFD_PIN_PROPERTIES results:")
598            bRecvBuffer = hex2int(bRecvBuffer, int(bRecvLength, 16))
599
600            print("  wLcdLayout:", bRecvBuffer[0], bRecvBuffer[1])
601            print("  bEntryValidationCondition:", bRecvBuffer[2])
602            print("  bTimeOut2:", bRecvBuffer[3])
603
604        elif dwControlCode == self.FEATURE_VERIFY_PIN_DIRECT:
605            print("  parsing FEATURE_VERIFY_PIN_DIRECT:")
606            bSendBuffer = hex2int(bSendBuffer, int(bSendLength, 16))
607
608            print("  bTimerOut:", bSendBuffer[0])
609            print("  bTimerOut2:", bSendBuffer[1])
610            print("  bmFormatString:", bSendBuffer[2])
611            print("  bmPINBlockString:", bSendBuffer[3])
612            print("  bmPINLengthFormat:", bSendBuffer[4])
613            print("  wPINMaxExtraDigit: 0x%02X%02X" % (bSendBuffer[6],
614                                                       bSendBuffer[5]))
615            print("   Min:", bSendBuffer[6])
616            print("   Max:", bSendBuffer[5])
617            print("  bEntryValidationCondition:", bSendBuffer[7])
618            print("  bNumberMessage:", bSendBuffer[8])
619            print("  wLangId: 0x%02X%02X" % (bSendBuffer[10],
620                bSendBuffer[9]))
621            print("  bMsgIndex:", bSendBuffer[11])
622            print("  bTeoPrologue:", bSendBuffer[12], bSendBuffer[13], \
623                bSendBuffer[14])
624            print("  ulDataLength:", bSendBuffer[15] + \
625                bSendBuffer[16] * 256 + bSendBuffer[17] * 2 ** 16 + \
626                bSendBuffer[18] * 2 ** 24)
627            print("  APDU:")
628            result = hexdump(bSendBuffer[19:])
629            for line in result:
630                print("  ", line)
631
632    def _SCardGetAttrib(self):
633        """ SCardGetAttrib """
634        self.log_name("SCardGetAttrib")
635        self.log_in_hCard()
636        self.log_in_attrid()
637        self.log_out2("bAttrLen")
638        self.log_buffer("bAttr", "out")
639        self._log_rv()
640
641    def _SCardSetAttrib(self):
642        """ SCardSetAttrib """
643        self.log_name("SCardSetAttrib")
644        self.log_in_hCard()
645        self.log_in_attrid()
646        self.log_in2("bAttrLen")
647        self.log_buffer("bAttr", "in")
648        self._log_rv()
649
650    def _SCardStatus(self):
651        """ SCardStatus """
652        self.log_name("SCardStatus")
653        self.log_in_hCard()
654        self.log_in2("pcchReaderLen")
655        self.log_in2("pcbAtrLen")
656        self.log_out2("cchReaderLen")
657        self.log_out2("mszReaderName")
658        self.log_out2("dwState")
659        self.log_out2("dwProtocol")
660        data = self.log_out2("bAtrLen")
661        if not data == "NULL":
662            self.log_out2("bAtr")
663        else:
664            self.log_out("bAtr")
665        self._log_rv()
666
667    def _SCardReconnect(self):
668        """ SCardReconnect """
669        self.log_name("SCardReconnect")
670        self.log_in_hCard()
671        self.log_in_dwShareMode()
672        self.log_in_dwPreferredProtocols()
673        self.log_in2("dwInitialization")
674        self.log_out_dwActiveProtocol()
675        self._log_rv()
676
677    def _SCardDisconnect(self):
678        """" SCardDisconnect """
679        self.log_name("SCardDisconnect")
680        self.log_in_hCard()
681        self.log_in_disposition()
682        self._log_rv()
683
684    def _SCardBeginTransaction(self):
685        """ SCardBeginTransaction """
686        self.log_name("SCardBeginTransaction")
687        self.log_in_hCard()
688        self._log_rv()
689
690    def _SCardEndTransaction(self):
691        """ SCardEndTransaction """
692        self.log_name("SCardEndTransaction")
693        self.log_in_hCard()
694        self.log_in_disposition()
695        self._log_rv()
696
697    def _SCardCancel(self):
698        """ SCardCancel """
699        self.log_name("SCardCancel")
700        self.log_in_hCard()
701        self._log_rv()
702
703    def __init__(self, queue, stats, indent=0, color=True, diffable=False):
704        """ constructor """
705
706        # communication queue
707        self.queue = queue
708
709        self.color = color
710        self.diffable = diffable
711        self.stats = stats
712        self.indent = " " * (indent * 4)
713        if display_thread:
714            self.indent += " (t%d) " % indent
715
716        self.features = {0x01: "FEATURE_VERIFY_PIN_START",
717                         0x02: "FEATURE_VERIFY_PIN_FINISH",
718                         0x03: "FEATURE_MODIFY_PIN_START",
719                         0x04: "FEATURE_MODIFY_PIN_FINISH",
720                         0x05: "FEATURE_GET_KEY_PRESSED",
721                         0x06: "FEATURE_VERIFY_PIN_DIRECT",
722                         0x07: "FEATURE_MODIFY_PIN_DIRECT",
723                         0x08: "FEATURE_MCT_READER_DIRECT",
724                         0x09: "FEATURE_MCT_UNIVERSAL",
725                         0x0A: "FEATURE_IFD_PIN_PROPERTIES",
726                         0x0B: "FEATURE_ABORT",
727                         0x0C: "FEATURE_SET_SPE_MESSAGE",
728                         0x0D: "FEATURE_VERIFY_PIN_DIRECT_APP_ID",
729                         0x0E: "FEATURE_MODIFY_PIN_DIRECT_APP_ID",
730                         0x0F: "FEATURE_WRITE_DISPLAY",
731                         0x10: "FEATURE_GET_KEY",
732                         0x11: "FEATURE_IFD_DISPLAY_PROPERTIES",
733                         0x12: "FEATURE_GET_TLV_PROPERTIES",
734                         0x13: "FEATURE_CCID_ESC_COMMAND"}
735
736        def SCARD_CTL_CODE(code):
737            return 0x42000000 + code
738
739        self.CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400)
740        self.ControlCodes = {
741            SCARD_CTL_CODE(1): "IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE",
742            SCARD_CTL_CODE(3400): "CM_IOCTL_GET_FEATURE_REQUEST"
743            }
744        # dwControlCode not yet known
745        for key in self.features.keys():
746            self.__dict__[self.features[key]] = -1
747
748    def worker(self, *args):
749        line = self.queue.get()
750        while line != '':
751            # Enter function?
752            if line[0] != '>':
753                if line == 'EXIT':
754                    return
755                else:
756                    print("Garbage: ", line)
757            else:
758                try:
759                    # dispatch
760                    (direction, sec, usec, fct) = line.strip().split('|')
761                    self.sec = int(sec)
762                    self.usec = int(usec)
763                    if fct == 'SCardEstablishContext':
764                        self._SCardEstablishContext()
765                    elif fct == 'SCardReleaseContext':
766                        self._SCardReleaseContext()
767                    elif fct == 'SCardIsValidContext':
768                        self._SCardIsValidContext()
769                    elif fct == 'SCardListReaderGroups':
770                        self._SCardListReaderGroups()
771                    elif fct == 'SCardFreeMemory':
772                        self._SCardFreeMemory()
773                    elif fct == 'SCardListReaders':
774                        self._SCardListReaders()
775                    elif fct == 'SCardGetStatusChange':
776                        self._SCardGetStatusChange()
777                    elif fct == 'SCardConnect':
778                        self._SCardConnect()
779                    elif fct == 'SCardTransmit':
780                        self._SCardTransmit()
781                    elif fct == 'SCardControl' or fct == 'SCardControl132':
782                        self._SCardControl()
783                    elif fct == 'SCardGetAttrib':
784                        self._SCardGetAttrib()
785                    elif fct == 'SCardSetAttrib':
786                        self._SCardSetAttrib()
787                    elif fct == 'SCardStatus':
788                        self._SCardStatus()
789                    elif fct == 'SCardReconnect':
790                        self._SCardReconnect()
791                    elif fct == 'SCardDisconnect':
792                        self._SCardDisconnect()
793                    elif fct == 'SCardBeginTransaction':
794                        self._SCardBeginTransaction()
795                    elif fct == 'SCardEndTransaction':
796                        self._SCardEndTransaction()
797                    elif fct == 'SCardCancel':
798                        self._SCardCancel()
799                    else:
800                        print("Unknown function:", fct)
801                except SpyExit:
802                    return
803
804            try:
805                record = self.stats[fct]
806            except KeyError:
807                record = self.stats[fct] = StatRecord(fct)
808            record.executions.append(self.execution_time)
809
810            line = self.queue.get()
811
812
813class PCSCdemultiplexer(object):
814    def __init__(self, logfile=None, color=True, diffable=False):
815        """ constructor """
816
817        # use default fifo file?
818        if logfile is None:
819            logfile = os.path.expanduser('~/pcsc-spy')
820
821            # create the FIFO file
822            try:
823                os.mkfifo(logfile)
824            except OSError:
825                print("fifo %s already present. Reusing it." % logfile)
826
827        self.sec = self.usec = 0
828
829        self.fifo = logfile
830        self.filedesc2 = open(self.fifo, 'r')
831
832        self.queues = dict()
833        self.color = color
834        self.diffable = diffable
835
836    def __del__(self):
837        """ cleanup """
838        from stat import S_ISFIFO
839        file_stat = os.stat(self.fifo)
840
841        #  remove the log fifo only if it is a FIFO and not a log file
842        if S_ISFIFO(file_stat.st_mode):
843            os.unlink(self.fifo)
844
845    def loop(self):
846        """ loop reading logs """
847
848        # for statistics
849        stats = dict()
850
851        threads = dict()
852        indent = 0
853
854        # dispatch
855        line = self.filedesc2.readline().strip()
856
857        (thread, tail) = line.split('@')
858        res = tail.strip().split('|')
859        # check the first line format
860        if res[0] != ">":
861            print("Wrong format!")
862            print("First line Should start with a '>' but got:")
863            print(tail)
864            return
865        (direction, sec, usec, fct) = res
866
867        start_time = int(sec) + int(usec) / 1000000.
868
869        lastest_result = ""
870        while line != '':
871            previous_thread = thread
872            (thread, tail) = line.split('@')
873            if "<" in tail:
874                lastest_result = tail
875
876            # in case the thread changes
877            if previous_thread != thread:
878                # schedule the other thread so it has time to empty its
879                # queue
880                time.sleep(.001)
881
882            try:
883                queue = self.queues[thread]
884            except KeyError:
885                queue = self.queues[thread] = Queue()
886                stats[thread] = dict()
887                # new worker
888                spy = PCSCspy(queue, stats[thread], indent=indent,
889                    color=self.color, diffable=self.diffable)
890                threads[thread] = Thread(target=spy.worker)
891                threads[thread].start()
892                indent += 1
893
894            queue.put(tail)
895
896            line = self.filedesc2.readline().strip()
897
898        # tell the workers to exit
899        for thread in self.queues.keys():
900            self.queues[thread].put('EXIT')
901
902        # wait for all the workers to finish
903        for thread in threads:
904            threads[thread].join()
905
906        (code, rv, sec, usec) = _parse_rv(lastest_result)
907        end_time = sec + usec / 1000000.
908        total_time = end_time - start_time
909
910        # compute some statistics
911        thread_n = 1
912        for thread in stats:
913            stat = stats[thread]
914            for fct in stat:
915                record = stat[fct]
916                record.occurences = len(record.executions)
917                record.total_time = sum(record.executions)
918
919            records = [stat[fct] for fct in stat]
920
921            # display statistics sorted by total_time
922            print()
923            print("Thread %d/%d" % (thread_n, len(stats)))
924            print("Results sorted by total execution time")
925            print("total time: %f sec" % total_time)
926            for record in sorted(records, key=attrgetter('total_time'),
927                                 reverse=True):
928                print("%f sec (%3d calls) %5.2f%% %s" % (record.total_time,
929                    record.occurences, record.total_time / total_time * 100.,
930                    record.name))
931
932            thread_n += 1
933
934
935def main(logfile=None, color=True, diffable=False):
936    """ main """
937    spy = PCSCdemultiplexer(logfile, color, diffable)
938    spy.loop()
939
940
941def signal_handler(sig, frame):
942    print('Ctrl-C, exiting.')
943    os.kill(os.getpid(), signal.SIGQUIT)
944
945
946def print_usage():
947    print("Usage: pcsc-spy [-n|--nocolor] [-d|--diffable] [-h|--help] [-v|--version] [-t|--thread]")
948
949if __name__ == "__main__":
950    import sys
951    import getopt
952
953    logfile = None
954    try:
955        opts, args = getopt.getopt(sys.argv[1:], "ndhvt", ["nocolor",
956            "diffable", "help", "version", "thread"])
957    except getopt.GetoptError:
958        print_usage()
959        sys.exit(1)
960
961    color = True
962    diffable = False
963    display_thread = False
964    for o, a in opts:
965        if o == "-n" or o == "--nocolor":
966            color = False
967        if o == "-d" or o == "--diffable":
968            diffable = True
969        if o == "-h" or o == "--help":
970            print_usage()
971            sys.exit(1)
972        if o == "-v" or o == "--version":
973            print("pcsc-spy version 1.1")
974            print("Copyright (c) 2011-2021, Ludovic Rousseau <ludovic.rousseau@free.fr>")
975            print()
976            sys.exit(1)
977        if o == "-t" or o == "--thread":
978            display_thread = True
979
980    if len(args) > 0:
981        logfile = args[0]
982
983    signal.signal(signal.SIGINT, signal_handler)
984    main(logfile, color=color, diffable=diffable)
985