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