forked from public/pysim
bip/smpp2sim: TERMINAL PROFILE that matches what we do
pySim-smpp2sim sends "ff" * 32, that byte list tells every card the terminal has a display, a keypad, a second card slot, a radio it can query for location and NMR, five BIP bearers and six transport modes and a toaster and a dog according to TS 102 223 5.2 We only have the twelfth byte and one bit of the seventeenth and no dog. A card issues annoying weird things like PROVIDE LOCAL INFORMATION or UDP only because the profile said so, so stop pretending we know what any of that is. Annex T table T.1 lists what a Connected Entity (a CAT client that is not the modem) may announce. Announce that and the SMS-PP download and SEND SHORT MESSAGE bits the OTA path needs, only the bearer is obviously made up, it's the host TCP stack and 5.2 closest match is GPRS. We can still extend and change all of this, but for now something that works and constrains what the card asks for and is therefore actually reproducible is important. Change-Id: I91a60760fc3ad816b7385da8b26ec7330b462abd
This commit is contained in:
+2
-3
@@ -50,7 +50,7 @@ from smpp.pdu import pdu_types, operations, pdu_encoding
|
|||||||
|
|
||||||
from pySim.sms import SMS_DELIVER, SMS_SUBMIT, AddressField
|
from pySim.sms import SMS_DELIVER, SMS_SUBMIT, AddressField
|
||||||
|
|
||||||
from pySim.bip import Proact
|
from pySim.bip import Proact, terminal_profile
|
||||||
from pySim.transport import LinkBase, ProactiveHandler, argparse_add_reader_args, init_reader, ApduTracer
|
from pySim.transport import LinkBase, ProactiveHandler, argparse_add_reader_args, init_reader, ApduTracer
|
||||||
from pySim.commands import SimCardCommands
|
from pySim.commands import SimCardCommands
|
||||||
from pySim.cards import UiccCardBase
|
from pySim.cards import UiccCardBase
|
||||||
@@ -126,8 +126,7 @@ class MyServer:
|
|||||||
self.scc.sel_ctrl = "0004"
|
self.scc.sel_ctrl = "0004"
|
||||||
self.card.read_aids()
|
self.card.read_aids()
|
||||||
self.card.select_adf_by_aid(adf='usim')
|
self.card.select_adf_by_aid(adf='usim')
|
||||||
# FIXME: create a more realistic profile than ffffff
|
self.scc.terminal_profile(b2h(terminal_profile()))
|
||||||
self.scc.terminal_profile('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
|
|
||||||
# Connect the BIP relay inbound path to the card.
|
# Connect the BIP relay inbound path to the card.
|
||||||
# relay socket receives data -> ME initiated ENVELOPE EVENT DOWNLOA
|
# relay socket receives data -> ME initiated ENVELOPE EVENT DOWNLOA
|
||||||
# -> triggers RECEIVE DATA proactive session.
|
# -> triggers RECEIVE DATA proactive session.
|
||||||
|
|||||||
+37
-3
@@ -43,6 +43,35 @@ from pySim.cat import (ProactiveCommand, SendShortMessage, SMS_TPDU, SMSPPDownlo
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def terminal_profile(num_channels: int = 7) -> bytes:
|
||||||
|
"""TERMINAL PROFILE for what we implement, TS 102 223 5.2 and annex T.
|
||||||
|
|
||||||
|
Annex T table T.1 lists what a Connected Entity, a CAT client that is not the modem
|
||||||
|
which is pretty much what we are, may announce, and its inverse is what only a modem may announce.
|
||||||
|
"""
|
||||||
|
if not 0 <= num_channels <= ProactChannels.MAX_CHANNELS:
|
||||||
|
raise ValueError('num_channels must be 0..%u' % ProactChannels.MAX_CHANNELS)
|
||||||
|
profile = bytearray(32)
|
||||||
|
# 1 (Download): b1 profile download, b2+b5 SMS-PP data download. Both of the latter, per the
|
||||||
|
# note in TS 31.111 5.2: "several bits may need to be set to 1 for the support of the same
|
||||||
|
# facility ... because of backward compatibility with SAT". The relay is OTA over SMS-PP.
|
||||||
|
profile[0] = 0x01 | 0x02 | 0x10
|
||||||
|
profile[1] = 0x01 # 2 (Other): b1 command result
|
||||||
|
profile[2] = 0x80 # 3: b8 REFRESH (empty result is a valid answer, 6.4.7)
|
||||||
|
profile[3] = 0x02 # 4: b2 SEND SHORT MESSAGE (the OTA response path)
|
||||||
|
profile[4] = 0x01 # 5: b1 SET UP EVENT LIST
|
||||||
|
profile[5] = 0x04 | 0x08 # 6: b3 Event Data available, b4 Event Channel status
|
||||||
|
# 12 (class "e"): b1..b5 OPEN CHANNEL, CLOSE CHANNEL, RECEIVE DATA, SEND DATA, GET CHANNEL
|
||||||
|
# STATUS.
|
||||||
|
profile[11] = 0x1f
|
||||||
|
# 13 (class "e" supported bearers): b2 GPRS, and b6..b8 the number of channels.
|
||||||
|
profile[12] = 0x02 | (num_channels << 5)
|
||||||
|
profile[13] = 0x40 | 0x20 # 14: b6 no display capability, b7 no keypad available
|
||||||
|
profile[16] = 0x01 # 15: b1 TCP, UICC in client mode, remote connection
|
||||||
|
return bytes(profile)
|
||||||
|
|
||||||
|
|
||||||
class ProactChannel:
|
class ProactChannel:
|
||||||
"""One BIP channel, TS 102 223 class "e", backed by a blocking TCP socket.
|
"""One BIP channel, TS 102 223 class "e", backed by a blocking TCP socket.
|
||||||
|
|
||||||
@@ -178,13 +207,18 @@ class ProactChannels:
|
|||||||
thread when data arrives in an empty Rx buffer. Proact turns it into an
|
thread when data arrives in an empty Rx buffer. Proact turns it into an
|
||||||
ENVELOPE EVENT DOWNLOAD (data available).
|
ENVELOPE EVENT DOWNLOAD (data available).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# TS 102 223 8.56 channel identifier in 3 bits as "1 to 7", 0 == no channel available
|
||||||
|
# TERMINAL PROFILE has to agree with byte 13 , "number of channels supported by terminal"
|
||||||
|
MAX_CHANNELS = 7
|
||||||
|
|
||||||
def __init__(self, on_data_available=None):
|
def __init__(self, on_data_available=None):
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
self._on_data_available = on_data_available
|
self._on_data_available = on_data_available
|
||||||
|
|
||||||
def channel_create(self) -> ProactChannel:
|
def channel_create(self) -> ProactChannel:
|
||||||
"""Create a new proactive channel, allocating its integer number."""
|
"""Create a new proactive channel, allocating its integer number."""
|
||||||
for i in range(1, 8):
|
for i in range(1, self.MAX_CHANNELS + 1):
|
||||||
if not i in self.channels:
|
if not i in self.channels:
|
||||||
self.channels[i] = ProactChannel(self, i)
|
self.channels[i] = ProactChannel(self, i)
|
||||||
return self.channels[i]
|
return self.channels[i]
|
||||||
@@ -223,8 +257,8 @@ class Proact(ProactiveHandler):
|
|||||||
"""Answer anything this handler has no specific handler for.
|
"""Answer anything this handler has no specific handler for.
|
||||||
|
|
||||||
A card coming up will usually issue PROVIDE LOCAL INFORMATION,
|
A card coming up will usually issue PROVIDE LOCAL INFORMATION,
|
||||||
POLL INTERVAL or TIMER MANAGEMENT before it gets anywhere near a BIP channel
|
POLL INTERVAL or TIMER MANAGEMENT before it gets anywhere near a BIP channel,
|
||||||
because we claim to support every feature flag there is.
|
whatever the TERMINAL PROFILE announces.
|
||||||
|
|
||||||
Note that this is not the spec-correct answer. TS 102 223 6.8.7
|
Note that this is not the spec-correct answer. TS 102 223 6.8.7
|
||||||
says a successful TERMINAL RESPONSE to PROVIDE LOCAL INFORMATION "shall" carry the
|
says a successful TERMINAL RESPONSE to PROVIDE LOCAL INFORMATION "shall" carry the
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from pySim.cat import (ProactiveCommand, CommandDetails, DeviceIdentities,
|
|||||||
OtherAddress, ChannelData, ChannelDataLength, ChannelStatus,
|
OtherAddress, ChannelData, ChannelDataLength, ChannelStatus,
|
||||||
Result)
|
Result)
|
||||||
|
|
||||||
from pySim.bip import Proact
|
from pySim.bip import Proact, ProactChannels, terminal_profile
|
||||||
|
|
||||||
|
|
||||||
class _EchoServer:
|
class _EchoServer:
|
||||||
@@ -329,6 +329,44 @@ class OpenChannelRefusalTest(unittest.TestCase):
|
|||||||
self.assertEqual(self.proact.channels.channels, {}) # channel given back
|
self.assertEqual(self.proact.channels.channels, {}) # channel given back
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalProfileTest(unittest.TestCase):
|
||||||
|
"""TS 102 223 5.2, one bit per CAT facility"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.profile = terminal_profile()
|
||||||
|
|
||||||
|
def byte(self, n):
|
||||||
|
return self.profile[n - 1] # 1-based, as 5.2 numbers them
|
||||||
|
|
||||||
|
def test_announced(self):
|
||||||
|
self.assertEqual(len(self.profile), 32)
|
||||||
|
self.assertEqual(self.byte(1), 0x13) # profile download, SMS-PP download b2+b5
|
||||||
|
self.assertEqual(self.byte(4), 0x02) # SEND SHORT MESSAGE
|
||||||
|
self.assertEqual(self.byte(5) & 0x01, 0x01) # SET UP EVENT LIST
|
||||||
|
self.assertEqual(self.byte(6), 0x0c) # events: data available, channel status
|
||||||
|
self.assertEqual(self.byte(12), 0x1f) # OPEN/CLOSE CHANNEL, RECEIVE/SEND DATA, STATUS
|
||||||
|
self.assertEqual(self.byte(13) >> 5, ProactChannels.MAX_CHANNELS)
|
||||||
|
self.assertEqual(self.byte(14), 0x60) # class ND, class NK
|
||||||
|
self.assertEqual(self.byte(17), 0x01) # TCP, UICC client mode, remote
|
||||||
|
|
||||||
|
def test_not_announced(self):
|
||||||
|
self.assertEqual(self.byte(3) & 0x60, 0) # POLL INTERVAL, POLLING OFF
|
||||||
|
self.assertEqual(self.byte(4) & 0xc0, 0) # PROVIDE LOCAL INFORMATION, NMR
|
||||||
|
self.assertEqual(self.byte(12) & 0xe0, 0) # SERVICE SEARCH/INFORMATION, DECLARE SERVICE
|
||||||
|
self.assertEqual(self.byte(14) & 0x1f, 0) # no characters down the display
|
||||||
|
for n in (7, 9, 10, 11, 15, 16, 18): # class "a", class "d", display, ESN/IMEISV
|
||||||
|
self.assertEqual(self.byte(n), 0)
|
||||||
|
|
||||||
|
def test_channel_count(self):
|
||||||
|
self.assertEqual(terminal_profile(3)[12] >> 5, 3)
|
||||||
|
with self.assertRaises(ValueError): # 8.56: 1 to 7
|
||||||
|
terminal_profile(8)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
|
||||||
class BipSinkTest(unittest.TestCase):
|
class BipSinkTest(unittest.TestCase):
|
||||||
"""Both sinks are optional, a driver with no SMS path at all must not crash and burn
|
"""Both sinks are optional, a driver with no SMS path at all must not crash and burn
|
||||||
with a card that sends one, and one that has one must get the PDU."""
|
with a card that sends one, and one that has one must get the PDU."""
|
||||||
|
|||||||
Reference in New Issue
Block a user