aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xpySim-prog.py8
-rwxr-xr-xpySim-read.py8
-rw-r--r--pySim/transport/modem_atcmd.py126
-rw-r--r--pySim/utils.py4
4 files changed, 146 insertions, 0 deletions
diff --git a/pySim-prog.py b/pySim-prog.py
index f707c57..601f980 100755
--- a/pySim-prog.py
+++ b/pySim-prog.py
@@ -61,6 +61,14 @@ def parse_options():
help="Which PC/SC reader number for SIM access",
default=None,
)
+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
+ default=None,
+ )
+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
+ help="Baudrate used for modem's port [default: %default]",
+ default=115200,
+ )
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
default=None,
diff --git a/pySim-read.py b/pySim-read.py
index df21531..e49a907 100755
--- a/pySim-read.py
+++ b/pySim-read.py
@@ -53,6 +53,14 @@ def parse_options():
help="Which PC/SC reader number for SIM access",
default=None,
)
+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
+ default=None,
+ )
+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
+ help="Baudrate used for modem's port [default: %default]",
+ default=115200,
+ )
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
default=None,
diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py
new file mode 100644
index 0000000..742ae8d
--- /dev/null
+++ b/pySim/transport/modem_atcmd.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+""" pySim: Transport Link for 3GPP TS 27.007 compliant modems
+"""
+
+# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.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/>.
+#
+
+from __future__ import absolute_import
+
+import logging as log
+import serial
+import time
+import re
+
+from pySim.transport import LinkBase
+from pySim.exceptions import *
+
+# HACK: if somebody needs to debug this thing
+# log.root.setLevel(log.DEBUG)
+
+class ModemATCommandLink(LinkBase):
+ def __init__(self, device='/dev/ttyUSB0', baudrate=115200):
+ self._sl = serial.Serial(device, baudrate, timeout=5)
+ self._device = device
+ self._atr = None
+
+ # Trigger initial reset
+ self.reset_card()
+
+ def __del__(self):
+ self._sl.close()
+
+ def send_at_cmd(self, cmd):
+ # Convert from string to bytes, if needed
+ bcmd = cmd if type(cmd) is bytes else cmd.encode()
+ bcmd += b'\r'
+
+ # Send command to the modem
+ log.debug('Sending AT command: %s' % cmd)
+ try:
+ wlen = self._sl.write(bcmd)
+ assert(wlen == len(bcmd))
+ except:
+ raise ReaderError('Failed to send AT command: %s' % cmd)
+
+ # Give the modem some time...
+ time.sleep(0.3)
+
+ # Read the response
+ try:
+ # Skip characters sent back
+ self._sl.read(wlen)
+ # Read the rest
+ rsp = self._sl.read_all()
+
+ # Strip '\r\n'
+ rsp = rsp.strip()
+ # Split into a list
+ rsp = rsp.split(b'\r\n\r\n')
+ except:
+ raise ReaderError('Failed parse response to AT command: %s' % cmd)
+
+ log.debug('Got response from modem: %s' % rsp)
+ return rsp
+
+ def reset_card(self):
+ # Make sure that we can talk to the modem
+ if self.send_at_cmd('AT') != [b'OK']:
+ raise ReaderError('Failed to connect to modem')
+
+ # Reset the modem, just to be sure
+ if self.send_at_cmd('ATZ') != [b'OK']:
+ raise ReaderError('Failed to reset the modem')
+
+ # Make sure that generic SIM access is supported
+ if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
+ raise ReaderError('The modem does not seem to support SIM access')
+
+ log.info('Modem at \'%s\' is ready!' % self._device)
+
+ def connect(self):
+ pass # Nothing to do really ...
+
+ def disconnect(self):
+ pass # Nothing to do really ...
+
+ def wait_for_card(self, timeout=None, newcardonly=False):
+ pass # Nothing to do really ...
+
+ def send_apdu_raw(self, pdu):
+ # Prepare the command as described in 8.17
+ cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
+
+ # Send AT+CSIM command to the modem
+ # TODO: also handle +CME ERROR: <err>
+ rsp = self.send_at_cmd(cmd)
+ if len(rsp) != 2 or rsp[-1] != b'OK':
+ raise ReaderError('APDU transfer failed: %s' % str(rsp))
+ rsp = rsp[0] # Get rid of b'OK'
+
+ # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
+ try:
+ result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
+ (rsp_pdu_len, rsp_pdu) = result.groups()
+ except:
+ raise ReaderError('Failed to parse response from modem: %s' % rsp)
+
+ # TODO: make sure we have at least SW
+ data = rsp_pdu[:-4].decode()
+ sw = rsp_pdu[-4:].decode()
+ return data, sw
diff --git a/pySim/utils.py b/pySim/utils.py
index 20eb5a8..496b918 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -485,6 +485,10 @@ def init_reader(opts):
print("Using Calypso-based (OsmocomBB) reader interface")
from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=opts.osmocon_sock)
+ elif opts.modem_dev is not None:
+ print("Using modem for Generic SIM Access (3GPP TS 27.007)")
+ from pySim.transport.modem_atcmd import ModemATCommandLink
+ sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud)
else: # Serial reader is default
print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink