Files
pysim/pySim/transport/serial.py
Philipp Maier 3077343739 transport: move init message into concrete classes
In in the module __init__.py we print an init message (which type of
LinkBase class is providing the SimLink). However in __init__.py we tend
to have only platform independed code but the message string can already
be categorized as platform depened. Let's put the init message into the
constructor of the concrete classes of LinkBase.

Related: OS#6210
Change-Id: I0a6dd7deb79a5f3e42b29094a1cf2535075fa430
2023-10-24 19:28:34 +00:00

243 lines
7.0 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import serial
import time
import os.path
from typing import Optional
from pySim.exceptions import NoCardError, ProtocolError
from pySim.transport import LinkBase
from pySim.utils import h2b, b2h, Hexstr, ResTuple
class SerialSimLink(LinkBase):
""" pySim: Transport Link for serial (RS232) based readers included with simcard"""
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts',
debug: bool = False, **kwargs):
super().__init__(**kwargs)
print("Using serial reader interface")
if not os.path.exists(device):
raise ValueError("device file %s does not exist -- abort" % device)
self._sl = serial.Serial(
port=device,
parity=serial.PARITY_EVEN,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_TWO,
timeout=1,
xonxoff=0,
rtscts=0,
baudrate=baudrate,
)
self._rst_pin = rst
self._debug = debug
self._atr = None
def __del__(self):
if (hasattr(self, "_sl")):
self._sl.close()
def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False):
# Direct try
existing = False
try:
self.reset_card()
if not newcardonly:
return
else:
existing = True
except NoCardError:
pass
# Poll ...
mt = time.time() + timeout if timeout is not None else None
pe = 0
while (mt is None) or (time.time() < mt):
try:
time.sleep(0.5)
self.reset_card()
if not existing:
return
except NoCardError:
existing = False
except ProtocolError:
if existing:
existing = False
else:
# Tolerate a couple of protocol error ... can happen if
# we try when the card is 'half' inserted
pe += 1
if (pe > 2):
raise
# Timed out ...
raise NoCardError()
def connect(self):
self.reset_card()
def get_atr(self) -> Hexstr:
return self._atr
def disconnect(self):
pass # Nothing to do really ...
def reset_card(self):
rv = self._reset_card()
if rv == 0:
raise NoCardError()
elif rv < 0:
raise ProtocolError()
return rv
def _reset_card(self):
self._atr = None
rst_meth_map = {
'rts': self._sl.setRTS,
'dtr': self._sl.setDTR,
}
rst_val_map = {'+': 0, '-': 1}
try:
rst_meth = rst_meth_map[self._rst_pin[1:]]
rst_val = rst_val_map[self._rst_pin[0]]
except:
raise ValueError('Invalid reset pin %s' % self._rst_pin)
rst_meth(rst_val)
time.sleep(0.1) # 100 ms
self._sl.flushInput()
rst_meth(rst_val ^ 1)
b = self._rx_byte()
if not b:
return 0
if ord(b) != 0x3b:
return -1
self._dbg_print("TS: 0x%x Direct convention" % ord(b))
while ord(b) == 0x3b:
b = self._rx_byte()
if not b:
return -1
t0 = ord(b)
self._dbg_print("T0: 0x%x" % t0)
self._atr = [0x3b, ord(b)]
for i in range(4):
if t0 & (0x10 << i):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
for i in range(0, t0 & 0xf):
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("Historical = %x" % ord(b))
while True:
x = self._rx_byte()
if not x:
break
self._atr.append(ord(x))
self._dbg_print("Extra: %x" % ord(x))
return 1
def _dbg_print(self, s):
if self._debug:
print(s)
def _tx_byte(self, b):
self._sl.write(b)
r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
ord(b), '%02x' % ord(r) if r else '(nil)'))
def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data
during the time of tx of the string !!!"""
self._sl.write(s)
r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo
raise ProtocolError(
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _rx_byte(self):
return self._sl.read()
def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
pdu = h2b(pdu)
data_len = pdu[4] # P3
# Send first CLASS,INS,P1,P2,P3
self._tx_string(pdu[0:5])
# Wait ack which can be
# - INS: Command acked -> go ahead
# - 0x60: NULL, just wait some more
# - SW1: The card can apparently proceed ...
while True:
b = self._rx_byte()
if ord(b) == pdu[1]:
break
elif b != '\x60':
# Ok, it 'could' be SW1
sw1 = b
sw2 = self._rx_byte()
nil = self._rx_byte()
if (sw2 and not nil):
return '', b2h(sw1+sw2)
raise ProtocolError()
# Send data (if any)
if len(pdu) > 5:
self._tx_string(pdu[5:])
# Receive data (including SW !)
# length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ]
to_recv = data_len - len(pdu) + 5 + 2
data = bytes(0)
while (len(data) < to_recv):
b = self._rx_byte()
if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?)
continue
if not b:
break
data += b
# Split datafield from SW
if len(data) < 2:
return None, None
sw = data[-2:]
data = data[0:-2]
# Return value
return b2h(data), b2h(sw)
def __str__(self) -> str:
return "serial:%s" % (self._sl.name)