Home | History | Annotate | Download | only in state_machines
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import math
      6 
      7 # Setup wardmodem package root and other autotest paths.
      8 import common
      9 
     10 import state_machine
     11 
     12 class NetworkIdentityMachine(state_machine.StateMachine):
     13     """
     14     Various identification numbers are used by the network to identify a
     15     customer's mobile equipment. These numbers are variously dependent on the
     16     SIM card, the network, the original subscriber, the geographical location
     17     etc.
     18 
     19     We serve queries regarding these identification numbers from this state
     20     machine, so that intuitive handles can be used to fake different
     21     combinations of the facets listed above.
     22 
     23     """
     24 
     25     def __init__(self, state, transceiver, modem_conf):
     26         """
     27         @param state: The GlobalState object shared by all state machines.
     28 
     29         @param transceiver: The ATTransceiver object to interact with.
     30 
     31         @param modem_conf: A ModemConfiguration object containing the
     32                 configuration data for the current modem.
     33 
     34         """
     35         super(NetworkIdentityMachine, self).__init__(state, transceiver,
     36                                                      modem_conf)
     37 
     38         # Register all responses.
     39         self._add_response_function('wm_response_sim_info_success')
     40         self._add_response_function('wm_response_sim_info_error_too_long')
     41         self._add_response_function('wm_response_mdn')
     42 
     43         # Load configuration.
     44         self._mcc = modem_conf.network_identity_default_mcc
     45         self._mnc = modem_conf.network_identity_default_mnc
     46         self._msin = modem_conf.network_identity_default_msin
     47         self._mdn = modem_conf.network_identity_default_mdn
     48 
     49 
     50     def get_well_known_name(self):
     51         """ Returns the well known name for this machine. """
     52         return 'network_identity_machine'
     53 
     54 
     55     # ##########################################################################
     56     # State machine API functions.
     57     def read_imsi_from_modem(self):
     58         """
     59         Return the IMSI stored in the modem.
     60 
     61         This is currently a stub that returns the same IMSI as returned from
     62         SIM (@see read_sim_imsi). Note that the format of the returned value is
     63         different for these two functions. Some modems actually report two
     64         different IMSIs depending on where it is queried from. Implement this
     65         function if you want to simulate that behaviour.
     66 
     67         """
     68         self._respond_text(self._get_sim_imsi())
     69         self._respond_ok()
     70 
     71 
     72     def read_sim_admin_data(self, length_str):
     73         """
     74         Return administrative data read from the SIM.
     75 
     76         The administrative data contains, besides the length of the MNC, the
     77         state of the device -- is it under active development, undergoing some
     78         qualification process etc.
     79         We force the mode to be normal, and allow modems to provide different
     80         length of MNC if desired.
     81 
     82         @param length_str: (Type: str) Length of expected response.
     83 
     84         """
     85         answer = '0000000'
     86         answer += str(len(self._mnc))
     87         self._check_length_and_respond(answer, length_str)
     88 
     89 
     90     def read_sim_imsi(self, length_str):
     91         """
     92         Return the IMSI.
     93 
     94         The IMSI information is also available from request_response state
     95         machine. These two IMSI values can actually be different.
     96 
     97         @param length_str: (Type: str) Length of the expected response.
     98 
     99         """
    100         imsi = self._get_sim_imsi()
    101         # See ETSI TS 151.11 V14.4.0
    102         # The format of the returned string is
    103         # 'l' ['x' '<padded_imsi>']
    104         #
    105         # |l| is the number of bytes used by the 'x' and 'imsi' together.
    106         #
    107         # |x| encodes some parity checking, and can be '8' or '9' based on
    108         # parity. We currently set it to 9 (I haven't seen '8' in practice')
    109         #
    110         # |padded_imsi| is imsi padded with 'f' on the right to make the whole
    111         # thing 18 characters
    112         #
    113         # Finally the bytes within [] are represented LSB first, so the odd and
    114         # even characters in that string need to be swapped.
    115         x = '9'
    116         padded_imsi = x + imsi
    117         l = str(int(math.ceil(len(padded_imsi)/2)))
    118         while len(padded_imsi) < 16:
    119             padded_imsi += 'F'
    120         # Encode this number LSB first.
    121         switched_imsi = []
    122         for i in range(8):
    123             switched_imsi.append(padded_imsi[2*i+1])
    124             switched_imsi.append(padded_imsi[2*i])
    125         padded_imsi = ''.join(switched_imsi)
    126 
    127         # l should be 2 character long
    128         if len(l) != 2:
    129             l = '0' + l
    130         response = l + padded_imsi
    131 
    132         self._check_length_and_respond(response, length_str)
    133 
    134 
    135     def read_service_provider_name(self, length_str):
    136         """
    137         Return the name of the service provider encoded as hex string.
    138 
    139         Not Implemented. None of the modems we use return this information
    140         correctly right now.
    141 
    142         @param length_str: (Type: str) Length of the expected response.
    143 
    144         """
    145         # 'F' is used to pad unused bits. An all-'F' string means that we do not
    146         # have this information.
    147         # (TODO) pprabhu: Figure out the encoding scheme for this information if
    148         # a test needs it.
    149         provider_name = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
    150         self._check_length_and_respond(provider_name, length_str)
    151 
    152 
    153     def read_mdn(self):
    154         """
    155         Return the Mobile Directory Number for the current service account.
    156 
    157         """
    158         self._respond(self.wm_response_mdn, 0, self._mdn)
    159         self._respond_ok()
    160 
    161 
    162     def _check_length_and_respond(self, response, length_str):
    163         """
    164         Checks that the length of the response is not less than the expected
    165         response. If it is more, the response is clipped.
    166 
    167         @param response: The string to respond with.
    168 
    169         @param length_str: (Type: str)The expected length in bytes. Note that
    170                 the number of bytes in |response| is half the length of the
    171                 string |response|, since each byte encodes two characters.
    172 
    173         """
    174         try:
    175             length = int(length_str)
    176         except ValueError as e:
    177             dbgstr = self._tag_with_name(
    178                     'Failed to detect expected length of the response. '
    179                     'Are you sure this is a number: |%s|' % length_str)
    180             self._respond_error()
    181             return
    182 
    183         # We require ceil(len(response)) number of bytes to encode the string
    184         # |response|, since each byte holds two characters.
    185         if 2 * length > len(response):
    186             dbgstr = self._tag_with_name(
    187                     'Response too short. Requested reponse length: %d, Actual '
    188                     'response length: %d' % (2 * length, len(response)))
    189             self._logger.warning(dbgstr)
    190             self._respond(self.wm_response_sim_info_error_too_long)
    191         else:
    192             dbgstr = self._tag_with_name(
    193                     'Response: |%s|, clipped to length %d: |%s|' %
    194                     (response, length, response[:2*length]))
    195             self._logger.debug(dbgstr)
    196             self._respond(self.wm_response_sim_info_success, 0,
    197                           response[:2*length])
    198         self._respond_ok()
    199 
    200 
    201     def _get_sim_imsi(self):
    202         return self._mcc + self._mnc + self._msin
    203