blob: 86d4443d4b65e9b728ef0c8af22ab9cc77936e4b [file] [log] [blame]
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +07001# -*- coding: utf-8 -*-
2
3""" pySim: Transport Link for 3GPP TS 27.007 compliant modems
4"""
5
6# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.com>
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 2 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#
21
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +070022import logging as log
23import serial
24import time
25import re
26
27from pySim.transport import LinkBase
28from pySim.exceptions import *
29
30# HACK: if somebody needs to debug this thing
31# log.root.setLevel(log.DEBUG)
32
33class ModemATCommandLink(LinkBase):
34 def __init__(self, device='/dev/ttyUSB0', baudrate=115200):
35 self._sl = serial.Serial(device, baudrate, timeout=5)
36 self._device = device
37 self._atr = None
38
39 # Trigger initial reset
40 self.reset_card()
41
42 def __del__(self):
43 self._sl.close()
44
45 def send_at_cmd(self, cmd):
46 # Convert from string to bytes, if needed
47 bcmd = cmd if type(cmd) is bytes else cmd.encode()
48 bcmd += b'\r'
49
50 # Send command to the modem
51 log.debug('Sending AT command: %s' % cmd)
52 try:
53 wlen = self._sl.write(bcmd)
54 assert(wlen == len(bcmd))
55 except:
56 raise ReaderError('Failed to send AT command: %s' % cmd)
57
58 # Give the modem some time...
59 time.sleep(0.3)
60
61 # Read the response
62 try:
63 # Skip characters sent back
64 self._sl.read(wlen)
65 # Read the rest
66 rsp = self._sl.read_all()
67
68 # Strip '\r\n'
69 rsp = rsp.strip()
70 # Split into a list
71 rsp = rsp.split(b'\r\n\r\n')
72 except:
73 raise ReaderError('Failed parse response to AT command: %s' % cmd)
74
75 log.debug('Got response from modem: %s' % rsp)
76 return rsp
77
78 def reset_card(self):
79 # Make sure that we can talk to the modem
80 if self.send_at_cmd('AT') != [b'OK']:
81 raise ReaderError('Failed to connect to modem')
82
83 # Reset the modem, just to be sure
84 if self.send_at_cmd('ATZ') != [b'OK']:
85 raise ReaderError('Failed to reset the modem')
86
87 # Make sure that generic SIM access is supported
88 if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
89 raise ReaderError('The modem does not seem to support SIM access')
90
91 log.info('Modem at \'%s\' is ready!' % self._device)
92
93 def connect(self):
94 pass # Nothing to do really ...
95
96 def disconnect(self):
97 pass # Nothing to do really ...
98
99 def wait_for_card(self, timeout=None, newcardonly=False):
100 pass # Nothing to do really ...
101
102 def send_apdu_raw(self, pdu):
103 # Prepare the command as described in 8.17
104 cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
105
106 # Send AT+CSIM command to the modem
107 # TODO: also handle +CME ERROR: <err>
108 rsp = self.send_at_cmd(cmd)
109 if len(rsp) != 2 or rsp[-1] != b'OK':
110 raise ReaderError('APDU transfer failed: %s' % str(rsp))
111 rsp = rsp[0] # Get rid of b'OK'
112
113 # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
114 try:
115 result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
116 (rsp_pdu_len, rsp_pdu) = result.groups()
117 except:
118 raise ReaderError('Failed to parse response from modem: %s' % rsp)
119
120 # TODO: make sure we have at least SW
121 data = rsp_pdu[:-4].decode()
122 sw = rsp_pdu[-4:].decode()
123 return data, sw