blob: 7544d2ec4d76f7bf9291d5742b84bff65134fa5a [file] [log] [blame]
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +07001# -*- coding: utf-8 -*-
2
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +07003# Copyright (C) 2018 Vadim Yanitskiy <axilirator@gmail.com>
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070019import select
20import struct
21import socket
22import os
Philipp Maier8c823782023-10-23 10:44:44 +020023import argparse
Harald Weltef9f8d7a2023-07-09 17:06:16 +020024from typing import Optional
Harald Welteab6897c2023-07-09 16:21:23 +020025
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070026from pySim.transport import LinkBase
27from pySim.exceptions import *
Harald Weltef9f8d7a2023-07-09 17:06:16 +020028from pySim.utils import h2b, b2h, Hexstr, ResTuple
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070029
Harald Weltec91085e2022-02-10 18:05:45 +010030
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +070031class L1CTLMessage:
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070032
Harald Weltec91085e2022-02-10 18:05:45 +010033 # Every (encoded) L1CTL message has the following structure:
34 # - msg_length (2 bytes, net order)
35 # - l1ctl_hdr (packed structure)
36 # - msg_type
37 # - flags
38 # - padding (2 spare bytes)
39 # - ... payload ...
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070040
Harald Weltec91085e2022-02-10 18:05:45 +010041 def __init__(self, msg_type, flags=0x00):
42 # Init L1CTL message header
43 self.data = struct.pack("BBxx", msg_type, flags)
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070044
Harald Weltec91085e2022-02-10 18:05:45 +010045 def gen_msg(self):
46 return struct.pack("!H", len(self.data)) + self.data
47
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070048
49class L1CTLMessageReset(L1CTLMessage):
50
Harald Weltec91085e2022-02-10 18:05:45 +010051 # L1CTL message types
52 L1CTL_RESET_REQ = 0x0d
53 L1CTL_RESET_IND = 0x07
54 L1CTL_RESET_CONF = 0x0e
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070055
Harald Weltec91085e2022-02-10 18:05:45 +010056 # Reset types
57 L1CTL_RES_T_BOOT = 0x00
58 L1CTL_RES_T_FULL = 0x01
59 L1CTL_RES_T_SCHED = 0x02
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070060
Harald Weltec91085e2022-02-10 18:05:45 +010061 def __init__(self, type=L1CTL_RES_T_FULL):
62 super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
63 self.data += struct.pack("Bxxx", type)
64
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070065
66class L1CTLMessageSIM(L1CTLMessage):
67
Harald Weltec91085e2022-02-10 18:05:45 +010068 # SIM related message types
69 L1CTL_SIM_REQ = 0x16
70 L1CTL_SIM_CONF = 0x17
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070071
Harald Weltec91085e2022-02-10 18:05:45 +010072 def __init__(self, pdu):
73 super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
74 self.data += pdu
75
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070076
77class CalypsoSimLink(LinkBase):
Harald Weltec91085e2022-02-10 18:05:45 +010078 """Transport Link for Calypso based phones."""
Harald Weltebaec4e92023-11-03 11:49:54 +010079 name = 'Calypso-based (OsmocomBB) reader'
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070080
Harald Welte0f177c12023-12-17 12:38:29 +010081 def __init__(self, opts: argparse.Namespace = argparse.Namespace(osmocon_sock="/tmp/osmocom_l2"), **kwargs):
82 sock_path = opts.osmocon_sock
Harald Weltec91085e2022-02-10 18:05:45 +010083 super().__init__(**kwargs)
84 # Make sure that a given socket path exists
85 if not os.path.exists(sock_path):
86 raise ReaderError(
87 "There is no such ('%s') UNIX socket" % sock_path)
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070088
Harald Weltec91085e2022-02-10 18:05:45 +010089 print("Connecting to osmocon at '%s'..." % sock_path)
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070090
Harald Weltec91085e2022-02-10 18:05:45 +010091 # Establish a client connection
92 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
93 self.sock.connect(sock_path)
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +070094
Philipp Maier6bfa8a82023-10-09 13:32:49 +020095 # Remember socket path
96 self._sock_path = sock_path
97
Harald Weltec91085e2022-02-10 18:05:45 +010098 def __del__(self):
99 self.sock.close()
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700100
Harald Welteab6897c2023-07-09 16:21:23 +0200101 def wait_for_rsp(self, exp_len: int = 128):
Harald Weltec91085e2022-02-10 18:05:45 +0100102 # Wait for incoming data (timeout is 3 seconds)
103 s, _, _ = select.select([self.sock], [], [], 3.0)
104 if not s:
105 raise ReaderError("Timeout waiting for card response")
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700106
Harald Weltec91085e2022-02-10 18:05:45 +0100107 # Receive expected amount of bytes from osmocon
108 rsp = self.sock.recv(exp_len)
109 return rsp
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700110
Harald Weltec91085e2022-02-10 18:05:45 +0100111 def reset_card(self):
112 # Request FULL reset
113 req_msg = L1CTLMessageReset()
114 self.sock.send(req_msg.gen_msg())
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700115
Harald Weltec91085e2022-02-10 18:05:45 +0100116 # Wait for confirmation
117 rsp = self.wait_for_rsp()
118 rsp_msg = struct.unpack_from("!HB", rsp)
119 if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
120 raise ReaderError("Failed to reset Calypso PHY")
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700121
Harald Weltec91085e2022-02-10 18:05:45 +0100122 def connect(self):
123 self.reset_card()
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700124
Harald Weltec91085e2022-02-10 18:05:45 +0100125 def disconnect(self):
126 pass # Nothing to do really ...
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700127
Harald Welteab6897c2023-07-09 16:21:23 +0200128 def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False):
Harald Weltec91085e2022-02-10 18:05:45 +0100129 pass # Nothing to do really ...
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700130
Harald Weltef9f8d7a2023-07-09 17:06:16 +0200131 def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple:
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700132
Harald Weltec91085e2022-02-10 18:05:45 +0100133 # Request FULL reset
134 req_msg = L1CTLMessageSIM(h2b(pdu))
135 self.sock.send(req_msg.gen_msg())
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700136
Harald Weltec91085e2022-02-10 18:05:45 +0100137 # Read message length first
138 rsp = self.wait_for_rsp(struct.calcsize("!H"))
139 msg_len = struct.unpack_from("!H", rsp)[0]
140 if msg_len < struct.calcsize("BBxx"):
141 raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700142
Harald Weltec91085e2022-02-10 18:05:45 +0100143 # Read the whole message then
144 rsp = self.sock.recv(msg_len)
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700145
Harald Weltec91085e2022-02-10 18:05:45 +0100146 # Verify L1CTL header
147 hdr = struct.unpack_from("BBxx", rsp)
148 if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
149 raise ReaderError("Unexpected L1CTL message received")
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700150
Harald Weltec91085e2022-02-10 18:05:45 +0100151 # Verify the payload length
152 offset = struct.calcsize("BBxx")
153 if len(rsp) <= offset:
154 raise ProtocolError("Empty response from SIM?!?")
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700155
Harald Weltec91085e2022-02-10 18:05:45 +0100156 # Omit L1CTL header
157 rsp = rsp[offset:]
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700158
Harald Weltec91085e2022-02-10 18:05:45 +0100159 # Unpack data and SW
160 data = rsp[:-2]
161 sw = rsp[-2:]
Vadim Yanitskiy1381ff12018-10-27 01:48:15 +0700162
Harald Weltec91085e2022-02-10 18:05:45 +0100163 return b2h(data), b2h(sw)
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200164
Philipp Maier58e89eb2023-10-10 11:59:03 +0200165 def __str__(self) -> str:
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200166 return "osmocon:%s" % (self._sock_path)
Philipp Maier8c823782023-10-23 10:44:44 +0200167
168 @staticmethod
169 def argparse_add_reader_args(arg_parser: argparse.ArgumentParser):
Harald Welte0ecbf632023-11-03 12:38:42 +0100170 osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader', """Use an OsmocomBB compatible phone
171to access the SIM inserted to the phone SIM slot. This will require you to run the OsmocomBB firmware inside
172the phone (can be ram-loaded). It also requires that you run the ``osmocon`` program, which provides a unix
173domain socket to which this reader driver can attach.""")
Philipp Maier8c823782023-10-23 10:44:44 +0200174 osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None,
175 help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)')