root/umpa/branches/protocols/umit/umpa/protocols/_protocols.py @ 5797

Revision 5797, 14.0 kB (checked in by kosma, 3 years ago)

fields.py, protocols.py: Added support for `active' flag on fields, making a simple framework for creating protocol headers with a dynamic structure that changes depending on the header content.

Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2008-2009 Adriano Monteiro Marques.
5#
6# Author: Bartosz SKOWRON <getxsick at gmail dot com>
7#
8# This library is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License as published
10# by the Free Software Foundation; either version 2.1 of the License, or
11# (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 MERCHANTABILITY
15# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16# License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with this library; if not, write to the Free Software Foundation,
20# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22"""
23Protocol - super-class for protocols implemenations.
24
25Every protocols have to be subclassed from the Protocol.
26Some methods have to be overridden. Read Protocol's docstrings for more
27information.
28"""
29
30from umit.umpa.protocols._consts import BYTE
31from umit.umpa.protocols._fields import Field, Flags
32from umit.umpa.utils.exceptions import UMPAException, UMPAAttributeException
33from umit.umpa.utils.tools import dict_from_sequence as _dict_from_sequence
34
35class Protocol(object):
36    """
37    Superclass for every protocol's implementations.
38   
39    You have to override following methods:
40     - _pre_raw()
41     - _post_raw()
42    They are used to make some tasks especially with SuperIntField objects.
43    Check IP.py, TCP.py for examples.
44
45    Also, override this __doc__ to get hints in some frontends
46    like the one provided by Umit Project.
47    """
48
49    _ordered_fields = ()
50    layer = None
51    protocol_id = None
52    name = None
53
54    def __init__(self, fields_list, **preset):
55        """
56        Create a new Protocol().
57
58        @type fields_list: C{list}
59        @param fields_list: list of fields B{in correct order}.
60
61        @param preset: predefined values for fields.
62        """
63
64        # pack objects of header's fields to the dict
65        fields = dict(zip(self._ordered_fields, fields_list))
66
67        # because of overwritten __setattr__ we need to assign with __dict__
68        self.__dict__['_fields'] = fields
69        self.__dict__['payload'] = None
70        self.__dict__['__raw_value'] = None
71
72        # setting up passed fields
73        for field in preset:
74            setattr(self, field, preset[field])
75
76        # short-fieldname update
77        for field in fields:
78            self.get_field(field)._shortname = field
79
80    def __getattr__(self, attr):
81        """
82        Return the value of the field.
83       
84        @type attr: C{str}
85        @param attr: name of the field.
86
87        @return: value of the field.
88        """
89       
90        return self.get_field(attr).get()
91
92    def __setattr__(self, attr, value):
93        """
94        Set value of the field.
95
96        @type attr: C{str}
97        @param attr: name of the field.
98
99        @param value: the new value.
100        """
101
102        self.get_field(attr).set(value)
103
104    def __str__(self):
105        """
106        Print in human-readable tree-style a content of the protocol.
107
108        Call print statement for fields.
109
110        @return: the part of the whole tree which accords to the protocol.
111        """
112
113        print "+-< %-27s >" % self.name
114        print "| \\"
115        for field in self.get_fields():
116            # print active fields only
117            if field.active:
118                print field
119        print "\\-< %-27s >\tcontains %d fields" % (self.name,
120                                                            len(self._fields))
121        return super(Protocol, self).__str__()
122
123    def _is_valid(self, field):
124        """
125        Validate if the filed is allowed.
126
127        @param field: requested field.
128
129        @rtype: C{bool}
130        @return: result of the validation.
131        """
132
133        return field in self._fields
134
135    @classmethod
136    def get_fields_keys(cls):
137        """
138        Yield the ordered sequence of the fields' names.
139
140        This is a generator for ordered names (keys) of header's fields.
141        """
142
143        for field in cls._ordered_fields:
144            yield field
145
146    def get_fields(self):
147        """
148        Yield the ordered sequence of the fields.
149
150        This is a generator for ordered fields.
151        """
152       
153        for field in self._ordered_fields:
154            yield self.get_field(field)
155   
156    def get_field(self, keyname):
157        """
158        Return the field.
159
160        @type keyname: C{str}
161        @param keyname: name of the field
162
163        @rtype: C{Field}
164        @return: requested field.
165        """
166
167        if self._is_valid(keyname):
168            return self._fields[keyname]
169        else:
170            raise UMPAAttributeException(keyname + ' is not allowed')
171
172    def set_fields(self, *args, **kwargs):
173        """
174        Set fields of the protocol.
175
176        There are 2 ways to do that with using tuple or dict-style.
177        For tuple, use sequence as: field_name1, value1, field_name2, value2.
178        For dict, use dict ;) field_name1=value1, field_name2=value2.
179
180        @param args: sequence of field_name and value.
181
182        @param kwargs: field_name=value.
183        """
184       
185        if len(args) % 2:
186            raise UMPAAttributeException('wrong amount of passed arguments')
187        # converting args list to the dict and update our kwargs
188        kwargs.update(_dict_from_sequence(args))
189
190        # first check if all keys are valid to avoid partial-changing
191        for key in kwargs:
192            if not self._is_valid(key):
193                raise UMPAAttributeException(key + ' is not allowed; '
194                                            'nothing has changed')
195
196        for key in kwargs:
197            setattr(self, key, kwargs[key])
198
199    def disable_fields(self, *args):
200        """
201        Disable the selected fields by setting the `active' flag to False.
202
203        @type args: list of C{str}
204        @param args: list of field names to disable
205        """
206
207        for name in args:
208            self.get_field(name).active = False
209
210    def enable_fields(self, *args):
211        """
212        Enable the selected fields by setting the `active' flag to True.
213
214        @type args: list of C{str}
215        @param args: list of field names to enable
216        """
217
218        for name in args:
219            self.get_field(name).active = True
220
221    def get_flags(self, name, *args):
222        """
223        Return flags of the field.
224
225        @type name: C{str}
226        @param name: name of the field.
227
228        @param args: names of flags.
229
230        @rtype: C{list}
231        @return: list of flags.
232        """
233
234        flag_field = self.get_field(name)
235        if not isinstance(flag_field, Flags):
236            raise UMPAAttributeException("No Flags instance for " + name)
237
238        if not args:
239            return flag_field.get([])
240        return flag_field.get(*args)
241
242    def set_flags(self, name, *args, **kwargs):
243        """
244        Set flags for flags-field.
245
246        There are 2 ways to do that with using tuple or dict-style.
247        For tuple, use sequence as: flag_name1, value1, flag_name2, value2.
248        For dict, use dict ;) flag_name1=value1, flag_name2=value2.
249
250        @type name: C{str}
251        @param name: name of the field.
252
253        @param args: sequence of field_name and value.
254
255        @param kwargs: field_name=value.
256        """
257
258        if len(args) % 2:
259            raise UMPAAttributeException('wrong amount of passed arguments')
260        # converting args list to the dict and update our kwargs
261        kwargs.update(_dict_from_sequence(args))
262
263        flag_field = self.get_field(name)
264        if isinstance(flag_field, Flags):
265            for flag_name in kwargs:
266                if kwargs[flag_name]:
267                    flag_field.set(flag_name)
268                else:
269                    flag_field.set(False, flag_name)
270        else:
271            raise UMPAAttributeException("No Flags instance for " + name)
272
273    def get_offset(self, field):
274        """
275        Return the offset for the field.
276
277        This offset is for the current protocol, not the packet.
278
279        @type field: C{str} or C{Field}
280        @param field: name of the field
281
282        @rtype: C{int}
283        @return: offset of the field in bits.
284        """
285
286        # checking if argument is a key or instance
287        if isinstance(field, str):
288            field_list = self._ordered_fields
289        elif isinstance(field, Field):
290            field_list = [ f for f in self.get_fields() ]
291        else:
292            raise UMPAException(str(type(field)) + ' unsupported')
293   
294        if field not in field_list:
295            raise UMPAAttributeException(repr(field) + ' is not allowed')
296
297        offset = 0
298        for i, val in enumerate(field_list):
299            if field == val:
300                break
301            # only active fields are included in the raw packet
302            if self.get_field(self._ordered_fields[i]).active:
303                offset += self.get_field(self._ordered_fields[i]).bits
304        return offset
305
306    def _pre_raw(self, raw_value, bit, protocol_container, protocol_bits):
307        """
308        Handle with fields before calling fillout() for them.
309
310        Some fields (especially SpecialIntField) need to handle with other
311        fields. Do it here. If they need to handle with other fields B{after}
312        the fillout() (e.g. to calculate a checksum), use _post_raw() instead.
313
314        I{Currently} means that if we're generating raw values for
315        the packet/protocol it's done by some loops. So, currently it's
316        the value in the current stage of that loops.
317
318        @type raw_value: C{int}
319        @param raw_value: currently raw value for the packet.
320
321        @type bit: C{int}
322        @param bit: currently length of the protocol.
323
324        @type protocol_container: C{tuple}
325        @param protocol_container: tuple of protocols included in the packet.
326
327        @type protocol_bits: C{int}
328        @param protocol_bits: currently length of the packet.
329
330        @return: C{raw_value, bit}
331        """
332       
333        raise NotImplementedError("this is abstract class")
334
335    def _raw(self, raw_value, bit, protocol_container, protocol_bits):
336        """
337        Call pre/post handling with other fields and merge results of fillout.
338
339        Call fillout() method for every fields from the protocol.
340        Handle with fields by pre/port methods.
341
342        @type raw_value: C{int}
343        @param raw_value: currently raw value for the packet.
344
345        @type bit: C{int}
346        @param bit: currently length of the protocol.
347
348        @type protocol_container: C{tuple}
349        @param protocol_container: tuple of protocols included in the packet.
350
351        @type protocol_bits: C{int}
352        @param protocol_bits: currently length of the packet.
353
354        @return: C{raw_value, bit}
355        """
356
357        # because of some protocols implementation, there are some tasks before
358        # we call fillout() for fields
359        raw_value, bit = self._pre_raw(raw_value, bit, protocol_container,
360                                                                protocol_bits)
361
362        # so we make a big number with bits of every active field of the protocol
363        for field in reversed(self._ordered_fields):
364            # only active fields are included in the raw packet
365            if self.get_field(field).active:
366                field_bits = self.get_field(field).fillout()
367                raw_value |= field_bits << bit
368                bit += self.get_field(field).bits
369
370        # because of some protocols implementation, there are some tasks after
371        # we call fillout() for fields
372        raw_value, bit = self._post_raw(raw_value, bit, protocol_container,
373                                                                protocol_bits)
374
375        return raw_value, bit
376
377    def _post_raw(self, raw_value, bit, protocol_container, protocol_bits):
378        """
379        Handle with fields after calling fillout() for them.
380
381        Some fields (especially SpecialIntField) need to handle with other
382        fields. First, think to do it in _pre_raw() method. But if they need
383        to handle with other fields B{after} the fillout() (e.g. to calculate
384        a checksum), do it here.
385
386        I{Currently} means that if we're generating raw values for
387        the packet/protocol it's done by some loops. So, currently it's
388        the value in the current stage of that loops.
389
390        @type raw_value: C{int}
391        @param raw_value: currently raw value for the packet.
392
393        @type bit: C{int}
394        @param bit: currently length of the protocol.
395
396        @type protocol_container: C{tuple}
397        @param protocol_container: tuple of protocols included in the packet.
398
399        @type protocol_bits: C{int}
400        @param protocol_bits: currently length of the packet.
401
402        @return: C{raw_value, bit}
403        """
404       
405        raise NotImplementedError("this is abstract class")
406
407    def get_raw(self, protocol_container, protocol_bits):
408        """
409        Return raw bits of the protocol.
410
411        @type protocol_container: C{tuple}
412        @param protocol_container: tuple of protocols included in the packet.
413
414        @type protocol_bits: C{int}
415        @param protocol_bits: currently length of the packet.
416
417        @return: C{raw_value, bit}
418        """
419
420        raw_value = 0
421        bit = 0
422        # The deal: we join all value's fields into one big number
423        # (with taking care about amount of bits).
424        # then we devide the number on byte-chunks
425        # and pack it by struct.pack() function
426        raw_value, bit = self._raw(raw_value, bit, protocol_container,
427                                                                protocol_bits)
428
429        # protocol should return byte-compatible length
430        if bit % BYTE != 0:
431            raise UMPAException('odd number of bits in ' + str(self.name))
432
433        self.__dict__['__raw_value'] = raw_value
434        return raw_value, bit
435
436    def load_raw(self, buffer):
437        """
438        Load raw and update a protocol's fields.
439
440        @return: raw payload
441        """
442
443        raise UMPAException("protocol doesn't support decoding "
444                            "or an abstract class.")
Note: See TracBrowser for help on using the browser.