blob: 742ae8d46a328f8862d5a3170775409b37fa46e7 [file] [log] [blame]
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +07001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4""" pySim: Transport Link for 3GPP TS 27.007 compliant modems
5"""
6
7# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.com>
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22
23from __future__ import absolute_import
24
25import logging as log
26import serial
27import time
28import re
29
30from pySim.transport import LinkBase
31from pySim.exceptions import *
32
33# HACK: if somebody needs to debug this thing
34# log.root.setLevel(log.DEBUG)
35
36class ModemATCommandLink(LinkBase):
37 def __init__(self, device='/dev/ttyUSB0', baudrate=115200):
38 self._sl = serial.Serial(device, baudrate, timeout=5)
39 self._device = device
40 self._atr = None
41
42 # Trigger initial reset
43 self.reset_card()
44
45 def __del__(self):
46 self._sl.close()
47
48 def send_at_cmd(self, cmd):
49 # Convert from string to bytes, if needed
50 bcmd = cmd if type(cmd) is bytes else cmd.encode()
51 bcmd += b'\r'
52
53 # Send command to the modem
54 log.debug('Sending AT command: %s' % cmd)
55 try:
56 wlen = self._sl.write(bcmd)
57 assert(wlen == len(bcmd))
58 except:
59 raise ReaderError('Failed to send AT command: %s' % cmd)
60
61 # Give the modem some time...
62 time.sleep(0.3)
63
64 # Read the response
65 try:
66 # Skip characters sent back
67 self._sl.read(wlen)
68 # Read the rest
69 rsp = self._sl.read_all()
70
71 # Strip '\r\n'
72 rsp = rsp.strip()
73 # Split into a list
74 rsp = rsp.split(b'\r\n\r\n')
75 except:
76 raise ReaderError('Failed parse response to AT command: %s' % cmd)
77
78 log.debug('Got response from modem: %s' % rsp)
79 return rsp
80
81 def reset_card(self):
82 # Make sure that we can talk to the modem
83 if self.send_at_cmd('AT') != [b'OK']:
84 raise ReaderError('Failed to connect to modem')
85
86 # Reset the modem, just to be sure
87 if self.send_at_cmd('ATZ') != [b'OK']:
88 raise ReaderError('Failed to reset the modem')
89
90 # Make sure that generic SIM access is supported
91 if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
92 raise ReaderError('The modem does not seem to support SIM access')
93
94 log.info('Modem at \'%s\' is ready!' % self._device)
95
96 def connect(self):
97 pass # Nothing to do really ...
98
99 def disconnect(self):
100 pass # Nothing to do really ...
101
102 def wait_for_card(self, timeout=None, newcardonly=False):
103 pass # Nothing to do really ...
104
105 def send_apdu_raw(self, pdu):
106 # Prepare the command as described in 8.17
107 cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
108
109 # Send AT+CSIM command to the modem
110 # TODO: also handle +CME ERROR: <err>
111 rsp = self.send_at_cmd(cmd)
112 if len(rsp) != 2 or rsp[-1] != b'OK':
113 raise ReaderError('APDU transfer failed: %s' % str(rsp))
114 rsp = rsp[0] # Get rid of b'OK'
115
116 # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
117 try:
118 result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
119 (rsp_pdu_len, rsp_pdu) = result.groups()
120 except:
121 raise ReaderError('Failed to parse response from modem: %s' % rsp)
122
123 # TODO: make sure we have at least SW
124 data = rsp_pdu[:-4].decode()
125 sw = rsp_pdu[-4:].decode()
126 return data, sw