1# Copyright (c) 2013 The Chromium 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
5import collections
6import pprint
7import re
8import xmlrpclib
9
10from autotest_lib.client.common_lib import global_config
11from autotest_lib.client.common_lib.cros.network import ap_constants
12from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
13from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
14from autotest_lib.server.cros.ap_configurators import ap_configurator
15from autotest_lib.server.cros.ap_configurators import ap_spec
16
17CartridgeCmd = collections.namedtuple('CartridgeCmd', ['method', 'args'])
18
19# DHCP delayed devices.  Some APs need additional time for the DHCP
20# server to come on-line.  These are AP based, so the BSS is used
21# since that is unique.
22DHCP_DELAY_DEVICES=['44:94:fc:71:88:9b', # Netgear wndr4300
23                    '10:0d:7f:4d:68:3c', # Netgear wndr3700v4
24                    '14:35:8b:0b:6c:80', # Medialink mwn_wapr150nv2
25                    '20:4e:7f:49:86:8f'] # Netgear wpn824n
26
27class StaticAPConfigurator(ap_configurator.APConfiguratorAbstract):
28    """Derived class to supply AP configuration information."""
29
30
31    def __init__(self, ap_config):
32        """
33        Initialize instance
34
35        @param ap_config: AP object to configure this instance
36
37        """
38        super(StaticAPConfigurator, self).__init__()
39        self._command_list = list()
40
41        # This allows the ability to build a generic configurator
42        # which can be used to get access to the members above.
43        self.class_name = ap_config.get_class()
44        self._short_name = ap_config.get_model()
45        self.mac_address = ap_config.get_wan_mac()
46        self.host_name = ap_config.get_wan_host()
47        # Get corresponding PDU from host name.
48        self.pdu = re.sub('host\d+', 'rpm1', self.host_name) + '.cros'
49        self.channel = ap_config.get_channel()
50        self.band = ap_config.get_band()
51        self.current_band = ap_config.get_band()
52        self.security = ap_config.get_security()
53        if self.security == ap_spec.SECURITY_TYPE_MIXED:
54           self.security = [ap_spec.SECURITY_TYPE_WPA2PSK,
55                            ap_spec.SECURITY_TYPE_WPAPSK]
56        else:
57           self.security = [self.security]
58        self.psk = ap_config.get_psk()
59        self._ssid = ap_config.get_ssid()
60        self.rpm_managed = ap_config.get_rpm_managed()
61
62        self._configuration_success = ap_constants.CONFIG_SUCCESS
63        self.config_data = ap_config
64
65        name_dict = {'Router name': self._short_name,
66                     'Controller class': self.class_name,
67                     '2.4 GHz MAC Address': ap_config.get_bss(),
68                     '5 GHz MAC Address': ap_config.get_bss5(),
69                     'Hostname': ap_config.get_wan_host()}
70
71        self._name = pprint.pformat(name_dict)
72
73        # Check if a delay needs to be added for this AP.
74        if (ap_config.get_bss() in DHCP_DELAY_DEVICES or
75            ap_config.get_bss5() in DHCP_DELAY_DEVICES):
76            self._dhcp_delay = 60
77
78        if self.rpm_managed:
79            rpm_frontend_server = global_config.global_config.get_config_value(
80                    'CROS', 'rpm_frontend_uri')
81            self.rpm_client = xmlrpclib.ServerProxy(
82                    rpm_frontend_server, verbose=False)
83
84
85    def __str__(self):
86        """Prettier display of the object"""
87        return('AP Name: %s\n'
88               'BSS: %s\n'
89               'SSID: %s\n'
90               'Short name: %s' % (self.name,
91                   self.config_data.get_bss(), self._ssid,
92                   self.short_name))
93
94
95    @property
96    def ssid(self):
97        """Returns the SSID."""
98        return self._ssid
99
100
101    def power_down_router(self):
102        """power down via rpm"""
103        if self.rpm_managed:
104            self._command_list.append(CartridgeCmd(
105                    self.rpm_client.queue_request,
106                    [self.host_name, 'OFF']))
107
108
109    def power_up_router(self):
110        """power up via rpm"""
111        if self.rpm_managed:
112            self._command_list.append(CartridgeCmd(
113                    self.rpm_client.queue_request,
114                    [self.host_name, 'ON']))
115
116
117    def set_using_ap_spec(self, set_ap_spec, power_up=True):
118        """
119        Sets all configurator options.
120
121        Note: for StaticAPs there is no config required, so the only action
122        here is to power up if needed
123
124        @param set_ap_spec: APSpec object
125
126        """
127        if power_up and self.rpm_managed:
128            self.power_up_router()
129
130
131    def apply_settings(self):
132        """Allow cartridge to run commands in _command_list"""
133        self.check_pdu_status()
134        for command in self._command_list:
135            command.method(*command.args)
136
137
138    def reset_command_list(self):
139        """Resets all internal command state."""
140        self._command_list = list()
141
142
143    @property
144    def name(self):
145        """Returns a string to describe the router."""
146        return self._name
147
148
149    @property
150    def short_name(self):
151        """Returns a short string to describe the router."""
152        return self._short_name
153
154
155    def get_supported_bands(self):
156        """Returns a list of dictionaries describing the supported bands.
157
158        Example: returned is a dictionary of band and a list of channels. The
159                 band object returned must be one of those defined in the
160                 __init___ of this class.
161
162        supported_bands = [{'band' : self.band_2GHz,
163                            'channels' : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]},
164                           {'band' : self.band_5ghz,
165                            'channels' : [26, 40, 44, 48, 149, 153, 165]}]
166
167        @return a list of dictionaries as described above
168
169        """
170        supported_bands = [{'band' : self.band,
171                            'channels' : [self.channel]}]
172
173        return supported_bands
174
175
176    def get_supported_modes(self):
177        """
178        Returns a list of dictionaries describing the supported modes.
179
180        Example: returned is a dictionary of band and a list of modes. The band
181                 and modes objects returned must be one of those defined in the
182                 __init___ of this class.
183
184        supported_modes = [{'band' : ap_spec.BAND_2GHZ,
185                            'modes' : [mode_b, mode_b | mode_g]},
186                           {'band' : ap_spec.BAND_5GHZ,
187                            'modes' : [mode_a, mode_n, mode_a | mode_n]}]
188
189        @return a list of dictionaries as described above
190
191        """
192        supported_modes = [{'band' : self.band,
193                            'modes' : [ap_spec.DEFAULT_5GHZ_MODE
194                    if self.channel in ap_spec.VALID_5GHZ_CHANNELS
195                    else ap_spec.DEFAULT_2GHZ_MODE]}]
196
197        return supported_modes
198
199
200    def is_visibility_supported(self):
201        """
202        Returns if AP supports setting the visibility (SSID broadcast).
203
204        @return False
205
206        """
207        return False
208
209
210    def is_band_and_channel_supported(self, band, channel):
211        """
212        Returns if a given band and channel are supported.
213
214        @param band: the band to check if supported
215        @param channel: the channel to check if supported
216
217        @return True if combination is supported; False otherwise.
218
219        """
220        bands = self.get_supported_bands()
221        for current_band in bands:
222            if (current_band['band'] == band and
223                channel in current_band['channels']):
224                return True
225        return False
226
227
228    def is_security_mode_supported(self, security_mode):
229        """
230        Returns if a given security_type is supported.
231
232        @param security_mode: one of the following modes:
233                         self.security_disabled,
234                         self.security_wep,
235                         self.security_wpapsk,
236                         self.security_wpa2psk
237
238        @return True if the security mode is supported; False otherwise.
239
240        """
241        return security_mode in self.security
242
243
244    def get_association_parameters(self):
245        """
246        Creates an AssociationParameters from the configured AP.
247
248        @returns AssociationParameters for the configured AP.
249
250        """
251        security_config = None
252        if (ap_spec.SECURITY_TYPE_WPAPSK in self.security or
253            ap_spec.SECURITY_TYPE_WPA2PSK in self.security):
254            # Not all of this is required but doing it just in case.
255            security_config = xmlrpc_security_types.WPAConfig(
256                    psk=self.psk,
257                    wpa_mode=xmlrpc_security_types.WPAConfig.MODE_MIXED_WPA,
258                    wpa_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP,
259                                 xmlrpc_security_types.WPAConfig.CIPHER_TKIP],
260                    wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP])
261        # TODO(jabele) Allow StaticAPs configured as hidden
262        #              by way of the ap_config file
263        return xmlrpc_datatypes.AssociationParameters(
264                ssid=self._ssid, security_config=security_config,
265                discovery_timeout=45, association_timeout=30,
266                configuration_timeout=30, is_hidden=False)
267