| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | # Copyright (C) 2008 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 | """ |
|---|
| 23 | Packets management. |
|---|
| 24 | |
|---|
| 25 | Packet class is a protocol container. |
|---|
| 26 | Use it to build a packet which contains several protocols. |
|---|
| 27 | """ |
|---|
| 28 | |
|---|
| 29 | import struct |
|---|
| 30 | import warnings |
|---|
| 31 | |
|---|
| 32 | import umpa.utils.bits |
|---|
| 33 | from umpa.utils.exceptions import UMPAException, UMPAStrictException |
|---|
| 34 | |
|---|
| 35 | BYTE = 8 |
|---|
| 36 | |
|---|
| 37 | class StrictWarning(Warning): |
|---|
| 38 | """ |
|---|
| 39 | New category of warning. |
|---|
| 40 | |
|---|
| 41 | It's being used for warning message with reference |
|---|
| 42 | to strict attribute of Packet's objects. |
|---|
| 43 | """ |
|---|
| 44 | |
|---|
| 45 | pass |
|---|
| 46 | |
|---|
| 47 | def _strict_warn(layer_a, layer_b): |
|---|
| 48 | """ |
|---|
| 49 | Issue the warning with prepared message as a StrictWarning category. |
|---|
| 50 | |
|---|
| 51 | @type layer_a: C{int} |
|---|
| 52 | @param layer_a: layer for first protocol. |
|---|
| 53 | |
|---|
| 54 | @type layer_b: C{int} |
|---|
| 55 | @param layer_b: layer for second protocol. |
|---|
| 56 | """ |
|---|
| 57 | |
|---|
| 58 | msg = "bad protocols ordering. first layer %d, second %d." % (layer_a, |
|---|
| 59 | layer_b) |
|---|
| 60 | warnings.warn(msg, StrictWarning, stacklevel=3) |
|---|
| 61 | |
|---|
| 62 | class Packet(object): |
|---|
| 63 | """ |
|---|
| 64 | This is a protocol container. |
|---|
| 65 | |
|---|
| 66 | Use this to build a completely packets. |
|---|
| 67 | An instance of the class should contains protocols which you want to send. |
|---|
| 68 | """ |
|---|
| 69 | |
|---|
| 70 | def __init__(self, *protos, **options): |
|---|
| 71 | """ |
|---|
| 72 | Create a new Packet(). |
|---|
| 73 | |
|---|
| 74 | @type protos: Optional C{Protocol} |
|---|
| 75 | @param protos: protocols which will be included into the object. |
|---|
| 76 | |
|---|
| 77 | @type options: Optional C{bool} |
|---|
| 78 | @param options: 2 keys are proper: |
|---|
| 79 | - strict (default: True): if True object will keep protocols order. |
|---|
| 80 | It avoids to build odd packets with with unsaved layer order |
|---|
| 81 | - warn (default: True): if True and strict is True, object will |
|---|
| 82 | issue warnings. Otherwise warnings are ignored. |
|---|
| 83 | """ |
|---|
| 84 | |
|---|
| 85 | # parsing options dictionary |
|---|
| 86 | available_options = { 'strict' : True, 'warn' : True } |
|---|
| 87 | for opt in available_options: |
|---|
| 88 | if opt in options: |
|---|
| 89 | setattr(self, opt, options[opt]) |
|---|
| 90 | options.pop(opt) |
|---|
| 91 | else: |
|---|
| 92 | setattr(self, opt, available_options[opt]) |
|---|
| 93 | if len(options) != 0: |
|---|
| 94 | raise UMPAException("Undefined options " + str(options.keys())) |
|---|
| 95 | |
|---|
| 96 | self.protos = [] |
|---|
| 97 | self._add_new_protocols(protos) |
|---|
| 98 | self.raw = None |
|---|
| 99 | self.bits = 0 |
|---|
| 100 | |
|---|
| 101 | def __str__(self): |
|---|
| 102 | """ |
|---|
| 103 | Print in human-readable tree-style a content of the packet. |
|---|
| 104 | |
|---|
| 105 | Call print statement for protocols. |
|---|
| 106 | |
|---|
| 107 | @return: call __str__ method of super-class. |
|---|
| 108 | """ |
|---|
| 109 | |
|---|
| 110 | print "Packet contains %d protocols" % len(self.protos) |
|---|
| 111 | for proto in self.protos: |
|---|
| 112 | print proto |
|---|
| 113 | return super(Packet, self).__str__() |
|---|
| 114 | |
|---|
| 115 | def include(self, *protos): |
|---|
| 116 | """ |
|---|
| 117 | Add protocols into packet. |
|---|
| 118 | |
|---|
| 119 | @type protos: C{Protocol} |
|---|
| 120 | @param protos: protocols which will be included into the packet. |
|---|
| 121 | """ |
|---|
| 122 | |
|---|
| 123 | self._add_new_protocols(protos) |
|---|
| 124 | |
|---|
| 125 | def _add_new_protocols(self, protos): |
|---|
| 126 | """ |
|---|
| 127 | Add protocols into packet. |
|---|
| 128 | |
|---|
| 129 | Check the strict attribute and |
|---|
| 130 | raise UMPAStrictException or issues warnings if needed. |
|---|
| 131 | |
|---|
| 132 | @param protos: protocols which will be included into the packet. |
|---|
| 133 | """ |
|---|
| 134 | |
|---|
| 135 | for p in protos: |
|---|
| 136 | if len(self.protos) > 0: |
|---|
| 137 | last_proto = self.protos[-1] |
|---|
| 138 | # FIXME: should we allow only the distance no less and |
|---|
| 139 | # no more than 1 between layers? |
|---|
| 140 | if p.layer - last_proto.layer != 1: |
|---|
| 141 | if self.strict: |
|---|
| 142 | raise UMPAStrictException("bad protocols ordering." |
|---|
| 143 | "first layer %d, second %d." |
|---|
| 144 | % (last_proto.layer, p.layer)) |
|---|
| 145 | else: |
|---|
| 146 | _strict_warn(last_proto.layer, p.layer) |
|---|
| 147 | last_proto.__dict__['payload'] = p |
|---|
| 148 | self.protos.append(p) |
|---|
| 149 | |
|---|
| 150 | def _get_raw(self): |
|---|
| 151 | """ |
|---|
| 152 | Return raw packet in the bit-mode (big-endian). |
|---|
| 153 | |
|---|
| 154 | Call every protocols to get the raw bits of them, |
|---|
| 155 | collect the results and return the raw packet. |
|---|
| 156 | |
|---|
| 157 | @return: Struct packed bits of every protocols in big-endian order. |
|---|
| 158 | """ |
|---|
| 159 | |
|---|
| 160 | self.raw = 0 |
|---|
| 161 | self.bits = 0 |
|---|
| 162 | proto_id = 0 |
|---|
| 163 | for proto in reversed(self.protos): |
|---|
| 164 | # unfortunately we must pass list of protocols to every protocol |
|---|
| 165 | # because some fields handle with other protocols, so they need |
|---|
| 166 | # an access to them |
|---|
| 167 | raw_proto, bit_proto = proto._get_raw(tuple(self.protos), |
|---|
| 168 | self.bits) |
|---|
| 169 | self.raw |= raw_proto << self.bits |
|---|
| 170 | self.bits += bit_proto |
|---|
| 171 | # split into chunks |
|---|
| 172 | # we make it because we need string for socket object |
|---|
| 173 | # so after that we pack it by struct module.pack() |
|---|
| 174 | byte_chunks = umpa.utils.bits.split_number_into_chunks(self.raw) |
|---|
| 175 | return struct.pack('!' + 'B'*(self.bits/BYTE), *byte_chunks) |
|---|
| 176 | |
|---|
| 177 | def _getwarn(self): |
|---|
| 178 | """ |
|---|
| 179 | Return warn attribute. |
|---|
| 180 | |
|---|
| 181 | Warn attribute set a behaviour of strict attribute. |
|---|
| 182 | If warn is True and strict is True, object will |
|---|
| 183 | issue warnings if needed. Otherwise warnings are ignored. |
|---|
| 184 | |
|---|
| 185 | @returns: warn attribute. |
|---|
| 186 | """ |
|---|
| 187 | return self._warn |
|---|
| 188 | |
|---|
| 189 | def _setwarn(self, value): |
|---|
| 190 | """ |
|---|
| 191 | Set warn attribute |
|---|
| 192 | |
|---|
| 193 | Warn attribute set a behaviour of strict attribute. |
|---|
| 194 | If warn is True and strict is True, object will |
|---|
| 195 | issue warnings if needed. Otherwise warnings are ignored. |
|---|
| 196 | |
|---|
| 197 | @type value: C{bool} |
|---|
| 198 | @param value: bool value. |
|---|
| 199 | """ |
|---|
| 200 | |
|---|
| 201 | self._warn = value |
|---|
| 202 | if self._warn: |
|---|
| 203 | warnings.simplefilter('always', StrictWarning) |
|---|
| 204 | else: |
|---|
| 205 | warnings.simplefilter('ignore', StrictWarning) |
|---|
| 206 | |
|---|
| 207 | warn = property(_getwarn, _setwarn, doc=""" |
|---|
| 208 | Control if issue warnings or ignore them |
|---|
| 209 | @type: C{bool} |
|---|
| 210 | """) |
|---|