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