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