<p>fixeria has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/18159">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Implement Generic SIM Access interface as per 3GPP TS 27.007<br><br>According to 3GPP TS 27.007, sections 8.17 and 8.18, the modem<br>may *optionally* provide Generic and/or Restricted SIM Access<br>to the TE (Terminal Equipment) by means of the AT commands.<br>This basically means that a modem can act as a card reader.<br><br>Generic SIM Access allows the TE to send raw PDUs in the format<br>as described in 3GPP TS 51.011 directly to the SIM card, while<br>Restricted SIM Access is more limited, and thus is not really<br>interesting to us.<br><br>This change implements a new transport called ModemATCommandLink,<br>so using it a SIM card can be read and/or programmed without the<br>need to remove it from the modem's socket. A downside of this<br>approach is relatively slow I/O speed compared to PC/SC readers.<br><br>Tested with Quectel EC20:<br><br> $ ./pySim-read.py --modem-dev /dev/ttyUSB2<br><br>Change-Id: I20bc00315e2c7c298f46283852865c1416047bc6<br>Signed-off-by: Vadim Yanitskiy <axilirator@gmail.com><br>---<br>M pySim-prog.py<br>M pySim-read.py<br>A pySim/transport/modem_atcmd.py<br>3 files changed, 147 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/59/18159/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/pySim-prog.py b/pySim-prog.py</span><br><span>index e3045a6..892a565 100755</span><br><span>--- a/pySim-prog.py</span><br><span>+++ b/pySim-prog.py</span><br><span>@@ -61,6 +61,14 @@</span><br><span> help="Which PC/SC reader number for SIM access",</span><br><span> default=None,</span><br><span> )</span><br><span style="color: hsl(120, 100%, 40%);">+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",</span><br><span style="color: hsl(120, 100%, 40%);">+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",</span><br><span style="color: hsl(120, 100%, 40%);">+ default=None,</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span style="color: hsl(120, 100%, 40%);">+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",</span><br><span style="color: hsl(120, 100%, 40%);">+ help="Baudrate used for modem's port [default: %default]",</span><br><span style="color: hsl(120, 100%, 40%);">+ default=115200,</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span> parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",</span><br><span> help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",</span><br><span> default=None,</span><br><span>@@ -693,6 +701,11 @@</span><br><span> % opts.osmocon_sock)</span><br><span> from pySim.transport.calypso import CalypsoSimLink</span><br><span> sl = CalypsoSimLink(sock_path=opts.osmocon_sock)</span><br><span style="color: hsl(120, 100%, 40%);">+ elif opts.modem_dev is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+ print("Using modem (dev=%s, speed=%d) for Generic SIM Access (3GPP TS 27.007)"</span><br><span style="color: hsl(120, 100%, 40%);">+ % (opts.modem_dev, opts.modem_baud))</span><br><span style="color: hsl(120, 100%, 40%);">+ from pySim.transport.modem_atcmd import ModemATCommandLink</span><br><span style="color: hsl(120, 100%, 40%);">+ sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud)</span><br><span> else: # Serial reader is default</span><br><span> print("Using serial reader (port=%s, baudrate=%d) interface"</span><br><span> % (opts.device, opts.baudrate))</span><br><span>diff --git a/pySim-read.py b/pySim-read.py</span><br><span>index 33e93a7..9713fb9 100755</span><br><span>--- a/pySim-read.py</span><br><span>+++ b/pySim-read.py</span><br><span>@@ -53,6 +53,14 @@</span><br><span> help="Which PC/SC reader number for SIM access",</span><br><span> default=None,</span><br><span> )</span><br><span style="color: hsl(120, 100%, 40%);">+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",</span><br><span style="color: hsl(120, 100%, 40%);">+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",</span><br><span style="color: hsl(120, 100%, 40%);">+ default=None,</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span style="color: hsl(120, 100%, 40%);">+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",</span><br><span style="color: hsl(120, 100%, 40%);">+ help="Baudrate used for modem's port [default: %default]",</span><br><span style="color: hsl(120, 100%, 40%);">+ default=115200,</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span> parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",</span><br><span> help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",</span><br><span> default=None,</span><br><span>@@ -82,6 +90,11 @@</span><br><span> % opts.osmocon_sock)</span><br><span> from pySim.transport.calypso import CalypsoSimLink</span><br><span> sl = CalypsoSimLink(sock_path=opts.osmocon_sock)</span><br><span style="color: hsl(120, 100%, 40%);">+ elif opts.modem_dev is not None:</span><br><span style="color: hsl(120, 100%, 40%);">+ print("Using modem (dev=%s, speed=%d) for Generic SIM Access (3GPP TS 27.007)"</span><br><span style="color: hsl(120, 100%, 40%);">+ % (opts.modem_dev, opts.modem_baud))</span><br><span style="color: hsl(120, 100%, 40%);">+ from pySim.transport.modem_atcmd import ModemATCommandLink</span><br><span style="color: hsl(120, 100%, 40%);">+ sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud)</span><br><span> else: # Serial reader is default</span><br><span> print("Using serial reader (port=%s, baudrate=%d) interface"</span><br><span> % (opts.device, opts.baudrate))</span><br><span>diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py</span><br><span>new file mode 100644</span><br><span>index 0000000..4594325</span><br><span>--- /dev/null</span><br><span>+++ b/pySim/transport/modem_atcmd.py</span><br><span>@@ -0,0 +1,121 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/usr/bin/env python</span><br><span style="color: hsl(120, 100%, 40%);">+# -*- coding: utf-8 -*-</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+""" pySim: Transport Link for 3GPP TS 27.007 compliant modems</span><br><span style="color: hsl(120, 100%, 40%);">+"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+# the Free Software Foundation, either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+# (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program. If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from __future__ import absolute_import</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import logging as log</span><br><span style="color: hsl(120, 100%, 40%);">+import serial</span><br><span style="color: hsl(120, 100%, 40%);">+import time</span><br><span style="color: hsl(120, 100%, 40%);">+import re</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.transport import LinkBase</span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.exceptions import *</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class ModemATCommandLink(LinkBase):</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, device='/dev/ttyUSB0', baudrate=115200):</span><br><span style="color: hsl(120, 100%, 40%);">+ self._sl = serial.Serial(device, baudrate, timeout=5)</span><br><span style="color: hsl(120, 100%, 40%);">+ self._device = device</span><br><span style="color: hsl(120, 100%, 40%);">+ self._atr = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # HACK: if somebody needs to debug this thing</span><br><span style="color: hsl(120, 100%, 40%);">+ # log.root.setLevel(log.DEBUG)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __del__(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ self._sl.close()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def send_at_cmd(self, cmd):</span><br><span style="color: hsl(120, 100%, 40%);">+ # Convert from string to bytes, if needed</span><br><span style="color: hsl(120, 100%, 40%);">+ if type(cmd) is str:</span><br><span style="color: hsl(120, 100%, 40%);">+ bcmd = str.encode(cmd)</span><br><span style="color: hsl(120, 100%, 40%);">+ bcmd += b'\r'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Send command to the modem</span><br><span style="color: hsl(120, 100%, 40%);">+ log.debug('Sending AT command: %s' % cmd)</span><br><span style="color: hsl(120, 100%, 40%);">+ try:</span><br><span style="color: hsl(120, 100%, 40%);">+ wlen = self._sl.write(bcmd)</span><br><span style="color: hsl(120, 100%, 40%);">+ assert(wlen == len(bcmd))</span><br><span style="color: hsl(120, 100%, 40%);">+ except:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('Failed to sent AT command: %s' % cmd)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Give the modem some time...</span><br><span style="color: hsl(120, 100%, 40%);">+ time.sleep(0.3)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Read the response</span><br><span style="color: hsl(120, 100%, 40%);">+ try:</span><br><span style="color: hsl(120, 100%, 40%);">+ # Skip characters sent back</span><br><span style="color: hsl(120, 100%, 40%);">+ self._sl.read(wlen)</span><br><span style="color: hsl(120, 100%, 40%);">+ # Read the rest</span><br><span style="color: hsl(120, 100%, 40%);">+ rsp = self._sl.read_all()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Strip '\r\n'</span><br><span style="color: hsl(120, 100%, 40%);">+ rsp = rsp.strip()</span><br><span style="color: hsl(120, 100%, 40%);">+ # Split into a list</span><br><span style="color: hsl(120, 100%, 40%);">+ rsp = rsp.split(b'\r\n\r\n')</span><br><span style="color: hsl(120, 100%, 40%);">+ except:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('Failed parse response to AT command: %s' % cmd)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ log.debug('Got response from modem: %s' % rsp)</span><br><span style="color: hsl(120, 100%, 40%);">+ return rsp</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def connect(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ # Make sure that we can talk to the modem</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.send_at_cmd('AT') != [b'OK']:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('Failed to connect to modem')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Reset the modem, just to be sure</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.send_at_cmd('ATZ') != [b'OK']:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('Failed to reset the modem')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Make sure that generic SIM access is supported</span><br><span style="color: hsl(120, 100%, 40%);">+ if self.send_at_cmd('AT+CSIM=?') != [b'OK']:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('The modem does not seem to support SIM access')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ log.info('Modem at \'%s\' is ready!' % device)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def disconnect(self):</span><br><span style="color: hsl(120, 100%, 40%);">+ pass # Nothing to do really ...</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def wait_for_card(self, timeout=None, newcardonly=False):</span><br><span style="color: hsl(120, 100%, 40%);">+ pass # Nothing to do really ...</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def send_apdu_raw(self, pdu):</span><br><span style="color: hsl(120, 100%, 40%);">+ # Prepare the command as described in 8.17</span><br><span style="color: hsl(120, 100%, 40%);">+ cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Send AT+CSIM command to the modem</span><br><span style="color: hsl(120, 100%, 40%);">+ # TODO: also handle +CME ERROR: <err></span><br><span style="color: hsl(120, 100%, 40%);">+ rsp = self.send_at_cmd(cmd)</span><br><span style="color: hsl(120, 100%, 40%);">+ if len(rsp) != 2 or rsp[-1] != b'OK':</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('APDU transfer failed: %s' % str(rsp))</span><br><span style="color: hsl(120, 100%, 40%);">+ rsp = rsp[0] # Get rid of b'OK'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # Make sure that the response has format: b'+CSIM: %d,\"%s\"'</span><br><span style="color: hsl(120, 100%, 40%);">+ try:</span><br><span style="color: hsl(120, 100%, 40%);">+ result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)</span><br><span style="color: hsl(120, 100%, 40%);">+ (rsp_pdu_len, rsp_pdu) = result.groups()</span><br><span style="color: hsl(120, 100%, 40%);">+ except:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ReaderError('Failed to parse response from modem: %s' % rsp)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # TODO: make sure we have at least SW</span><br><span style="color: hsl(120, 100%, 40%);">+ data = rsp_pdu[:-4].decode()</span><br><span style="color: hsl(120, 100%, 40%);">+ sw = rsp_pdu[-4:].decode()</span><br><span style="color: hsl(120, 100%, 40%);">+ return data, sw</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/18159">change 18159</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/pysim/+/18159"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: pysim </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I20bc00315e2c7c298f46283852865c1416047bc6 </div>
<div style="display:none"> Gerrit-Change-Number: 18159 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: fixeria <axilirator@gmail.com> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>