Merge branch 'bug_246_cutlistedit'
[enigma2.git] / lib / python / Plugins / SystemPlugins / WirelessLan / iwlibs.py
1 # -*- coding: ISO-8859-1 -*-
2 # python-wifi -- a wireless library to access wireless cards via python
3 # Copyright (C) 2004, 2005, 2006 RĂ³man Joost
4
5 # Contributions from:
6 #   Mike Auty <m.auty@softhome.net> (Iwscanresult, Iwscan)
7 #
8 #    This library is free software; you can redistribute it and/or
9 #    modify it under the terms of the GNU Lesser General Public License
10 #    as published by the Free Software Foundation; either version 2.1 of
11 #    the License, or (at your option) any later version.
12 #
13 #    This library is distributed in the hope that it will be useful, but
14 #    WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 #    Lesser General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Lesser General Public
19 #    License along with this library; if not, write to the Free Software
20 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 #    USA 
22
23 from struct import pack as struct_pack, \
24         unpack as struct_unpack, \
25         calcsize as struct_calcsize
26
27 from array import array
28 from math import ceil, log10
29 from fcntl import ioctl
30 from socket import AF_INET, SOCK_DGRAM, socket
31 from time import sleep
32 from re import compile
33
34 from flags import *    
35
36 def getNICnames():
37     """ extract wireless device names of /proc/net/wireless 
38         
39         returns empty list if no devices are present
40
41         >>> getNICnames()
42         ['eth1', 'wifi0']
43     """
44     device = compile('[a-z]+[0-9]+')
45     ifnames = []
46     
47     f = open('/proc/net/wireless', 'r')
48     data = f.readlines()
49     for line in data:
50         try:
51             ifnames.append(device.search(line).group())
52         except AttributeError:
53             pass 
54     # if we couldn't lookup the devices, try to ask the kernel
55     if ifnames == []:
56         ifnames = getConfiguredNICnames()
57     
58     return ifnames
59
60 def getConfiguredNICnames():
61     """get the *configured* ifnames by a systemcall
62        
63        >>> getConfiguredNICnames()
64        []
65     """
66     iwstruct = Iwstruct()
67     ifnames = []
68     buff = array('c', '\0'*1024)
69     caddr_t, length = buff.buffer_info()
70     s = iwstruct.pack('iP', length, caddr_t)
71     try:
72         result = iwstruct._fcntl(SIOCGIFCONF, s)
73     except IOError, (i, e):
74         return i, e
75    
76     # get the interface names out of the buffer
77     for i in range(0, 1024, 32):
78         ifname = buff.tostring()[i:i+32]
79         ifname = struct_unpack('32s', ifname)[0]
80         ifname = ifname.split('\0', 1)[0]
81         if ifname:
82             # verify if ifnames are really wifi devices
83             wifi = Wireless(ifname)
84             result = wifi.getAPaddr()
85             if result[0] == 0:
86                 ifnames.append(ifname)
87
88     return ifnames  
89
90 def makedict(**kwargs):
91     return kwargs
92
93
94 class Wireless(object):
95     """Access to wireless interfaces"""
96     
97     def __init__(self, ifname):
98         self.sockfd = socket(AF_INET, SOCK_DGRAM)
99         self.ifname = ifname
100         self.iwstruct = Iwstruct()
101     
102     def getAPaddr(self):
103         """ returns accesspoint mac address 
104         
105             >>> from iwlibs import Wireless, getNICnames
106             >>> ifnames = getNICnames()
107             >>> ifnames
108             ['eth1', 'wifi0']
109             >>> wifi = Wireless(ifnames[0])
110             >>> wifi.getAPaddr()
111             '00:0D:88:8E:4E:93'
112
113             Test with non-wifi card:
114             >>> wifi = Wireless('eth0')
115             >>> wifi.getAPaddr()
116             (95, 'Operation not supported')
117
118             Test with non-existant card:
119             >>> wifi = Wireless('eth2')
120             >>> wifi.getAPaddr()
121             (19, 'No such device')
122         """
123         buff, s = self.iwstruct.pack_wrq(32)
124         i, result = self.iwstruct.iw_get_ext(self.ifname, 
125                                              SIOCGIWAP,
126                                              data=s)
127         if i > 0:
128             return result
129
130         return self.iwstruct.getMAC(result)
131    
132     def getBitrate(self):
133         """returns device currently set bit rate 
134         
135             >>> from iwlibs import Wireless
136             >>> wifi = Wireless('eth1')
137             >>> wifi.getBitrate()
138             '11 Mb/s'
139         """
140         i, result = self.iwstruct.iw_get_ext(self.ifname, 
141                                             SIOCGIWRATE)
142         if i > 0:
143             return result
144         iwfreq = Iwfreq(result)
145         return iwfreq.getBitrate()
146     
147     def getBitrates(self):
148         """returns the number of available bitrates for the device
149            
150             >>> from iwlibs import Wireless
151             >>> wifi = Wireless('eth1')
152             >>> num, rates = wifi.getBitrates()
153             >>> num == len(rates)
154             True
155         """
156         range = Iwrange(self.ifname)
157         if range.errorflag:
158             return (range.errorflag, range.error)
159         return (range.num_bitrates, range.bitrates)
160
161     def getChannelInfo(self):
162         """returns the number of channels and available frequency for
163            the device
164
165             >>> from iwlibs import Wireless
166             >>> wifi = Wireless('eth1')
167             >>> num, rates = wifi.getChannelInfo()
168             >>> num == len(rates)
169             True
170             """
171         range = Iwrange(self.ifname)
172         if range.errorflag:
173             return (range.errorflag, range.error)
174         return (range.num_channels, range.frequencies)
175
176     def getEssid(self):
177         """get essid information
178             
179             >>> from iwlibs import Wireless
180             >>> wifi = Wireless('eth1')
181             >>> wifi.getEssid()
182             'romanofski'
183         """
184         essid = ""
185         buff, s = self.iwstruct.pack_wrq(32)
186         i, result = self.iwstruct.iw_get_ext(self.ifname, 
187                                              SIOCGIWESSID, 
188                                              data=s)
189         if i > 0:
190             return result
191         str = buff.tostring()
192         return str.strip('\x00')
193
194     def setEssid(self, essid):
195         """set essid """
196         raise NotImplementedError
197         if len(essid) > IW_ESSID_MAX_SIZE:
198             return "essid to big"
199         buff, s = self.iwstruct.pack_test(essid, 32)
200         i, result = self.iwstruct.iw_get_ext(self.ifname, 
201                                              SIOCSIWESSID, 
202                                              data=s)
203         if i > 0:
204             return result
205
206     def getEncryption(self):
207         """get encryption information which is probably a string of '*',
208         'open', 'private'
209             
210             as a normal user, you will get a 'Operation not permitted'
211             error:
212         
213             >>> from iwlibs import Wireless
214             >>> wifi = Wireless('eth1')
215             >>> wifi.getEncryption()
216             (1, 'Operation not permitted')
217         """
218         iwpoint = Iwpoint(self.ifname)
219         if iwpoint.errorflag:
220             return (iwpoint.errorflag, iwpoint.error)
221         return iwpoint.getEncryptionKey()
222
223     def getFragmentation(self):
224         """returns fragmentation threshold 
225            
226            It depends on what the driver says. If you have fragmentation
227            threshold turned on, you'll get an int. If it's turned of
228            you'll get a string: 'off'.
229             >>> from iwlibs import Wireless
230             >>> wifi = Wireless('eth1')
231             >>> wifi.getFragmentation()
232             'off'
233         """
234         iwparam = Iwparam(self.ifname, SIOCGIWFRAG)
235         if iwparam.errorflag:
236             return (iwparam.errorflag, iwparam.error)
237         return iwparam.getValue()
238         
239     def getFrequency(self):
240         """returns currently set frequency of the card 
241             
242             >>> from iwlibs import Wireless
243             >>> wifi = Wireless('eth1')
244             >>> wifi.getFrequency()
245             '2.417GHz' 
246         """
247         i, r = self.iwstruct.iw_get_ext(self.ifname, 
248                                         SIOCGIWFREQ)
249         if i > 0:
250             return (i, r)
251         iwfreq = Iwfreq(r)
252         return iwfreq.getFrequency()
253     
254         
255     def getMode(self):
256         """returns currently set operation mode 
257             
258             >>> from iwlibs import Wireless
259             >>> wifi = Wireless('eth1')
260             >>> wifi.getMode()
261             'Managed' 
262         """
263         i, result = self.iwstruct.iw_get_ext(self.ifname, 
264                                              SIOCGIWMODE)
265         if i > 0:
266             return result
267         mode = self.iwstruct.unpack('i', result[:4])[0]
268         return modes[mode]
269
270     def setMode(self, mode):
271         """sets the operation mode """
272         try:
273             this_modes = [x.lower() for x in modes]
274             mode = mode.lower()
275             wifimode = this_modes.index(mode)
276         except ValueError:
277             return "Invalid operation mode!"
278         
279         s = self.iwstruct.pack('I', wifimode)
280         i, result = self.iwstruct.iw_get_ext(self.ifname, 
281                                              SIOCSIWMODE, 
282                                              data=s)
283         if i > 0:
284             return result
285     
286     def getWirelessName(self):
287         """ returns wireless name 
288             
289             >>> from iwlibs import Wireless
290             >>> wifi = Wireless('eth1')
291             >>> wifi.getWirelessName()
292             'IEEE 802.11-DS'
293         """
294         i, result = self.iwstruct.iw_get_ext(self.ifname, 
295                                              SIOCGIWNAME)
296         if i > 0:
297             return result
298         return result.split('\0')[0]
299     
300     def getPowermanagement(self):
301         """returns power management settings 
302             
303             >>> from iwlibs import Wireless
304             >>> wifi = Wireless('eth1')
305             >>> wifi.getPowermanagement()
306             'off'
307         """
308         iwparam = Iwparam(self.ifname, SIOCGIWPOWER)
309         if iwparam.errorflag:
310             return (iwparam.errorflag, iwparam.error)
311         return iwparam.getValue()
312
313     
314     def getRetrylimit(self):
315         """returns limit retry/lifetime
316
317             man iwconfig:
318             Most cards have MAC retransmissions, and some  allow  to set
319             the behaviour of the retry mechanism.
320                      
321             >>> from iwlibs import Wireless
322             >>> wifi = Wireless('eth1')
323             >>> wifi.getRetrylimit()
324             16
325         """
326         iwparam = Iwparam(self.ifname, SIOCGIWRETRY)
327         if iwparam.errorflag:
328             return (iwparam.errorflag, iwparam.error)
329         return iwparam.getValue()
330     
331     def getRTS(self):
332         """returns rts threshold 
333             
334             returns int, 'auto', 'fixed', 'off'
335         
336             man iwconfig:
337             RTS/CTS adds a handshake before each packet transmission to
338             make sure that the channel is clear. This adds overhead, but
339             increases performance in case of hidden  nodes or  a large
340             number of active nodes. This parameter sets the size of the
341             smallest packet for which the node sends RTS;  a value equal
342             to the maximum packet size disable the mechanism. 
343             
344             >>> from iwlibs import Wireless
345             >>> wifi = Wireless('eth1')
346             >>> wifi.getRTS()
347             'off'
348         """
349         iwparam = Iwparam(self.ifname, SIOCGIWRTS)
350         if iwparam.errorflag:
351             return (iwparam.errorflag, iwparam.error)
352         return iwparam.getValue()
353     
354     def getSensitivity(self):
355         """returns sensitivity information 
356         
357             man iwconfig:
358             This is the lowest signal level for which the hardware
359             attempt  packet  reception, signals  weaker  than  this are
360             ignored. This is used to avoid receiving background noise,
361             so you should  set  it according  to  the  average noise
362             level. Positive values are assumed to be the raw value used
363             by the hardware  or a percentage, negative values are
364             assumed to be dBm.
365         
366             >>> from iwlibs import Wireless
367             >>> wifi = Wireless('eth1')
368             >>> wifi.getSensitivity()
369             'off'
370             
371         """
372         iwparam = Iwparam(self.ifname, SIOCGIWSENS)
373         if iwparam.errorflag:
374             return (iwparam.errorflag, iwparam.error)
375         return iwparam.getValue()
376         
377     def getTXPower(self):
378         """returns transmit power in dBm 
379         
380             >>> from iwlibs import Wireless
381             >>> wifi = Wireless('eth1')
382             >>> wifi.getTXPower()
383             '17 dBm'
384         """
385         i, r = self.iwstruct.iw_get_ext(self.ifname, 
386                                         SIOCGIWTXPOW)
387         if i > 0:
388             return (i, r)
389         iwfreq = Iwfreq(r)
390         return iwfreq.getTransmitPower()
391          
392     def getStatistics(self):
393         """returns statistics information which can also be found in
394            /proc/net/wireless 
395         """
396         iwstats = Iwstats(self.ifname)
397         if iwstats.errorflag > 0:
398             return (iwstats.errorflag, iwstats.error)
399         return [iwstats.status, iwstats.qual, iwstats.discard,
400             iwstats.missed_beacon]
401
402     def scan(self):
403         """returns Iwscanresult objects, after a successful scan"""
404         iwscan = Iwscan(self.ifname)
405         return iwscan.scan()
406
407
408 class Iwstruct(object):
409     """basic class to handle iwstruct data """
410     
411     def __init__(self):
412         self.idx = 0
413         self.sockfd = socket(AF_INET, SOCK_DGRAM)
414
415     def parse_data(self, fmt, data):
416         """ unpacks raw C data """
417         size = struct_calcsize(fmt)
418         idx = self.idx
419
420         str = data[idx:idx + size]
421         self.idx = idx+size
422         value = struct_unpack(fmt, str)
423
424         # take care of a tuple like (int, )
425         if len(value) == 1:
426             return value[0]
427         else:
428             return value
429     
430     def pack(self, fmt, *args):
431         """ calls struct_pack and returns the result """
432         return struct_pack(fmt, *args)
433
434     def pack_wrq(self, buffsize):
435         """ packs wireless request data for sending it to the kernel """
436         # Prepare a buffer
437         # We need the address of our buffer and the size for it. The
438         # ioctl itself looks for the pointer to the address in our
439         # memory and the size of it.
440         # Dont change the order how the structure is packed!!!
441         buff = array('c', '\0'*buffsize)
442         caddr_t, length = buff.buffer_info()
443         s = struct_pack('Pi', caddr_t, length)
444         return buff, s
445     
446     def pack_test(self, string, buffsize):
447         """ packs wireless request data for sending it to the kernel """
448         buffsize = buffsize - len(string)
449         buff = array('c', string+'\0'*buffsize)
450         caddr_t, length = buff.buffer_info()
451         s = struct_pack('Pii', caddr_t, length, 1)
452         return buff, s
453
454     def unpack(self, fmt, packed_data):
455         """ unpacks data with given format """
456         return struct_unpack(fmt, packed_data)
457
458     def _fcntl(self, request, args):
459         return ioctl(self.sockfd.fileno(), request, args)
460     
461     def iw_get_ext(self, ifname, request, data=None):
462         """ read information from ifname """
463         # put some additional data behind the interface name
464         if data is not None:
465             buff = IFNAMSIZE-len(ifname)
466             ifreq = ifname + '\0'*buff
467             ifreq = ifreq + data
468         else:
469             ifreq = (ifname + '\0'*32)
470             
471         try:
472             result = self._fcntl(request, ifreq)
473         except IOError, (i, e):
474             return i, e
475         
476         return (0, result[16:])
477
478     def getMAC(self, packed_data):
479         """ extracts mac addr from packed data and returns it as str """
480         mac_addr = struct_unpack('xxBBBBBB', packed_data[:8])
481         return "%02X:%02X:%02X:%02X:%02X:%02X" % mac_addr
482
483 class Iwparam(object):
484     """class to hold iwparam data """
485     
486     def __init__(self, ifname, ioctl):
487         # (i) value, (b) fixed, (b) disabled, (b) flags
488         self.fmt = "ibbH"
489         self.value = 0
490         self.fixed = 0
491         self.disabled = 0
492         self.flags = 0
493         self.errorflag = 0
494         self.error = ""
495         self.ioctl = ioctl 
496         self.ifname = ifname
497         self.update()
498     
499     def getValue(self):
500         """returns the value if not disabled """
501
502         if self.disabled:
503             return 'off'
504         if self.flags & IW_RETRY_TYPE == 0:
505             return self.getRLAttributes()
506         else:
507             return self.getPMAttributes()
508
509     def getRLAttributes(self):
510         """returns a string with attributes determined by self.flags
511         """
512         return self.value
513
514     def getPMAttributes(self):
515         """returns a string with attributes determined by self.flags
516            and IW_POWER*
517         """
518         result = ""
519         
520         # Modifiers
521         if self.flags & IW_POWER_MIN == 0:
522             result = " min"
523         if self.flags & IW_POWER_MAX == 0:
524             result = " max"
525             
526         # Type
527         if self.flags & IW_POWER_TIMEOUT == 0:
528             result = " period:" 
529         else:
530             result = " timeout:"
531         # Value with or without units
532         # IW_POWER_RELATIVE - value is *not* in s/ms/us
533         if self.flags & IW_POWER_RELATIVE:
534             result += "%f" %(float(self.value)/MEGA)
535         else:
536             if self.value >= MEGA:
537                 result += "%fs" %(float(self.value)/MEGA)
538             elif self.value >= KILO:
539                 result += "%fms" %(float(self.value)/KILO)
540             else:
541                 result += "%dus" % self.value
542
543         return result
544         
545     def update(self):
546         iwstruct = Iwstruct()
547         i, r = iwstruct.iw_get_ext(self.ifname, 
548                                    self.ioctl)
549         if i > 0:
550             self.errorflag = i
551             self.error = r
552         self._parse(r)
553     
554     def _parse(self, data):
555         """ unpacks iwparam data """
556         iwstruct = Iwstruct()
557         self.value, self.fixed, self.disabled, self.flags =\
558             iwstruct.parse_data(self.fmt, data)
559         
560 class Iwfreq(object):
561     """ class to hold iwfreq data
562         delegates to Iwstruct class
563     """
564     
565     def __init__(self, data=None):
566         self.fmt = "ihbb"
567         if data is not None:
568             self.frequency = self.parse(data)
569         else:
570             self.frequency = 0
571         self.iwstruct = Iwstruct()
572         
573     def __getattr__(self, attr):
574         return getattr(self.iwstruct, attr)
575
576     def parse(self, data):
577         """ unpacks iwparam"""
578         
579         size = struct_calcsize(self.fmt)
580         m, e, i, pad = struct_unpack(self.fmt, data[:size])
581         # XXX well, its not *the* frequency - we need a better name
582         if e == 0:
583             return m
584         else:
585             return float(m)*10**e
586     
587     def getFrequency(self):
588         """returns Frequency (str) 
589             
590            data - binary data returned by systemcall (iw_get_ext())
591         """
592         freq = self.frequency
593         
594         if freq >= GIGA:
595             return "%0.3fGHz" %(freq/GIGA)
596
597         if freq >= MEGA:
598             return "%0.3fMHZ" %(freq/MEGA)
599
600         if freq >= KILO:
601             return "%0.3fKHz" %(freq/KILO)
602     
603     def getBitrate(self):
604         """ returns Bitrate in Mbit 
605         
606            data - binary data returned by systemcall (iw_get_ext())
607         """
608         bitrate = self.frequency
609
610         if bitrate >= GIGA:
611             return "%i Gb/s" %(bitrate/GIGA)
612
613         if bitrate >= MEGA:
614             return "%i Mb/s" %(bitrate/MEGA)
615         
616         if bitrate >= KILO:
617             return "%i Kb/s" %(bitrate/KILO)
618
619     def getTransmitPower(self):
620         """ returns transmit power in dbm """
621         # XXX something flaky is going on with m and e
622         # eg. m = 50 and e should than be 0, because the number is stored in
623         # m and don't needs to be recalculated
624         return "%i dBm" %self.mw2dbm(self.frequency/10)
625     
626     def getChannel(self, freq):
627         """returns channel information given by frequency
628            
629            returns None if frequency can't be converted
630            freq = frequency to convert (int)
631            iwrange = Iwrange object
632         """
633         
634         try:
635             freq = float(freq)
636         except:
637             return None
638         
639         lut = {}
640         #13 Channels beginning at 2.412GHz and inreasing by 0,005 GHz steps
641         for i in range(0,12):
642             cur = float( 2.412 + ( i * 0.005 ) )
643             lut[str(cur)] = i+1
644         # Channel 14 need special actions ;)
645         lut['2.484'] = 14
646         
647         
648         if str(freq) in lut.keys():
649                 return lut[str(freq)]
650         
651         return None
652     
653           
654     def mw2dbm(self, mwatt):
655         """ converts mw to dbm(float) """
656         return ceil(10.0 * log10(mwatt))
657         
658     def _setFrequency(self, list):
659         """sets self.frequency by given list 
660            
661            currently only used by Iwrange
662         """
663         assert len(list) == 4
664         m, e, i, pad = list
665         if e == 0:
666             self.frequency = m
667         else:
668             self.frequency = m #float(m)*10**e
669
670 class Iwstats(object):
671     """ class to hold iwstat data """
672
673     def __init__(self, ifname):
674         # (2B) status, 4B iw_quality, 6i iw_discarded
675         self.fmt = "2B4B6i"
676         self.status = 0
677         self.qual = Iwquality()
678         self.discard = {}
679         self.missed_beacon = 0
680         self.ifname = ifname
681         self.errorflag = 0
682         self.error = ""
683         self.update()
684
685     def update(self):
686         iwstruct = Iwstruct()
687         buff, s = iwstruct.pack_wrq(32)
688         i, result = iwstruct.iw_get_ext(self.ifname, 
689                                         SIOCGIWSTATS, 
690                                         data=s)
691         if i > 0:
692             self.error = result
693             self.errorflag = i
694         self._parse(buff.tostring())
695     
696     def _parse(self, data):
697         """ unpacks iwstruct data """
698         struct = Iwstruct()
699         iwqual = Iwquality()
700         iwstats_data = struct.parse_data(self.fmt, data)
701         
702         self.status = iwstats_data[0:2]
703         self.qual.quality, self.qual.sl, self.qual.nl,\
704             self.qual.flags = iwstats_data[2:6]
705         nwid, code, frag, retries, flags = iwstats_data[6:11]
706         self.missed_beacon = iwstats_data[11:12][0]
707         self.discard = makedict(nwid=nwid, code=code,
708             fragment=frag, retries=retries, misc=flags)
709
710 class Iwquality(object):
711     """ class to hold iwquality data """
712
713     def __init__(self):
714         self.quality = 0
715         self.sl = 0
716         self.nl = 0
717         self.updated = 0
718         self.fmt = "4B"
719
720     def parse(self, data):
721         """ unpacks iwquality data """
722         struct = Iwstruct()
723         qual, sl, nl, flags = struct.parse_data(self.fmt, data)
724
725         # compute signal and noise level
726         self.signal_level = sl
727         self.noise_level = nl
728
729         # asign the other values
730         self.quality = qual
731         self.updated = flags
732
733     def setValues(self, list):
734         """ assigns values given by a list to our attributes """
735         attributes = ["quality", "signallevel", "noise_level",
736             "updated"]
737         assert len(list) == 4
738         
739         for i in range(len(list)):
740             setattr(self, attributes[i], list[i])
741     
742     def getSignallevel(self):
743         """ returns signal level """
744         return self.sl-0x100
745
746     def setSignallevel(self, sl):
747         """ sets signal level """
748         self.sl = sl
749     signallevel = property(getSignallevel, setSignallevel)
750     
751     def getNoiselevel(self):
752         """ returns noise level """
753         return self.nl - 0x100
754
755     def setNoiselevel(self):
756         raise NotImplementedError
757         self.nl = nl
758     noiselevel = property(getNoiselevel, setNoiselevel)
759
760 class Iwpoint(object):
761     """ class to hold iwpoint data """
762
763     def __init__(self, ifname):
764         self.key = [0,0,0,0]
765         self.fields = 0
766         self.flags = 0
767         # (4B) pointer to data, H length, H flags
768         self.fmt = "4BHH"
769         self.errorflag = 0
770         self.error = ""
771         self.ifname = ifname
772         self.update()
773
774     def __getattr__(self, attr):
775         return getattr(self.iwstruct, attr)
776     
777     def update(self):
778         iwstruct = Iwstruct()
779         buff, s = iwstruct.pack_wrq(32)
780         i, result = iwstruct.iw_get_ext(self.ifname, 
781                                         SIOCGIWENCODE, 
782                                         data=s)
783         if i > 0:
784             self.errorflag = i
785             self.error = result
786         self._parse(result)
787         
788     def getEncryptionKey(self):
789         """ returns encryption key as '**' or 'off' as str """
790         if self.flags & IW_ENCODE_DISABLED != 0:
791             return 'off'
792         elif self.flags & IW_ENCODE_NOKEY != 0:
793             # a key is set, so print it
794             return '**' * self.fields
795     
796     def _parse(self, data):
797         """ unpacks iwpoint data
798         """
799         iwstruct = Iwstruct()
800         ptr, ptr, ptr, ptr, self.fields, self.flags =\
801             iwstruct.parse_data(self.fmt, data)
802         self.key = [ptr, ptr, ptr, ptr]
803
804 class Iwrange(object):
805     """holds iwrange struct """
806     IW_MAX_FREQUENCIES = 32
807
808     def __init__(self, ifname):
809         self.fmt = "iiihb6ii4B4Bi32i2i2i2i2i3h8h2b2bhi8i2b3h2i2ihB17x"\
810             + self.IW_MAX_FREQUENCIES*"ihbb"
811         
812         self.ifname = ifname
813         self.errorflag = 0
814         self.error = ""
815         
816         # informative stuff
817         self.throughput = 0
818         
819         # nwid (or domain id)
820         self.min_nwid = self.max_nwid = 0
821         
822         # frequency for backward compatibility
823         self.old_num_channels = self.old_num_frequency = self.old_freq = 0
824         
825         # signal level threshold
826         self.sensitivity = 0
827         
828         # link quality
829         self.max_qual = Iwquality()
830         self.avg_qual = Iwquality()
831
832         # rates
833         self.num_bitrates = 0
834         self.bitrates = []
835
836         # rts threshold
837         self.min_rts = self.max_rts = 0
838
839         # fragmention threshold
840         self.min_frag = self.max_frag = 0
841
842         # power managment
843         self.min_pmp = self.max_pmp = 0
844         self.min_pmt = self.max_pmt = 0
845         self.pmp_flags = self.pmt_flags = self.pm_capa = 0
846
847         # encoder stuff
848         self.encoding_size = 0
849         self.num_encoding_sizes = self.max_encoding_tokens = 0
850         self.encoding_login_index = 0
851
852         # transmit power
853         self.txpower_capa = self.num_txpower = self.txpower = 0
854
855         # wireless extension version info
856         self.we_vers_compiled = self.we_vers_src = 0
857
858         # retry limits and lifetime
859         self.retry_capa = self.retry_flags = self.r_time_flags = 0
860         self.min_retry = self.max_retry = 0
861         self.min_r_time = self.max_r_time = 0
862
863         # frequency
864         self.num_channels = self.num_frequency = 0
865         self.frequencies = []
866         self.update()
867     
868     def update(self):
869         """updates Iwrange object by a system call to the kernel 
870            and updates internal attributes
871         """
872         iwstruct = Iwstruct()
873         buff, s = iwstruct.pack_wrq(640)
874         i, result = iwstruct.iw_get_ext(self.ifname, 
875                                         SIOCGIWRANGE, 
876                                         data=s)
877         if i > 0:
878             self.errorflag = i
879             self.error = result
880         data = buff.tostring()
881         self._parse(data)
882         
883     def _parse(self, data):
884         struct = Iwstruct()
885         result = struct.parse_data(self.fmt, data)
886         
887         # XXX there is maybe a much more elegant way to do this
888         self.throughput, self.min_nwid, self.max_nwid = result[0:3]
889         self.old_num_channels, self.old_num_frequency = result[3:5]
890         self.old_freq = result[5:11]
891         self.sensitivity = result[11]
892         self.max_qual.setValues(result[12:16])
893         self.avg_qual.setValues(result[16:20])
894         self.num_bitrates = result[20] # <- XXX
895         raw_bitrates = result[21:53]
896         for rate in raw_bitrates:
897             iwfreq = Iwfreq()
898             iwfreq.frequency = rate
899             br = iwfreq.getBitrate()
900             if br is not None:
901                 self.bitrates.append(br)
902             
903         self.min_rts, self.max_rts = result[53:55]
904         self.min_frag, self.max_frag = result[55:57]
905         self.min_pmp, self.max_pmp = result[57:59]
906         self.min_pmt, self.max_pmt = result[59:61]
907         self.pmp_flags, self.pmt_flags, self.pm_capa = result[61:64]
908         self.encoding_size = result[64:72]
909         self.num_encoding_sizes, self.max_encoding_tokens = result[72:74]
910         self.encoding_login_index = result[74:76]
911         self.txpower_capa, self.num_txpower = result[76:78]
912         self.txpower = result[78:86]
913         self.we_vers_compiled, self.we_vers_src = result[86:88]
914         self.retry_capa, self.retry_flags, self.r_time_flags = result[88:91]
915         self.min_retry, self.max_retry = result[91:93]
916         self.min_r_time, self.max_r_time = result[93:95]
917         self.num_channels = result[95]
918         self.num_frequency = result[96]
919         freq = result[97:]
920         
921         i = self.num_frequency
922         for x in range(0, len(freq), 4):
923             iwfreq = Iwfreq()
924             iwfreq._setFrequency(freq[x:x+4])
925             fq = iwfreq.getFrequency()
926             if fq is not None:
927                 self.frequencies.append(fq)
928             i -= 1
929             if i <= 0:
930                 break
931         
932 class Iwscan(object):
933     """class to handle AP scanning"""
934     
935     def __init__(self, ifname):
936         self.ifname = ifname
937         self.range = Iwrange(ifname)
938         self.errorflag = 0
939         self.error = ""
940         self.stream = None
941         self.aplist = None
942                 
943     def scan(self, fullscan=True):
944         """Completes a scan for available access points,
945            and returns them in Iwscanresult format
946            
947            fullscan: If False, data is read from a cache of the last scan
948                      If True, a scan is conducted, and then the data is read
949         """
950         # By default everything is fine, do not wait
951         result = 1
952         if fullscan:
953             self.setScan()
954             if self.errorflag > EPERM:
955                 raise RuntimeError, 'setScan failure ' + str(self.errorflag) + " " + str(self.error)
956                 return None
957             elif self.errorflag < EPERM:
958                 # Permission was NOT denied, therefore we must WAIT to get results
959                 result = 250
960         
961         while (result > 0):
962             sleep(result/1000)
963             result = self.getScan()
964         
965         if result < 0 or self.errorflag != 0:
966             raise RuntimeError, 'getScan failure ' + str(self.errorflag) + " " + str(self.error)
967         
968         return self.aplist
969         
970         
971     def setScan(self):
972         """Triggers the scan, if we have permission
973         """
974         iwstruct = Iwstruct()
975         s = iwstruct.pack('Pii', 0, 0, 0)
976         i, result = iwstruct.iw_get_ext(self.ifname, 
977                                         SIOCSIWSCAN,s)
978         if i > 0:
979             self.errorflag = i
980             self.error = result
981         return result
982         
983     def getScan(self):
984         """Retreives results, stored from the most recent scan
985            Returns 0 if successful, a delay if the data isn't ready yet
986            or -1 if something really nasty happened
987         """
988         iwstruct = Iwstruct()
989         i = E2BIG
990         bufflen = IW_SCAN_MAX_DATA
991         
992         # Keep resizing the buffer until it's large enough to hold the scan
993         while (i == E2BIG):
994             buff, s = iwstruct.pack_wrq(bufflen)
995             i, result = iwstruct.iw_get_ext(self.ifname, 
996                                             SIOCGIWSCAN,
997                                             data=s)
998             if i == E2BIG:
999                 pbuff, newlen = iwstruct.unpack('Pi', s)
1000                 if bufflen < newlen:
1001                     bufflen = newlen
1002                 else:
1003                     bufflen = bufflen * 2
1004         
1005         if i == EAGAIN:
1006             return 100
1007         if i > 0:
1008             self.errorflag = i
1009             self.error = result
1010             return -1
1011         
1012         pbuff, reslen = iwstruct.unpack('Pi', s)
1013         if reslen > 0:
1014             # Initialize the stream, and turn it into an enumerator
1015             self.aplist = self._parse(buff.tostring())
1016             return 0
1017         
1018     def _parse(self, data):
1019         """Parse the event stream, and return a list of Iwscanresult objects
1020         """
1021         iwstruct = Iwstruct()
1022         scanresult = None
1023         aplist = []
1024
1025         # Run through the stream, until broken
1026         while 1:
1027             # If we're the stream doesn't have enough space left for a header, break
1028             if len(data) < IW_EV_LCP_LEN:
1029                 break;
1030         
1031             # Unpack the header
1032             length, cmd = iwstruct.unpack('HH', data[:4])
1033             # If the header says the following data is shorter than the header, then break
1034             if length < IW_EV_LCP_LEN:
1035                 break;
1036
1037             # Put the events into their respective result data
1038             if cmd == SIOCGIWAP:
1039                 if scanresult is not None:
1040                     aplist.append(scanresult)
1041                 scanresult = Iwscanresult(data[IW_EV_LCP_LEN:length], self.range)
1042             elif scanresult is None:
1043                 raise RuntimeError, 'Attempting to add an event without AP data'
1044             else:
1045                 scanresult.addEvent(cmd, data[IW_EV_LCP_LEN:length])
1046             
1047             # We're finished with the preveious event
1048             data = data[length:]
1049         
1050         # Don't forgset the final result
1051         if scanresult.bssid != "00:00:00:00:00:00":
1052             aplist.append(scanresult)
1053         else:
1054             raise RuntimeError, 'Attempting to add an AP without a bssid'
1055         return aplist
1056
1057 class Iwscanresult(object):
1058     """An object to contain all the events associated with a single scanned AP
1059     """
1060     
1061     def __init__(self, data, range):
1062         """Initialize the scan result with the access point data"""
1063         self.iwstruct = Iwstruct()
1064         self.range = range
1065         self.bssid = "%02X:%02X:%02X:%02X:%02X:%02X" % struct_unpack('BBBBBB', data[2:8])
1066         self.essid = None
1067         self.mode = None
1068         self.rate = []
1069         self.quality = Iwquality() 
1070         self.frequency = None
1071         self.encode = None
1072         self.custom = []
1073         self.protocol = None
1074
1075     def addEvent(self, cmd, data):
1076         """Attempts to add the data from an event to a scanresult
1077            Only certain data is accept, in which case the result is True
1078            If the event data is invalid, None is returned
1079            If the data is valid but unused, False is returned
1080         """
1081         if cmd <= SIOCIWLAST:
1082             if cmd < SIOCIWFIRST:
1083                 return None
1084         elif cmd >= IWEVFIRST:
1085             if cmd > IWEVLAST:
1086                 return None
1087         else:
1088             return None
1089             
1090         if cmd == SIOCGIWESSID:
1091             self.essid = data[4:]
1092         elif cmd == SIOCGIWMODE:
1093             self.mode = modes[self.iwstruct.unpack('i', data[:4])[0]]
1094         elif cmd == SIOCGIWRATE:
1095             # TODO, deal with multiple rates, or at least the highest rate
1096             freqsize = struct_calcsize("ihbb")
1097             while len(data) >= freqsize:
1098                 iwfreq = Iwfreq(data)
1099                 self.rate.append(iwfreq.getBitrate())
1100                 data = data[freqsize:]
1101         elif cmd == IWEVQUAL:
1102             self.quality.parse(data)
1103         elif cmd == SIOCGIWFREQ:
1104             self.frequency = Iwfreq(data)
1105         elif cmd == SIOCGIWENCODE:
1106             self.encode = data
1107         elif cmd == IWEVCUSTOM:
1108             self.custom.append(data[1:])
1109         elif cmd == SIOCGIWNAME:
1110             self.protocol = data[:len(data)-2]
1111         else:
1112             #print "Cmd:", cmd
1113             return False
1114         return True