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
5from autotest_lib.client.common_lib.cros.network import iw_runner
6
7
8# Supported bands
9BAND_2GHZ = '2.4GHz'
10BAND_5GHZ = '5GHz'
11
12# List of valid bands.
13VALID_BANDS = [BAND_2GHZ, BAND_5GHZ]
14
15# List of valid 802.11 protocols (modes).
16MODE_A = 0x01
17MODE_B = 0x02
18MODE_G = 0x04
19MODE_N = 0x08
20MODE_AC = 0x10
21MODE_AUTO = 0x20
22MODE_M = MODE_A | MODE_B | MODE_G # Used for standard maintenance
23MODE_D = MODE_A | MODE_B | MODE_N # International roaming extensions
24
25# List of valid modes.
26VALID_MODES = [MODE_A, MODE_AC, MODE_AUTO, MODE_B, MODE_D, MODE_G, MODE_M,
27               MODE_N]
28VALID_2GHZ_MODES = [MODE_B, MODE_G, MODE_N]
29VALID_5GHZ_MODES = [MODE_A, MODE_AC, MODE_N]
30
31# Supported security types
32SECURITY_TYPE_DISABLED = iw_runner.SECURITY_OPEN
33SECURITY_TYPE_WEP = iw_runner.SECURITY_WEP
34SECURITY_TYPE_WPAPSK = iw_runner.SECURITY_WPA
35SECURITY_TYPE_WPA2PSK = iw_runner.SECURITY_WPA2
36# Mixed mode security is wpa/wpa2
37SECURITY_TYPE_MIXED = iw_runner.SECURITY_MIXED
38
39WEP_AUTHENTICATION_OPEN = object()
40WEP_AUTHENTICATION_SHARED = object()
41
42# List of valid securities.
43# TODO (krisr) the configurators do not support WEP at this time.
44VALID_SECURITIES = [SECURITY_TYPE_DISABLED,
45                    SECURITY_TYPE_WPAPSK,
46                    SECURITY_TYPE_WPA2PSK]
47
48# List of valid channels.
49VALID_2GHZ_CHANNELS = range(1,15)
50VALID_5GHZ_CHANNELS = [36, 40, 44, 48, 149, 153, 157, 161, 165]
51
52# Frequency to channel conversion table
53CHANNEL_TABLE = {2412: 1, 2417: 2, 2422: 3,
54                 2427: 4, 2432: 5, 2437: 6,
55                 2442: 7, 2447: 8, 2452: 9,
56                 2457: 10, 2462: 11, 2467: 12,
57                 2472: 13, 2484: 14, 5180: 36,
58                 5200: 40, 5220: 44, 5240: 48,
59                 5745: 149, 5765: 153, 5785: 157,
60                 5805: 161, 5825: 165}
61
62# This only works because the frequency table is one to one
63# for channels/frequencies.
64FREQUENCY_TABLE = dict((v,k) for k,v in CHANNEL_TABLE.iteritems())
65
66# Configurator type
67CONFIGURATOR_STATIC = 1
68CONFIGURATOR_DYNAMIC = 2
69CONFIGURATOR_ANY = 3
70
71# Default values
72DEFAULT_BAND = BAND_2GHZ
73
74DEFAULT_2GHZ_MODE = MODE_G
75DEFAULT_5GHZ_MODE = MODE_A
76
77DEFAULT_SECURITY_TYPE = SECURITY_TYPE_DISABLED
78
79DEFAULT_2GHZ_CHANNEL = 5
80DEFAULT_5GHZ_CHANNEL = 149
81
82# Convenience method to convert modes and bands to human readable strings.
83def band_string_for_band(band):
84    """Returns a human readable string of the band
85
86    @param band: band object
87    @returns: string representation of the band
88    """
89    if band == BAND_2GHZ:
90        return '2.4 GHz'
91    elif band == BAND_5GHZ:
92        return '5 GHz'
93
94
95def mode_string_for_mode(mode):
96    """Returns a human readable string of the mode.
97
98    @param mode: integer, the mode to convert.
99    @returns: string representation of the mode
100    """
101    string_table = {MODE_A:'a', MODE_AC:'ac', MODE_B:'b', MODE_G:'g',
102                    MODE_N:'n'}
103
104    if mode == MODE_AUTO:
105        return 'Auto'
106    total = 0
107    string = ''
108    for current_mode in sorted(string_table.keys()):
109        i = current_mode & mode
110        total = total | i
111        if i in string_table:
112            string = string + string_table[i] + '/'
113    if total == MODE_M:
114        string = 'm'
115    elif total == MODE_D:
116        string = 'd'
117    if string[-1] == '/':
118        return string[:-1]
119    return string
120
121
122class APSpec(object):
123    """Object to specify an APs desired capabilities.
124
125    The APSpec object is immutable.  All of the parameters are optional.
126    For those not given the defaults listed above will be used.  Validation
127    is done on the values to make sure the spec created is valid.  If
128    validation fails a ValueError is raised.
129    """
130
131
132    def __init__(self, visible=True, security=SECURITY_TYPE_DISABLED,
133                 band=None, mode=None, channel=None, hostnames=None,
134                 configurator_type=CONFIGURATOR_ANY,
135                 # lab_ap set to true means the AP must be in the lab;
136                 # if it set to false the AP is outside of the lab.
137                 lab_ap=True):
138        super(APSpec, self).__init__()
139        self._visible = visible
140        self._security = security
141        self._mode = mode
142        self._channel = channel
143        self._hostnames = hostnames
144        self._configurator_type = configurator_type
145        self._lab_ap = lab_ap
146        self._webdriver_hostname = None
147
148        if not self._channel and (self._mode == MODE_N or not self._mode):
149            if band == BAND_2GHZ or not band:
150                self._channel = DEFAULT_2GHZ_CHANNEL
151                if not self._mode:
152                    self._mode = DEFAULT_2GHZ_MODE
153            elif band == BAND_5GHZ:
154                self._channel = DEFAULT_5GHZ_CHANNEL
155                if not self._mode:
156                    self._mode = DEFAULT_5GHZ_MODE
157            else:
158                raise ValueError('Invalid Band.')
159
160        self._validate_channel_and_mode()
161
162        if ((band == BAND_2GHZ and self._mode not in VALID_2GHZ_MODES) or
163            (band == BAND_5GHZ and self._mode not in VALID_5GHZ_MODES)):
164            raise ValueError('Conflicting band and modes/channels.')
165
166        self._validate_security()
167
168
169    def __str__(self):
170        return ('AP Specification:\n'
171                'visible=%r\n'
172                'security=%s\n'
173                'band=%s\n'
174                'mode=%s\n'
175                'channel=%d\n'
176                'password=%s' % (self._visible, self._security,
177                                 band_string_for_band(self.band),
178                                 mode_string_for_mode(self._mode),
179                                 self._channel, self._password))
180
181
182    @property
183    def password(self):
184        """Returns the password for password supported secured networks."""
185        return self._password
186
187
188
189    @property
190    def visible(self):
191        """Returns if the SSID is visible or not."""
192        return self._visible
193
194
195    @property
196    def security(self):
197        """Returns the type of security."""
198        return self._security
199
200
201    @property
202    def band(self):
203        """Return the band."""
204        if self._channel in VALID_2GHZ_CHANNELS:
205            return BAND_2GHZ
206        return BAND_5GHZ
207
208
209    @property
210    def mode(self):
211        """Return the mode."""
212        return self._mode
213
214
215    @property
216    def channel(self):
217        """Return the channel."""
218        return self._channel
219
220
221    @property
222    def frequency(self):
223        """Return the frequency equivalent of the channel."""
224        return FREQUENCY_TABLE[self._channel]
225
226
227    @property
228    def hostnames(self):
229        """Return the hostnames; this may be None."""
230        return self._hostnames
231
232
233    @property
234    def configurator_type(self):
235        """Returns the configurator type."""
236        return self._configurator_type
237
238
239    @property
240    def lab_ap(self):
241        """Returns if the AP should be in the lab or not."""
242        return self._lab_ap
243
244
245    @property
246    def webdriver_hostname(self):
247        """Returns locked webdriver hostname."""
248        return self._webdriver_hostname
249
250
251    @webdriver_hostname.setter
252    def webdriver_hostname(self, value):
253        """Sets webdriver_hostname to locked instance.
254
255        @param value: locked webdriver hostname
256
257        """
258        self._webdriver_hostname = value
259
260
261    def _validate_channel_and_mode(self):
262        """Validates the channel and mode selected are correct.
263
264        raises ValueError: if the channel or mode selected is invalid
265        """
266        if self._channel and self._mode:
267            if ((self._channel in VALID_2GHZ_CHANNELS and
268                 self._mode not in VALID_2GHZ_MODES) or
269                (self._channel in VALID_5GHZ_CHANNELS and
270                 self._mode not in VALID_5GHZ_MODES)):
271                raise ValueError('Conflicting mode/channel has been selected.')
272        elif self._channel:
273            if self._channel in VALID_2GHZ_CHANNELS:
274                self._mode = DEFAULT_2GHZ_MODE
275            elif self._channel in VALID_5GHZ_CHANNELS:
276                self._mode = DEFAULT_5GHZ_MODE
277            else:
278                raise ValueError('Invalid channel passed.')
279        else:
280            if self._mode in VALID_2GHZ_MODES:
281                self._channel = DEFAULT_2GHZ_CHANNEL
282            elif self._mode in VALID_5GHZ_MODES:
283                self._channel = DEFAULT_5GHZ_CHANNEL
284            else:
285                raise ValueError('Invalid mode passed.')
286
287
288    def _validate_security(self):
289        """Sets a password for security settings that need it.
290
291        raises ValueError: if the security setting passed is invalid.
292        """
293        if self._security == SECURITY_TYPE_DISABLED:
294            self._password = None
295        elif (self._security == SECURITY_TYPE_WPAPSK or
296             self._security == SECURITY_TYPE_WPA2PSK):
297             self._password = 'chromeos'
298        else:
299            raise ValueError('Invalid security passed.')
300