1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li"""An implementation of the ModemManager1 DBUS interface. 6*9c5db199SXin Li 7*9c5db199SXin LiThis modem mimics a GSM (eventually LTE & CDMA) modem and allows a 8*9c5db199SXin Liuser to test shill and UI behaviors when a supported SIM is inserted 9*9c5db199SXin Liinto the device. Invoked with the proper flags it can test that SMS 10*9c5db199SXin Limessages are deliver to the UI. 11*9c5db199SXin Li 12*9c5db199SXin LiThis program creates a virtual network interface to simulate the 13*9c5db199SXin Linetwork interface of a modem. It depends on modemmanager-next to 14*9c5db199SXin Liset the dbus permissions properly. 15*9c5db199SXin Li 16*9c5db199SXin LiTODO: 17*9c5db199SXin Li * Use more appropriate values for many of the properties 18*9c5db199SXin Li * Support all ModemManager1 interfaces 19*9c5db199SXin Li * implement LTE modems 20*9c5db199SXin Li * implement CDMA modems 21*9c5db199SXin Li""" 22*9c5db199SXin Li 23*9c5db199SXin Lifrom __future__ import absolute_import 24*9c5db199SXin Lifrom __future__ import division 25*9c5db199SXin Lifrom __future__ import print_function 26*9c5db199SXin Li 27*9c5db199SXin Lifrom optparse import OptionParser 28*9c5db199SXin Liimport logging 29*9c5db199SXin Liimport os 30*9c5db199SXin Liimport signal 31*9c5db199SXin Liimport string 32*9c5db199SXin Liimport subprocess 33*9c5db199SXin Liimport sys 34*9c5db199SXin Liimport time 35*9c5db199SXin Li 36*9c5db199SXin Liimport dbus 37*9c5db199SXin Lifrom dbus.exceptions import DBusException 38*9c5db199SXin Liimport dbus.mainloop.glib 39*9c5db199SXin Liimport dbus.service 40*9c5db199SXin Lifrom dbus.types import Int32 41*9c5db199SXin Lifrom dbus.types import ObjectPath 42*9c5db199SXin Lifrom dbus.types import Struct 43*9c5db199SXin Lifrom dbus.types import UInt32 44*9c5db199SXin Liimport glib 45*9c5db199SXin Li# AU tests use ToT client code, but ToT -3 client version. 46*9c5db199SXin Litry: 47*9c5db199SXin Li from gi.repository import GObject 48*9c5db199SXin Liexcept ImportError: 49*9c5db199SXin Li import gobject as GObject 50*9c5db199SXin Liimport mm1 51*9c5db199SXin Lifrom six.moves import range 52*9c5db199SXin Li 53*9c5db199SXin Li 54*9c5db199SXin Li# Miscellaneous delays to simulate a modem 55*9c5db199SXin LiDEFAULT_CONNECT_DELAY_MS = 1500 56*9c5db199SXin Li 57*9c5db199SXin LiDEFAULT_CARRIER = 'att' 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Liclass DBusObjectWithProperties(dbus.service.Object): 61*9c5db199SXin Li """Implements the org.freedesktop.DBus.Properties interface. 62*9c5db199SXin Li 63*9c5db199SXin Li Implements the org.freedesktop.DBus.Properties interface, specifically 64*9c5db199SXin Li the Get and GetAll methods. Class which inherit from this class must 65*9c5db199SXin Li implement the InterfacesAndProperties function which will return a 66*9c5db199SXin Li dictionary of all interfaces and the properties defined on those interfaces. 67*9c5db199SXin Li """ 68*9c5db199SXin Li 69*9c5db199SXin Li def __init__(self, bus, path): 70*9c5db199SXin Li dbus.service.Object.__init__(self, bus, path) 71*9c5db199SXin Li 72*9c5db199SXin Li @dbus.service.method(dbus.PROPERTIES_IFACE, 73*9c5db199SXin Li in_signature='ss', out_signature='v') 74*9c5db199SXin Li def Get(self, interface, property_name, *args, **kwargs): 75*9c5db199SXin Li """Returns: The value of property_name on interface.""" 76*9c5db199SXin Li logging.info('%s: Get %s, %s', self.path, interface, property_name) 77*9c5db199SXin Li interfaces = self.InterfacesAndProperties() 78*9c5db199SXin Li properties = interfaces.get(interface, None) 79*9c5db199SXin Li if property_name in properties: 80*9c5db199SXin Li return properties[property_name] 81*9c5db199SXin Li raise dbus.exceptions.DBusException( 82*9c5db199SXin Li mm1.MODEM_MANAGER_INTERFACE + '.UnknownProperty', 83*9c5db199SXin Li 'Property %s not defined for interface %s' % 84*9c5db199SXin Li (property_name, interface)) 85*9c5db199SXin Li 86*9c5db199SXin Li @dbus.service.method(dbus.PROPERTIES_IFACE, 87*9c5db199SXin Li in_signature='s', out_signature='a{sv}') 88*9c5db199SXin Li def GetAll(self, interface, *args, **kwargs): 89*9c5db199SXin Li """Returns: A dictionary. The properties on interface.""" 90*9c5db199SXin Li logging.info('%s: GetAll %s', self.path, interface) 91*9c5db199SXin Li interfaces = self.InterfacesAndProperties() 92*9c5db199SXin Li properties = interfaces.get(interface, None) 93*9c5db199SXin Li if properties is not None: 94*9c5db199SXin Li return properties 95*9c5db199SXin Li raise dbus.exceptions.DBusException( 96*9c5db199SXin Li mm1.MODEM_MANAGER_INTERFACE + '.UnknownInterface', 97*9c5db199SXin Li 'Object does not implement the %s interface' % interface) 98*9c5db199SXin Li 99*9c5db199SXin Li def InterfacesAndProperties(self): 100*9c5db199SXin Li """Subclasses must implement this function. 101*9c5db199SXin Li 102*9c5db199SXin Li Returns: 103*9c5db199SXin Li A dictionary of interfaces where the values are dictionaries 104*9c5db199SXin Li of dbus properties. 105*9c5db199SXin Li """ 106*9c5db199SXin Li pass 107*9c5db199SXin Li 108*9c5db199SXin Li 109*9c5db199SXin Liclass SIM(DBusObjectWithProperties): 110*9c5db199SXin Li """SIM Object. 111*9c5db199SXin Li 112*9c5db199SXin Li Mock SIM Card and the typical information it might contain. 113*9c5db199SXin Li SIM cards of different carriers can be created by providing 114*9c5db199SXin Li the MCC, MNC, operator name, imsi, and msin. SIM objects are 115*9c5db199SXin Li passed to the Modem during Modem initialization. 116*9c5db199SXin Li """ 117*9c5db199SXin Li 118*9c5db199SXin Li DEFAULT_MCC = '310' 119*9c5db199SXin Li DEFAULT_MNC = '090' 120*9c5db199SXin Li DEFAULT_OPERATOR = 'AT&T' 121*9c5db199SXin Li DEFAULT_MSIN = '1234567890' 122*9c5db199SXin Li DEFAULT_IMSI = '888999111' 123*9c5db199SXin Li MCC_LIST = { 124*9c5db199SXin Li 'us': '310', 125*9c5db199SXin Li 'de': '262', 126*9c5db199SXin Li 'es': '214', 127*9c5db199SXin Li 'fr': '208', 128*9c5db199SXin Li 'gb': '234', 129*9c5db199SXin Li 'it': '222', 130*9c5db199SXin Li 'nl': '204', 131*9c5db199SXin Li } 132*9c5db199SXin Li CARRIERS = { 133*9c5db199SXin Li 'att': ('us', '090', 'AT&T'), 134*9c5db199SXin Li 'tmobile': ('us', '026', 'T-Mobile'), 135*9c5db199SXin Li 'simyo': ('de', '03', 'simyo'), 136*9c5db199SXin Li 'movistar': ('es', '07', 'Movistar'), 137*9c5db199SXin Li 'sfr': ('fr', '10', 'SFR'), 138*9c5db199SXin Li 'three': ('gb', '20', '3'), 139*9c5db199SXin Li 'threeita': ('it', '99', '3ITA'), 140*9c5db199SXin Li 'kpn': ('nl', '08', 'KPN') 141*9c5db199SXin Li } 142*9c5db199SXin Li 143*9c5db199SXin Li def __init__(self, 144*9c5db199SXin Li manager, 145*9c5db199SXin Li mcc_country='us', 146*9c5db199SXin Li mnc=DEFAULT_MNC, 147*9c5db199SXin Li operator_name=DEFAULT_OPERATOR, 148*9c5db199SXin Li msin=DEFAULT_MSIN, 149*9c5db199SXin Li imsi=None, 150*9c5db199SXin Li mcc=None, 151*9c5db199SXin Li name='/Sim/0'): 152*9c5db199SXin Li self.manager = manager 153*9c5db199SXin Li self.name = name 154*9c5db199SXin Li self.path = manager.path + name 155*9c5db199SXin Li self.mcc = mcc or SIM.MCC_LIST.get(mcc_country, '000') 156*9c5db199SXin Li self.mnc = mnc 157*9c5db199SXin Li self.operator_name = operator_name 158*9c5db199SXin Li self.msin = msin 159*9c5db199SXin Li self.imsi = imsi or (self.mcc + self.mnc + SIM.DEFAULT_IMSI) 160*9c5db199SXin Li DBusObjectWithProperties.__init__(self, manager.bus, self.path) 161*9c5db199SXin Li 162*9c5db199SXin Li @staticmethod 163*9c5db199SXin Li def FromCarrier(carrier, manager): 164*9c5db199SXin Li """Creates a SIM card object for a given carrier.""" 165*9c5db199SXin Li args = SIM.CARRIERS.get(carrier, []) 166*9c5db199SXin Li return SIM(manager, *args) 167*9c5db199SXin Li 168*9c5db199SXin Li def Properties(self): 169*9c5db199SXin Li return { 170*9c5db199SXin Li 'SimIdentifier': self.msin, 171*9c5db199SXin Li 'Imsi': self.imsi, 172*9c5db199SXin Li 'OperatorIdentifier': self.mcc + self.mnc, 173*9c5db199SXin Li 'OperatorName': self.operator_name 174*9c5db199SXin Li } 175*9c5db199SXin Li 176*9c5db199SXin Li def InterfacesAndProperties(self): 177*9c5db199SXin Li return {mm1.SIM_INTERFACE: self.Properties()} 178*9c5db199SXin Li 179*9c5db199SXin Liclass SMS(DBusObjectWithProperties): 180*9c5db199SXin Li """SMS Object. 181*9c5db199SXin Li 182*9c5db199SXin Li Mock SMS message. 183*9c5db199SXin Li """ 184*9c5db199SXin Li 185*9c5db199SXin Li def __init__(self, manager, name='/SMS/0', text='test', 186*9c5db199SXin Li number='123', timestamp='12:00', smsc=''): 187*9c5db199SXin Li self.manager = manager 188*9c5db199SXin Li self.name = name 189*9c5db199SXin Li self.path = manager.path + name 190*9c5db199SXin Li self.text = text or 'test sms at %s' % name 191*9c5db199SXin Li self.number = number 192*9c5db199SXin Li self.timestamp = timestamp 193*9c5db199SXin Li self.smsc = smsc 194*9c5db199SXin Li DBusObjectWithProperties.__init__(self, manager.bus, self.path) 195*9c5db199SXin Li 196*9c5db199SXin Li def Properties(self): 197*9c5db199SXin Li # TODO(jglasgow): State, Validity, Class, Storage are also defined 198*9c5db199SXin Li return { 199*9c5db199SXin Li 'Text': self.text, 200*9c5db199SXin Li 'Number': self.number, 201*9c5db199SXin Li 'Timestamp': self.timestamp, 202*9c5db199SXin Li 'SMSC': self.smsc 203*9c5db199SXin Li } 204*9c5db199SXin Li 205*9c5db199SXin Li def InterfacesAndProperties(self): 206*9c5db199SXin Li return {mm1.SMS_INTERFACE: self.Properties()} 207*9c5db199SXin Li 208*9c5db199SXin Li 209*9c5db199SXin Liclass PseudoNetworkInterface(object): 210*9c5db199SXin Li """A Pseudo network interface. 211*9c5db199SXin Li 212*9c5db199SXin Li This uses a pair of network interfaces and dnsmasq to simulate the 213*9c5db199SXin Li network device normally associated with a modem. 214*9c5db199SXin Li """ 215*9c5db199SXin Li 216*9c5db199SXin Li # Any interface that shill manages will get its own routing 217*9c5db199SXin Li # table. Routes added to the main routing table with RTPROT_BOOT (the 218*9c5db199SXin Li # default proto value) will be sent to the corresponding interface's 219*9c5db199SXin Li # routing table. We want to prevent that in this case, so we use 220*9c5db199SXin Li # proto 5, as shill currently ignores proto values greater than 4. 221*9c5db199SXin Li ROUTE_PROTO = 'proto 5' 222*9c5db199SXin Li 223*9c5db199SXin Li def __init__(self, interface, base): 224*9c5db199SXin Li self.interface = interface 225*9c5db199SXin Li self.peer = self.interface + 'p' 226*9c5db199SXin Li self.base = base 227*9c5db199SXin Li self.lease_file = '/tmp/dnsmasq.%s.leases' % self.interface 228*9c5db199SXin Li self.dnsmasq = None 229*9c5db199SXin Li 230*9c5db199SXin Li def __enter__(self): 231*9c5db199SXin Li """Make usable with "with" statement.""" 232*9c5db199SXin Li self.CreateInterface() 233*9c5db199SXin Li return self 234*9c5db199SXin Li 235*9c5db199SXin Li def __exit__(self, exception, value, traceback): 236*9c5db199SXin Li """Make usable with "with" statement.""" 237*9c5db199SXin Li self.DestroyInterface() 238*9c5db199SXin Li return False 239*9c5db199SXin Li 240*9c5db199SXin Li def CreateInterface(self): 241*9c5db199SXin Li """Creates a virtual interface. 242*9c5db199SXin Li 243*9c5db199SXin Li Creates the virtual interface self.interface as well as a peer 244*9c5db199SXin Li interface. Runs dnsmasq on the peer interface so that a DHCP 245*9c5db199SXin Li service can offer ip addresses to the virtual interface. 246*9c5db199SXin Li """ 247*9c5db199SXin Li os.system('ip link add name %s type veth peer name %s' % ( 248*9c5db199SXin Li self.interface, self.peer)) 249*9c5db199SXin Li 250*9c5db199SXin Li os.system('ifconfig %s %s.1/24' % (self.peer, self.base)) 251*9c5db199SXin Li os.system('ifconfig %s up' % self.peer) 252*9c5db199SXin Li 253*9c5db199SXin Li os.system('ifconfig %s up' % self.interface) 254*9c5db199SXin Li os.system('ip route add 255.255.255.255 dev %s %s' % 255*9c5db199SXin Li (self.peer, self.ROUTE_PROTO)) 256*9c5db199SXin Li os.close(os.open(self.lease_file, os.O_CREAT | os.O_TRUNC)) 257*9c5db199SXin Li self.dnsmasq = subprocess.Popen( 258*9c5db199SXin Li ['/usr/local/sbin/dnsmasq', 259*9c5db199SXin Li '--pid-file', 260*9c5db199SXin Li '-k', 261*9c5db199SXin Li '--dhcp-leasefile=%s' % self.lease_file, 262*9c5db199SXin Li '--dhcp-range=%s.2,%s.254' % (self.base, self.base), 263*9c5db199SXin Li '--port=0', 264*9c5db199SXin Li '--interface=%s' % self.peer, 265*9c5db199SXin Li '--bind-interfaces' 266*9c5db199SXin Li ]) 267*9c5db199SXin Li # iptables default policy is to reject packets. Add ACCEPT as the 268*9c5db199SXin Li # target for the virtual and peer interfaces. Note that this currently 269*9c5db199SXin Li # only accepts v4 traffic. 270*9c5db199SXin Li os.system('iptables -I INPUT -i %s -j ACCEPT' % self.peer) 271*9c5db199SXin Li os.system('iptables -I INPUT -i %s -j ACCEPT' % self.interface) 272*9c5db199SXin Li 273*9c5db199SXin Li def DestroyInterface(self): 274*9c5db199SXin Li """Destroys the virtual interface. 275*9c5db199SXin Li 276*9c5db199SXin Li Stops dnsmasq and cleans up all on disk state. 277*9c5db199SXin Li """ 278*9c5db199SXin Li if self.dnsmasq: 279*9c5db199SXin Li self.dnsmasq.terminate() 280*9c5db199SXin Li try: 281*9c5db199SXin Li os.system('ip route del 255.255.255.255 %s' % self.ROUTE_PROTO) 282*9c5db199SXin Li except: 283*9c5db199SXin Li pass 284*9c5db199SXin Li try: 285*9c5db199SXin Li os.system('ip link del %s' % self.interface) 286*9c5db199SXin Li except: 287*9c5db199SXin Li pass 288*9c5db199SXin Li os.system('iptables -D INPUT -i %s -j ACCEPT' % self.peer) 289*9c5db199SXin Li os.system('iptables -D INPUT -i %s -j ACCEPT' % self.interface) 290*9c5db199SXin Li if os.path.exists(self.lease_file): 291*9c5db199SXin Li os.remove(self.lease_file) 292*9c5db199SXin Li 293*9c5db199SXin Li 294*9c5db199SXin Liclass Modem(DBusObjectWithProperties): 295*9c5db199SXin Li """A Modem object that implements the ModemManager DBUS API.""" 296*9c5db199SXin Li 297*9c5db199SXin Li def __init__(self, manager, name='/Modem/0', 298*9c5db199SXin Li device='pseudomodem0', 299*9c5db199SXin Li mdn='0000001234', 300*9c5db199SXin Li meid='A100000DCE2CA0', 301*9c5db199SXin Li carrier='CrCarrier', 302*9c5db199SXin Li esn='EDD1EDD1', 303*9c5db199SXin Li sim=None): 304*9c5db199SXin Li """Instantiates a Modem with some options. 305*9c5db199SXin Li 306*9c5db199SXin Li Args: 307*9c5db199SXin Li manager: a ModemManager object. 308*9c5db199SXin Li name: string, a dbus path name. 309*9c5db199SXin Li device: string, the network device to use. 310*9c5db199SXin Li mdn: string, the mobile directory number. 311*9c5db199SXin Li meid: string, the mobile equipment id (CDMA only?). 312*9c5db199SXin Li carrier: string, the name of the carrier. 313*9c5db199SXin Li esn: string, the electronic serial number. 314*9c5db199SXin Li sim: a SIM object. 315*9c5db199SXin Li """ 316*9c5db199SXin Li self.state = mm1.MM_MODEM_STATE_DISABLED 317*9c5db199SXin Li self.manager = manager 318*9c5db199SXin Li self.name = name 319*9c5db199SXin Li self.path = manager.path + name 320*9c5db199SXin Li self.device = device 321*9c5db199SXin Li self.mdn = mdn 322*9c5db199SXin Li self.meid = meid 323*9c5db199SXin Li self.carrier = carrier 324*9c5db199SXin Li self.operator_name = carrier 325*9c5db199SXin Li self.operator_code = '123' 326*9c5db199SXin Li self.esn = esn 327*9c5db199SXin Li self.registration_state = mm1.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE 328*9c5db199SXin Li self.sim = sim 329*9c5db199SXin Li DBusObjectWithProperties.__init__(self, manager.bus, self.path) 330*9c5db199SXin Li self.pseudo_interface = PseudoNetworkInterface(self.device, '192.168.7') 331*9c5db199SXin Li self.smses = {} 332*9c5db199SXin Li 333*9c5db199SXin Li def __enter__(self): 334*9c5db199SXin Li """Make usable with "with" statement.""" 335*9c5db199SXin Li self.pseudo_interface.__enter__() 336*9c5db199SXin Li # Add the device to the manager only after the pseudo 337*9c5db199SXin Li # interface has been created. 338*9c5db199SXin Li self.manager.Add(self) 339*9c5db199SXin Li return self 340*9c5db199SXin Li 341*9c5db199SXin Li def __exit__(self, exception, value, traceback): 342*9c5db199SXin Li """Make usable with "with" statement.""" 343*9c5db199SXin Li self.manager.Remove(self) 344*9c5db199SXin Li return self.pseudo_interface.__exit__(exception, value, traceback) 345*9c5db199SXin Li 346*9c5db199SXin Li def DiscardModem(self): 347*9c5db199SXin Li """Discard this DBUS Object. 348*9c5db199SXin Li 349*9c5db199SXin Li Send a message that a modem has disappeared and deregister from DBUS. 350*9c5db199SXin Li """ 351*9c5db199SXin Li logging.info('DiscardModem') 352*9c5db199SXin Li self.remove_from_connection() 353*9c5db199SXin Li self.manager.Remove(self) 354*9c5db199SXin Li 355*9c5db199SXin Li def ModemProperties(self): 356*9c5db199SXin Li """Return the properties of the modem object.""" 357*9c5db199SXin Li properties = { 358*9c5db199SXin Li # 'Sim': type='o' 359*9c5db199SXin Li 'ModemCapabilities': UInt32(0), 360*9c5db199SXin Li 'CurrentCapabilities': UInt32(0), 361*9c5db199SXin Li 'MaxBearers': UInt32(2), 362*9c5db199SXin Li 'MaxActiveBearers': UInt32(2), 363*9c5db199SXin Li 'Manufacturer': 'Foo Electronics', 364*9c5db199SXin Li 'Model': 'Super Foo Modem', 365*9c5db199SXin Li 'Revision': '1.0', 366*9c5db199SXin Li 'DeviceIdentifier': '123456789', 367*9c5db199SXin Li 'Device': self.device, 368*9c5db199SXin Li 'Driver': 'fake', 369*9c5db199SXin Li 'Plugin': 'Foo Plugin', 370*9c5db199SXin Li 'EquipmentIdentifier': self.meid, 371*9c5db199SXin Li 'UnlockRequired': UInt32(0), 372*9c5db199SXin Li #'UnlockRetries' type='a{uu}' 373*9c5db199SXin Li mm1.MM_MODEM_PROPERTY_STATE: Int32(self.state), 374*9c5db199SXin Li 'AccessTechnologies': UInt32(self.state), 375*9c5db199SXin Li 'SignalQuality': Struct([UInt32(90), True], signature='ub'), 376*9c5db199SXin Li 'OwnNumbers': ['6175551212'], 377*9c5db199SXin Li 'SupportedModes': UInt32(0), 378*9c5db199SXin Li 'AllowedModes': UInt32(0), 379*9c5db199SXin Li 'PreferredMode': UInt32(0), 380*9c5db199SXin Li 'SupportedBands': [UInt32(0)], 381*9c5db199SXin Li 'Bands': [UInt32(0)] 382*9c5db199SXin Li } 383*9c5db199SXin Li if self.sim: 384*9c5db199SXin Li properties['Sim'] = ObjectPath(self.sim.path) 385*9c5db199SXin Li return properties 386*9c5db199SXin Li 387*9c5db199SXin Li def InterfacesAndProperties(self): 388*9c5db199SXin Li """Return all supported interfaces and their properties.""" 389*9c5db199SXin Li return { 390*9c5db199SXin Li mm1.MODEM_INTERFACE: self.ModemProperties(), 391*9c5db199SXin Li } 392*9c5db199SXin Li 393*9c5db199SXin Li def ChangeState(self, new_state, 394*9c5db199SXin Li why=mm1.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN): 395*9c5db199SXin Li logging.info('Change state from %s to %s', self.state, new_state) 396*9c5db199SXin Li self.StateChanged(Int32(self.state), Int32(new_state), UInt32(why)) 397*9c5db199SXin Li self.PropertiesChanged(mm1.MODEM_INTERFACE, 398*9c5db199SXin Li {mm1.MM_MODEM_PROPERTY_STATE: Int32(new_state)}, 399*9c5db199SXin Li []) 400*9c5db199SXin Li self.state = new_state 401*9c5db199SXin Li 402*9c5db199SXin Li @dbus.service.method(mm1.MODEM_INTERFACE, 403*9c5db199SXin Li in_signature='b', out_signature='') 404*9c5db199SXin Li def Enable(self, on, *args, **kwargs): 405*9c5db199SXin Li """Enables the Modem.""" 406*9c5db199SXin Li logging.info('Modem: Enable %s', str(on)) 407*9c5db199SXin Li if on: 408*9c5db199SXin Li if self.state <= mm1.MM_MODEM_STATE_ENABLING: 409*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_ENABLING) 410*9c5db199SXin Li if self.state <= mm1.MM_MODEM_STATE_ENABLED: 411*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_ENABLED) 412*9c5db199SXin Li if self.state <= mm1.MM_MODEM_STATE_SEARCHING: 413*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_SEARCHING) 414*9c5db199SXin Li glib.timeout_add(250, self.OnRegistered) 415*9c5db199SXin Li else: 416*9c5db199SXin Li if self.state >= mm1.MM_MODEM_STATE_DISABLING: 417*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_DISABLING) 418*9c5db199SXin Li if self.state >= mm1.MM_MODEM_STATE_DISABLED: 419*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_DISABLED) 420*9c5db199SXin Li self.ChangeRegistrationState( 421*9c5db199SXin Li mm1.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE) 422*9c5db199SXin Li return None 423*9c5db199SXin Li 424*9c5db199SXin Li def ChangeRegistrationState(self, new_state): 425*9c5db199SXin Li """Updates the registration state of the modem. 426*9c5db199SXin Li 427*9c5db199SXin Li Updates the registration state of the modem and broadcasts a 428*9c5db199SXin Li DBUS signal. 429*9c5db199SXin Li 430*9c5db199SXin Li Args: 431*9c5db199SXin Li new_state: the new registation state of the modem. 432*9c5db199SXin Li """ 433*9c5db199SXin Li if new_state != self.registration_state: 434*9c5db199SXin Li self.registration_state = new_state 435*9c5db199SXin Li self.PropertiesChanged( 436*9c5db199SXin Li mm1.MODEM_MODEM3GPP_INTERFACE, 437*9c5db199SXin Li {mm1.MM_MODEM3GPP_PROPERTY_REGISTRATION_STATE: 438*9c5db199SXin Li UInt32(new_state)}, 439*9c5db199SXin Li []) 440*9c5db199SXin Li 441*9c5db199SXin Li def OnRegistered(self): 442*9c5db199SXin Li """Called when the Modem is Registered.""" 443*9c5db199SXin Li if (self.state >= mm1.MM_MODEM_STATE_ENABLED and 444*9c5db199SXin Li self.state <= mm1.MM_MODEM_STATE_REGISTERED): 445*9c5db199SXin Li logging.info('Modem: Marking Registered') 446*9c5db199SXin Li self.ChangeRegistrationState( 447*9c5db199SXin Li mm1.MM_MODEM_3GPP_REGISTRATION_STATE_HOME) 448*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_REGISTERED) 449*9c5db199SXin Li 450*9c5db199SXin Li @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='', 451*9c5db199SXin Li out_signature='a{sv}') 452*9c5db199SXin Li def GetStatus(self, *args, **kwargs): 453*9c5db199SXin Li """Gets the general modem status. 454*9c5db199SXin Li 455*9c5db199SXin Li Returns: 456*9c5db199SXin Li A dictionary of properties. 457*9c5db199SXin Li """ 458*9c5db199SXin Li logging.info('Modem: GetStatus') 459*9c5db199SXin Li properties = { 460*9c5db199SXin Li 'state': UInt32(self.state), 461*9c5db199SXin Li 'signal-quality': UInt32(99), 462*9c5db199SXin Li 'bands': self.carrier, 463*9c5db199SXin Li 'access-technology': UInt32(0), 464*9c5db199SXin Li 'm3gpp-registration-state': UInt32(self.registration_state), 465*9c5db199SXin Li 'm3gpp-operator-code': '123', 466*9c5db199SXin Li 'm3gpp-operator-name': '123', 467*9c5db199SXin Li 'cdma-cdma1x-registration-state': UInt32(99), 468*9c5db199SXin Li 'cdma-evdo-registration-state': UInt32(99), 469*9c5db199SXin Li 'cdma-sid': '123', 470*9c5db199SXin Li 'cdma-nid': '123', 471*9c5db199SXin Li } 472*9c5db199SXin Li if self.state >= mm1.MM_MODEM_STATE_ENABLED: 473*9c5db199SXin Li properties['carrier'] = 'Test Network' 474*9c5db199SXin Li return properties 475*9c5db199SXin Li 476*9c5db199SXin Li @dbus.service.signal(mm1.MODEM_INTERFACE, signature='iiu') 477*9c5db199SXin Li def StateChanged(self, old_state, new_state, why): 478*9c5db199SXin Li pass 479*9c5db199SXin Li 480*9c5db199SXin Li @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='a{sv}', 481*9c5db199SXin Li out_signature='o', 482*9c5db199SXin Li async_callbacks=('return_cb', 'raise_cb')) 483*9c5db199SXin Li def Connect(self, unused_props, return_cb, raise_cb, **kwargs): 484*9c5db199SXin Li """Connect the modem to the network. 485*9c5db199SXin Li 486*9c5db199SXin Li Args: 487*9c5db199SXin Li unused_props: connection properties. See ModemManager documentation. 488*9c5db199SXin Li return_cb: function to call to return result asynchronously. 489*9c5db199SXin Li raise_cb: function to call to raise an error asynchronously. 490*9c5db199SXin Li """ 491*9c5db199SXin Li 492*9c5db199SXin Li def ConnectDone(new, why): 493*9c5db199SXin Li logging.info('Modem: ConnectDone %s -> %s because %s', 494*9c5db199SXin Li str(self.state), str(new), str(why)) 495*9c5db199SXin Li if self.state == mm1.MM_MODEM_STATE_CONNECTING: 496*9c5db199SXin Li self.ChangeState(new, why) 497*9c5db199SXin Li # TODO(jglasgow): implement a bearer object 498*9c5db199SXin Li bearer_path = '/Bearer/0' 499*9c5db199SXin Li return_cb(bearer_path) 500*9c5db199SXin Li else: 501*9c5db199SXin Li raise_cb(mm1.ConnectionUnknownError()) 502*9c5db199SXin Li 503*9c5db199SXin Li logging.info('Modem: Connect') 504*9c5db199SXin Li if self.state != mm1.MM_MODEM_STATE_REGISTERED: 505*9c5db199SXin Li logging.info( 506*9c5db199SXin Li 'Modem: Connect fails on unregistered modem. State = %s', 507*9c5db199SXin Li self.state) 508*9c5db199SXin Li raise mm1.NoNetworkError() 509*9c5db199SXin Li delay_ms = kwargs.get('connect_delay_ms', DEFAULT_CONNECT_DELAY_MS) 510*9c5db199SXin Li time.sleep(delay_ms / 1000.0) 511*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_CONNECTING) 512*9c5db199SXin Li glib.timeout_add(50, lambda: ConnectDone( 513*9c5db199SXin Li mm1.MM_MODEM_STATE_CONNECTED, 514*9c5db199SXin Li mm1.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)) 515*9c5db199SXin Li 516*9c5db199SXin Li @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='o', 517*9c5db199SXin Li async_callbacks=('return_cb', 'raise_cb')) 518*9c5db199SXin Li def Disconnect(self, bearer, return_cb, raise_cb, **kwargs): 519*9c5db199SXin Li """Disconnect the modem from the network.""" 520*9c5db199SXin Li 521*9c5db199SXin Li def DisconnectDone(old, new, why): 522*9c5db199SXin Li logging.info('Modem: DisconnectDone %s -> %s because %s', 523*9c5db199SXin Li str(old), str(new), str(why)) 524*9c5db199SXin Li if self.state == mm1.MM_MODEM_STATE_DISCONNECTING: 525*9c5db199SXin Li logging.info('Modem: State is DISCONNECTING, changing to %s', 526*9c5db199SXin Li str(new)) 527*9c5db199SXin Li self.ChangeState(new) 528*9c5db199SXin Li return_cb() 529*9c5db199SXin Li elif self.state == mm1.MM_MODEM_STATE_DISABLED: 530*9c5db199SXin Li logging.info('Modem: State is DISABLED, not changing state') 531*9c5db199SXin Li return_cb() 532*9c5db199SXin Li else: 533*9c5db199SXin Li raise_cb(mm1.ConnectionUnknownError()) 534*9c5db199SXin Li 535*9c5db199SXin Li logging.info('Modem: Disconnect') 536*9c5db199SXin Li self.ChangeState(mm1.MM_MODEM_STATE_DISCONNECTING) 537*9c5db199SXin Li glib.timeout_add( 538*9c5db199SXin Li 500, 539*9c5db199SXin Li lambda: DisconnectDone( 540*9c5db199SXin Li self.state, 541*9c5db199SXin Li mm1.MM_MODEM_STATE_REGISTERED, 542*9c5db199SXin Li mm1.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)) 543*9c5db199SXin Li 544*9c5db199SXin Li @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as') 545*9c5db199SXin Li def PropertiesChanged(self, interface, changed_properties, 546*9c5db199SXin Li invalidated_properties): 547*9c5db199SXin Li pass 548*9c5db199SXin Li 549*9c5db199SXin Li def AddSMS(self, sms): 550*9c5db199SXin Li logging.info('Adding SMS %s to list', sms.path) 551*9c5db199SXin Li self.smses[sms.path] = sms 552*9c5db199SXin Li self.Added(self.path, True) 553*9c5db199SXin Li 554*9c5db199SXin Li @dbus.service.method(mm1.MODEM_MESSAGING_INTERFACE, in_signature='', 555*9c5db199SXin Li out_signature='ao') 556*9c5db199SXin Li def List(self, *args, **kwargs): 557*9c5db199SXin Li logging.info('Modem.Messaging: List: %s', 558*9c5db199SXin Li ', '.join(list(self.smses.keys()))) 559*9c5db199SXin Li return list(self.smses.keys()) 560*9c5db199SXin Li 561*9c5db199SXin Li @dbus.service.method(mm1.MODEM_MESSAGING_INTERFACE, in_signature='o', 562*9c5db199SXin Li out_signature='') 563*9c5db199SXin Li def Delete(self, sms_path, *args, **kwargs): 564*9c5db199SXin Li logging.info('Modem.Messaging: Delete %s', sms_path) 565*9c5db199SXin Li del self.smses[sms_path] 566*9c5db199SXin Li 567*9c5db199SXin Li @dbus.service.signal(mm1.MODEM_MESSAGING_INTERFACE, signature='ob') 568*9c5db199SXin Li def Added(self, sms_path, complete): 569*9c5db199SXin Li pass 570*9c5db199SXin Li 571*9c5db199SXin Li 572*9c5db199SXin Liclass GSMModem(Modem): 573*9c5db199SXin Li """A GSMModem implements the mm1.MODEM_MODEM3GPP_INTERFACE interface.""" 574*9c5db199SXin Li 575*9c5db199SXin Li def __init__(self, manager, imei='00112342342', **kwargs): 576*9c5db199SXin Li self.imei = imei 577*9c5db199SXin Li Modem.__init__(self, manager, **kwargs) 578*9c5db199SXin Li 579*9c5db199SXin Li @dbus.service.method(mm1.MODEM_MODEM3GPP_INTERFACE, 580*9c5db199SXin Li in_signature='s', out_signature='') 581*9c5db199SXin Li def Register(self, operator_id, *args, **kwargs): 582*9c5db199SXin Li """Register the modem on the network.""" 583*9c5db199SXin Li pass 584*9c5db199SXin Li 585*9c5db199SXin Li def Modem3GPPProperties(self): 586*9c5db199SXin Li """Return the 3GPP Properties of the modem object.""" 587*9c5db199SXin Li return { 588*9c5db199SXin Li 'Imei': self.imei, 589*9c5db199SXin Li mm1.MM_MODEM3GPP_PROPERTY_REGISTRATION_STATE: 590*9c5db199SXin Li UInt32(self.registration_state), 591*9c5db199SXin Li 'OperatorCode': self.operator_code, 592*9c5db199SXin Li 'OperatorName': self.operator_name, 593*9c5db199SXin Li 'EnabledFacilityLocks': UInt32(0) 594*9c5db199SXin Li } 595*9c5db199SXin Li 596*9c5db199SXin Li def InterfacesAndProperties(self): 597*9c5db199SXin Li """Return all supported interfaces and their properties.""" 598*9c5db199SXin Li return { 599*9c5db199SXin Li mm1.MODEM_INTERFACE: self.ModemProperties(), 600*9c5db199SXin Li mm1.MODEM_MODEM3GPP_INTERFACE: self.Modem3GPPProperties() 601*9c5db199SXin Li } 602*9c5db199SXin Li 603*9c5db199SXin Li @dbus.service.method(mm1.MODEM_MODEM3GPP_INTERFACE, in_signature='', 604*9c5db199SXin Li out_signature='aa{sv}') 605*9c5db199SXin Li def Scan(self, *args, **kwargs): 606*9c5db199SXin Li """Scan for networks.""" 607*9c5db199SXin Li raise mm1.CoreUnsupportedError() 608*9c5db199SXin Li 609*9c5db199SXin Li 610*9c5db199SXin Liclass ModemManager(dbus.service.Object): 611*9c5db199SXin Li """Implements the org.freedesktop.DBus.ObjectManager interface.""" 612*9c5db199SXin Li 613*9c5db199SXin Li def __init__(self, bus, path): 614*9c5db199SXin Li self.devices = [] 615*9c5db199SXin Li self.bus = bus 616*9c5db199SXin Li self.path = path 617*9c5db199SXin Li dbus.service.Object.__init__(self, bus, path) 618*9c5db199SXin Li 619*9c5db199SXin Li def Add(self, device): 620*9c5db199SXin Li """Adds a modem device to the list of devices that are managed.""" 621*9c5db199SXin Li logging.info('ModemManager: add %s', device.name) 622*9c5db199SXin Li self.devices.append(device) 623*9c5db199SXin Li interfaces = device.InterfacesAndProperties() 624*9c5db199SXin Li logging.info('Add: %s', interfaces) 625*9c5db199SXin Li self.InterfacesAdded(device.path, interfaces) 626*9c5db199SXin Li 627*9c5db199SXin Li def Remove(self, device): 628*9c5db199SXin Li """Removes a modem device from the list of managed devices.""" 629*9c5db199SXin Li logging.info('ModemManager: remove %s', device.name) 630*9c5db199SXin Li self.devices.remove(device) 631*9c5db199SXin Li interfaces = list(device.InterfacesAndProperties().keys()) 632*9c5db199SXin Li self.InterfacesRemoved(device.path, interfaces) 633*9c5db199SXin Li 634*9c5db199SXin Li @dbus.service.method(mm1.OFDOM, out_signature='a{oa{sa{sv}}}') 635*9c5db199SXin Li def GetManagedObjects(self): 636*9c5db199SXin Li """Returns the list of managed objects and their properties.""" 637*9c5db199SXin Li results = {} 638*9c5db199SXin Li for device in self.devices: 639*9c5db199SXin Li results[device.path] = device.InterfacesAndProperties() 640*9c5db199SXin Li logging.info('GetManagedObjects: %s', ', '.join(list(results.keys()))) 641*9c5db199SXin Li return results 642*9c5db199SXin Li 643*9c5db199SXin Li @dbus.service.signal(mm1.OFDOM, signature='oa{sa{sv}}') 644*9c5db199SXin Li def InterfacesAdded(self, object_path, interfaces_and_properties): 645*9c5db199SXin Li pass 646*9c5db199SXin Li 647*9c5db199SXin Li @dbus.service.signal(mm1.OFDOM, signature='oas') 648*9c5db199SXin Li def InterfacesRemoved(self, object_path, interfaces): 649*9c5db199SXin Li pass 650*9c5db199SXin Li 651*9c5db199SXin Li 652*9c5db199SXin Lidef main(): 653*9c5db199SXin Li usage = """ 654*9c5db199SXin LiRun pseudo_modem to simulate a GSM modem using the modemmanager-next 655*9c5db199SXin LiDBUS interfaces. This can be used for the following: 656*9c5db199SXin Li - to simpilify the verification process of UI features that use of 657*9c5db199SXin Li overseas SIM cards 658*9c5db199SXin Li - to test shill on a virtual machine without a physical modem 659*9c5db199SXin Li - to test that Chrome property displays SMS messages 660*9c5db199SXin Li 661*9c5db199SXin LiTo use on a test image you use test_that to run 662*9c5db199SXin Linetwork_3GModemControl which will cause pseudo_modem.py to be 663*9c5db199SXin Liinstalled in /usr/local/autotests/cros/cellular. Then stop 664*9c5db199SXin Limodemmanager and start the pseudo modem with the commands: 665*9c5db199SXin Li 666*9c5db199SXin Li stop modemmanager 667*9c5db199SXin Li /usr/local/autotest/cros/cellular/pseudo_modem.py 668*9c5db199SXin Li 669*9c5db199SXin LiWhen done, use Control-C to stop the process and restart modem manager: 670*9c5db199SXin Li start modemmanager 671*9c5db199SXin Li 672*9c5db199SXin LiAdditional help documentation is available by invoking pseudo_modem.py 673*9c5db199SXin Li--help. 674*9c5db199SXin Li 675*9c5db199SXin LiSMS testing can be accomnplished by supplying the -s flag to simulate 676*9c5db199SXin Lithe receipt of a number of SMS messages. The message text can be 677*9c5db199SXin Lispecified with the --text option on the command line, or read from a 678*9c5db199SXin Lifile by using the --file option. If the messages are located in a 679*9c5db199SXin Lifile, then each line corresponds to a single SMS message. 680*9c5db199SXin Li 681*9c5db199SXin LiChrome should display the SMS messages as soon as a user logs in to 682*9c5db199SXin Lithe Chromebook, or if the user is already logged in, then shortly 683*9c5db199SXin Liafter the pseudo modem is recognized by shill. 684*9c5db199SXin Li""" 685*9c5db199SXin Li parser = OptionParser(usage=usage) 686*9c5db199SXin Li parser.add_option('-c', '--carrier', dest='carrier_name', 687*9c5db199SXin Li metavar='<carrier name>', 688*9c5db199SXin Li help='<carrier name> := %s' % ' | '.join( 689*9c5db199SXin Li list(SIM.CARRIERS.keys()))) 690*9c5db199SXin Li parser.add_option('-s', '--smscount', dest='sms_count', 691*9c5db199SXin Li default=0, 692*9c5db199SXin Li metavar='<smscount>', 693*9c5db199SXin Li help='<smscount> := integer') 694*9c5db199SXin Li parser.add_option('-l', '--logfile', dest='logfile', 695*9c5db199SXin Li default='', 696*9c5db199SXin Li metavar='<filename>', 697*9c5db199SXin Li help='<filename> := filename for logging output') 698*9c5db199SXin Li parser.add_option('-t', '--text', dest='sms_text', 699*9c5db199SXin Li default=None, 700*9c5db199SXin Li metavar='<text>', 701*9c5db199SXin Li help='<text> := text for sms messages') 702*9c5db199SXin Li parser.add_option('-f', '--file', dest='filename', 703*9c5db199SXin Li default=None, 704*9c5db199SXin Li metavar='<filename>', 705*9c5db199SXin Li help='<filename> := file with text for sms messages') 706*9c5db199SXin Li 707*9c5db199SXin Li (options, args) = parser.parse_args() 708*9c5db199SXin Li 709*9c5db199SXin Li kwargs = {} 710*9c5db199SXin Li if options.logfile: 711*9c5db199SXin Li kwargs['filename'] = options.logfile 712*9c5db199SXin Li logging.basicConfig(format='%(asctime)-15s %(message)s', 713*9c5db199SXin Li level=logging.DEBUG, 714*9c5db199SXin Li **kwargs) 715*9c5db199SXin Li 716*9c5db199SXin Li if not options.carrier_name: 717*9c5db199SXin Li options.carrier_name = DEFAULT_CARRIER 718*9c5db199SXin Li 719*9c5db199SXin Li dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 720*9c5db199SXin Li bus = dbus.SystemBus() 721*9c5db199SXin Li name = dbus.service.BusName(mm1.MODEM_MANAGER_INTERFACE, bus) 722*9c5db199SXin Li manager = ModemManager(bus, mm1.OMM) 723*9c5db199SXin Li sim_card = SIM.FromCarrier(string.lower(options.carrier_name), 724*9c5db199SXin Li manager) 725*9c5db199SXin Li with GSMModem(manager, sim=sim_card) as modem: 726*9c5db199SXin Li if options.filename: 727*9c5db199SXin Li f = open(options.filename, 'r') 728*9c5db199SXin Li for index, line in enumerate(f.readlines()): 729*9c5db199SXin Li line = line.strip() 730*9c5db199SXin Li if line: 731*9c5db199SXin Li sms = SMS(manager, name='/SMS/%s' % index, text=line) 732*9c5db199SXin Li modem.AddSMS(sms) 733*9c5db199SXin Li else: 734*9c5db199SXin Li for index in range(int(options.sms_count)): 735*9c5db199SXin Li sms = SMS(manager, name='/SMS/%s' % index, 736*9c5db199SXin Li text=options.sms_text) 737*9c5db199SXin Li modem.AddSMS(sms) 738*9c5db199SXin Li 739*9c5db199SXin Li mainloop = GObject.MainLoop() 740*9c5db199SXin Li 741*9c5db199SXin Li def SignalHandler(signum, frame): 742*9c5db199SXin Li logging.info('Signal handler called with signal: %s', signum) 743*9c5db199SXin Li mainloop.quit() 744*9c5db199SXin Li 745*9c5db199SXin Li signal.signal(signal.SIGTERM, SignalHandler) 746*9c5db199SXin Li 747*9c5db199SXin Li mainloop.run() 748*9c5db199SXin Li 749*9c5db199SXin Liif __name__ == '__main__': 750*9c5db199SXin Li main() 751