diff --git a/pySim/bip.py b/pySim/bip.py index 549655a8..1cd3ba04 100644 --- a/pySim/bip.py +++ b/pySim/bip.py @@ -39,10 +39,16 @@ from pySim.sms import SMS_DELIVER, SMS_SUBMIT, AddressField from pySim.cat import (ProactiveCommand, SendShortMessage, SMS_TPDU, SMSPPDownload, BearerDescription, DeviceIdentities, Address, OtherAddress, UiccTransportLevel, BufferSize, ChannelStatus, ChannelData, - ChannelDataLength, EventList, EventDownload, Result) + ChannelDataLength, EventList, EventDownload, Result, + CommandDetails, LocationInformation) logger = logging.getLogger(__name__) +# PROVIDE LOCAL INFORMATION location, GERAN TS 31.111 8.19.1 +# - 3 byte PLMN of TS 24.008 10.5.1.3 -> 262-01 +# - 2 byte LAC and a 2 byte cid. +DEFAULT_LOCATION = h2b('62f21000010001') + def terminal_profile(num_channels: int = 7) -> bytes: """TERMINAL PROFILE for what we implement, TS 102 223 5.2 and annex T. @@ -247,12 +253,29 @@ class Proact(ProactiveHandler): sms_sink: callback(pdu), called with the SMPP deliver_sm of a SEND SHORT MESSAGE the card issued; pySim-smpp2sim.py hands it to its SMPP server. None: the SMS is logged and dropped. + location: Location information returned in PROVIDE LOCAL INFORMATION (location). """ - def __init__(self, data_available_sink=None, sms_sink=None): + def __init__(self, data_available_sink=None, sms_sink=None, location: bytes = DEFAULT_LOCATION): self.data_available_sink = data_available_sink self.sms_sink = sms_sink + self.location = location self.channels = ProactChannels(on_data_available=self._on_channel_data_available) + def handle_ProvideLocalInformation(self, pcmd: ProactiveCommand): + """only location + + TS 102 223 6.8.7 says TERMINAL RESPONSE to PROVIDE LOCAL INFORMATION "shall" + contain the data object the command qualifier (6.6.15) asked for. At least answer '00', + location information, usually requested. + + answering "terminal currently unable to process - no service", which is a handset + out of coverage makes SJA5 believe it and postpones the entire session! + it registers a location status event, starts a ten minute timer and waits for coverage.""" + cmd_det_ie = Proact._find_first_element_of_type(pcmd.children, CommandDetails) + if cmd_det_ie is not None and cmd_det_ie.decoded['command_qualifier'] == 0x00: + return self.prepare_response(pcmd) + [LocationInformation(decoded=self.location)] + return self.prepare_response(pcmd) + def receive_fetch(self, pcmd: ProactiveCommand): """Answer anything this handler has no specific handler for. diff --git a/pySim/cat.py b/pySim/cat.py index 92b7ea37..e65af3f2 100644 --- a/pySim/cat.py +++ b/pySim/cat.py @@ -318,7 +318,9 @@ class FileList(COMPR_TLV_IE, tag=0x92): # TS 102 223 Section 8.19 class LocationInformation(COMPR_TLV_IE, tag=0x93): - pass + # 8.19: coding is per access technology, and the lengths differ (TS 131.111 8.19.1-.4: GERAN 7, + # UTRAN/E-UTRAN 9, NG-RAN 11) with nothing in the IE to say which -> keep the value opaque. + _construct = GreedyBytes class MobileIdentityAdapter(Adapter): """TS 124.008 section 10.5.1.4 figure 10.5.4 + table 10.5.4 diff --git a/tests/unittests/test_bip_relay.py b/tests/unittests/test_bip_relay.py index 46281bd0..5f724114 100644 --- a/tests/unittests/test_bip_relay.py +++ b/tests/unittests/test_bip_relay.py @@ -28,7 +28,7 @@ from pySim.sms import SMS_SUBMIT, AddressField from pySim.cat import (ProactiveCommand, CommandDetails, DeviceIdentities, BearerDescription, BufferSize, UiccTransportLevel, OtherAddress, ChannelData, ChannelDataLength, ChannelStatus, - Result) + Result, LocationInformation) from pySim.bip import Proact, ProactChannels, terminal_profile @@ -329,6 +329,34 @@ class OpenChannelRefusalTest(unittest.TestCase): self.assertEqual(self.proact.channels.channels, {}) # channel given back +class ProvideLocalInformationTest(unittest.TestCase): + """TS 102 223 6.8.7: only 00 gets a data object; the rest keeps the empty result.""" + + def _cmd(self, qualifier): + return _pcmd([ + CommandDetails(decoded={'command_number': 1, 'type_of_command': 'provide_local_info', + 'command_qualifier': qualifier}).to_tlv(), + DeviceIdentities(decoded={'source_dev_id': 'uicc', 'dest_dev_id': 'terminal'}).to_tlv()]) + + def test_location(self): + til = Proact().handle_ProvideLocalInformation(self._cmd(0x00)) + b''.join(x.to_tlv() for x in til) + self.assertEqual(_first(til, Result).decoded['general_result'], 'performed_successfully') + self.assertEqual(b2h(_first(til, LocationInformation).to_tlv()), '930762f21000010001') + self.assertEqual(b2h(_first(til, DeviceIdentities).to_tlv()), '82028281') # 6.8.2 + + def test_other_qualifiers_get_no_data_object(self): + for qualifier in (0x01, 0x03, 0x04, 0x1a): + with self.subTest(command_qualifier=qualifier): + til = Proact().handle_ProvideLocalInformation(self._cmd(qualifier)) + b''.join(x.to_tlv() for x in til) + self.assertIsNone(_first(til, LocationInformation)) + + def test_location_is_configurable(self): + til = Proact(location=h2b('26f8100539')).handle_ProvideLocalInformation(self._cmd(0x00)) + self.assertEqual(b2h(_first(til, LocationInformation).to_tlv()), '930526f8100539') + + class TerminalProfileTest(unittest.TestCase): """TS 102 223 5.2, one bit per CAT facility"""