xref: /aosp_15_r20/external/autotest/client/cros/cellular/pseudo_modem.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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