~ubuntu-branches/ubuntu/wily/python-tx-tftp/wily-proposed

« back to all changes in this revision

Viewing changes to .pc/05_lp1317705.patch/tftp/datagram.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2015-06-22 12:33:26 UTC
  • Revision ID: package-import@ubuntu.com-20150622123326-86ibcov22pw02ed7
Tags: 0.1~bzr38-0ubuntu4
debian/patches/05_lp1317705.patch: Regonise error code 8, which is
used to terminate a transfer due to option negotiation.
See RFC 2347, "TFTP Option Extension". (LP: #1317705)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''
 
2
@author: shylent
 
3
'''
 
4
from itertools import chain
 
5
from tftp.errors import (WireProtocolError, InvalidOpcodeError,
 
6
    PayloadDecodeError, InvalidErrorcodeError, OptionsDecodeError)
 
7
from twisted.python.util import OrderedDict
 
8
import struct
 
9
 
 
10
OP_RRQ = 1
 
11
OP_WRQ = 2
 
12
OP_DATA = 3
 
13
OP_ACK = 4
 
14
OP_ERROR = 5
 
15
OP_OACK = 6
 
16
 
 
17
ERR_NOT_DEFINED = 0
 
18
ERR_FILE_NOT_FOUND = 1
 
19
ERR_ACCESS_VIOLATION = 2
 
20
ERR_DISK_FULL = 3
 
21
ERR_ILLEGAL_OP = 4
 
22
ERR_TID_UNKNOWN = 5
 
23
ERR_FILE_EXISTS = 6
 
24
ERR_NO_SUCH_USER = 7
 
25
 
 
26
errors = {
 
27
    ERR_NOT_DEFINED :       "",
 
28
    ERR_FILE_NOT_FOUND  :   "File not found",
 
29
    ERR_ACCESS_VIOLATION :  "Access violation",
 
30
    ERR_DISK_FULL :         "Disk full or allocation exceeded",
 
31
    ERR_ILLEGAL_OP :        "Illegal TFTP operation",
 
32
    ERR_TID_UNKNOWN :       "Unknown transfer ID",
 
33
    ERR_FILE_EXISTS :       "File already exists",
 
34
    ERR_NO_SUCH_USER :      "No such user"
 
35
 
 
36
}
 
37
 
 
38
def split_opcode(datagram):
 
39
    """Split the raw datagram into opcode and payload.
 
40
 
 
41
    @param datagram: raw datagram
 
42
    @type datagram: C{str}
 
43
 
 
44
    @return: a 2-tuple, the first item is the opcode and the second item is the payload
 
45
    @rtype: (C{int}, C{str})
 
46
 
 
47
    @raise WireProtocolError: if the opcode cannot be extracted
 
48
 
 
49
    """
 
50
 
 
51
    try:
 
52
        return struct.unpack("!H", datagram[:2])[0], datagram[2:]
 
53
    except struct.error:
 
54
        raise WireProtocolError("Failed to extract the opcode")
 
55
 
 
56
 
 
57
class TFTPDatagram(object):
 
58
    """Base class for datagrams
 
59
 
 
60
    @cvar opcode: The opcode, corresponding to this datagram
 
61
    @type opcode: C{int}
 
62
 
 
63
    """
 
64
 
 
65
    opcode = None
 
66
 
 
67
    @classmethod
 
68
    def from_wire(cls, payload):
 
69
        """Parse the payload and return a datagram object
 
70
 
 
71
        @param payload: Binary representation of the payload (without the opcode)
 
72
        @type payload: C{str}
 
73
 
 
74
        """
 
75
        raise NotImplementedError("Subclasses must override this")
 
76
 
 
77
    def to_wire(self):
 
78
        """Return the wire representation of the datagram.
 
79
 
 
80
        @rtype: C{str}
 
81
 
 
82
        """
 
83
        raise NotImplementedError("Subclasses must override this")
 
84
 
 
85
 
 
86
class RQDatagram(TFTPDatagram):
 
87
    """Base class for "RQ" (request) datagrams.
 
88
 
 
89
    @ivar filename: File name, that corresponds to this request.
 
90
    @type filename: C{str}
 
91
 
 
92
    @ivar mode: Transfer mode. Valid values are C{netascii} and C{octet}.
 
93
    Case-insensitive.
 
94
    @type mode: C{str}
 
95
 
 
96
    @ivar options: Any options, that were requested by the client (as per
 
97
    U{RFC2374<http://tools.ietf.org/html/rfc2347>}
 
98
    @type options: C{dict}
 
99
 
 
100
    """
 
101
 
 
102
    @classmethod
 
103
    def from_wire(cls, payload):
 
104
        """Parse the payload and return a RRQ/WRQ datagram object.
 
105
 
 
106
        @return: datagram object
 
107
        @rtype: L{RRQDatagram} or L{WRQDatagram}
 
108
 
 
109
        @raise OptionsDecodeError: if we failed to decode the options, requested
 
110
        by the client
 
111
        @raise PayloadDecodeError: if there were not enough fields in the payload.
 
112
        Fields are terminated by NUL.
 
113
 
 
114
        """
 
115
        parts = payload.split('\x00')
 
116
        try:
 
117
            filename, mode = parts.pop(0), parts.pop(0)
 
118
        except IndexError:
 
119
            raise PayloadDecodeError("Not enough fields in the payload")
 
120
        if parts and not parts[-1]:
 
121
            parts.pop(-1)
 
122
        options = OrderedDict()
 
123
        # To maintain consistency during testing.
 
124
        # The actual order of options is not important as per RFC2347
 
125
        if len(parts) % 2:
 
126
            raise OptionsDecodeError("No value for option %s" % parts[-1])
 
127
        for ind, opt_name in enumerate(parts[::2]):
 
128
            if opt_name in options:
 
129
                raise OptionsDecodeError("Duplicate option specified: %s" % opt_name)
 
130
            options[opt_name] = parts[ind * 2 + 1]
 
131
        return cls(filename, mode, options)
 
132
 
 
133
    def __init__(self, filename, mode, options):
 
134
        self.filename = filename
 
135
        self.mode = mode.lower()
 
136
        self.options = options
 
137
 
 
138
    def __repr__(self):
 
139
        if self.options:
 
140
            return ("<%s(filename=%s, mode=%s, options=%s)>" %
 
141
                    (self.__class__.__name__, self.filename, self.mode, self.options))
 
142
        return "<%s(filename=%s, mode=%s)>" % (self.__class__.__name__,
 
143
                                               self.filename, self.mode)
 
144
 
 
145
    def to_wire(self):
 
146
        opcode = struct.pack("!H", self.opcode)
 
147
        if self.options:
 
148
            options = '\x00'.join(chain.from_iterable(self.options.iteritems()))
 
149
            return ''.join((opcode, self.filename, '\x00', self.mode, '\x00',
 
150
                            options, '\x00'))
 
151
        else:
 
152
            return ''.join((opcode, self.filename, '\x00', self.mode, '\x00'))
 
153
 
 
154
class RRQDatagram(RQDatagram):
 
155
    opcode = OP_RRQ
 
156
 
 
157
class WRQDatagram(RQDatagram):
 
158
    opcode = OP_WRQ
 
159
 
 
160
class OACKDatagram(TFTPDatagram):
 
161
    """An OACK datagram
 
162
 
 
163
    @ivar options: Any options, that were requested by the client (as per
 
164
    U{RFC2374<http://tools.ietf.org/html/rfc2347>}
 
165
    @type options: C{dict}
 
166
 
 
167
    """
 
168
    opcode = OP_OACK
 
169
 
 
170
    @classmethod
 
171
    def from_wire(cls, payload):
 
172
        """Parse the payload and return an OACK datagram object.
 
173
 
 
174
        @return: datagram object
 
175
        @rtype: L{OACKDatagram}
 
176
 
 
177
        @raise OptionsDecodeError: if we failed to decode the options
 
178
 
 
179
        """
 
180
        parts = payload.split('\x00')
 
181
        #FIXME: Boo, code duplication
 
182
        if parts and not parts[-1]:
 
183
            parts.pop(-1)
 
184
        options = OrderedDict()
 
185
        if len(parts) % 2:
 
186
            raise OptionsDecodeError("No value for option %s" % parts[-1])
 
187
        for ind, opt_name in enumerate(parts[::2]):
 
188
            if opt_name in options:
 
189
                raise OptionsDecodeError("Duplicate option specified: %s" % opt_name)
 
190
            options[opt_name] = parts[ind * 2 + 1]
 
191
        return cls(options)
 
192
 
 
193
    def __init__(self, options):
 
194
        self.options = options
 
195
 
 
196
    def __repr__(self):
 
197
        return ("<%s(options=%s)>" % (self.__class__.__name__, self.options))
 
198
 
 
199
    def to_wire(self):
 
200
        opcode = struct.pack("!H", self.opcode)
 
201
        if self.options:
 
202
            options = '\x00'.join(chain.from_iterable(self.options.iteritems()))
 
203
            return ''.join((opcode, options, '\x00'))
 
204
        else:
 
205
            return opcode
 
206
 
 
207
class DATADatagram(TFTPDatagram):
 
208
    """A DATA datagram
 
209
 
 
210
    @ivar blocknum: A block number, that this chunk of data is associated with
 
211
    @type blocknum: C{int}
 
212
 
 
213
    @ivar data: binary data
 
214
    @type data: C{str}
 
215
 
 
216
    """
 
217
    opcode = OP_DATA
 
218
 
 
219
    @classmethod
 
220
    def from_wire(cls, payload):
 
221
        """Parse the payload and return a L{DATADatagram} object.
 
222
 
 
223
        @param payload: Binary representation of the payload (without the opcode)
 
224
        @type payload: C{str}
 
225
 
 
226
        @return: A L{DATADatagram} object
 
227
        @rtype: L{DATADatagram}
 
228
 
 
229
        @raise PayloadDecodeError: if the format of payload is incorrect
 
230
 
 
231
        """
 
232
        try:
 
233
            blocknum, data = struct.unpack('!H', payload[:2])[0], payload[2:]
 
234
        except struct.error:
 
235
            raise PayloadDecodeError()
 
236
        return cls(blocknum, data)
 
237
 
 
238
    def __init__(self, blocknum, data):
 
239
        self.blocknum = blocknum
 
240
        self.data = data
 
241
 
 
242
    def __repr__(self):
 
243
        return "<%s(blocknum=%s, %s bytes of data)>" % (self.__class__.__name__,
 
244
                                                        self.blocknum, len(self.data))
 
245
 
 
246
    def to_wire(self):
 
247
        return ''.join((struct.pack('!HH', self.opcode, self.blocknum), self.data))
 
248
 
 
249
class ACKDatagram(TFTPDatagram):
 
250
    """An ACK datagram.
 
251
 
 
252
    @ivar blocknum: Block number of the data chunk, which this datagram is supposed to acknowledge
 
253
    @type blocknum: C{int}
 
254
 
 
255
    """
 
256
    opcode = OP_ACK
 
257
 
 
258
    @classmethod
 
259
    def from_wire(cls, payload):
 
260
        """Parse the payload and return a L{ACKDatagram} object.
 
261
 
 
262
        @param payload: Binary representation of the payload (without the opcode)
 
263
        @type payload: C{str}
 
264
 
 
265
        @return: An L{ACKDatagram} object
 
266
        @rtype: L{ACKDatagram}
 
267
 
 
268
        @raise PayloadDecodeError: if the format of payload is incorrect
 
269
 
 
270
        """
 
271
        try:
 
272
            blocknum = struct.unpack('!H', payload)[0]
 
273
        except struct.error:
 
274
            raise PayloadDecodeError("Unable to extract the block number")
 
275
        return cls(blocknum)
 
276
 
 
277
    def __init__(self, blocknum):
 
278
        self.blocknum = blocknum
 
279
 
 
280
    def __repr__(self):
 
281
        return "<%s(blocknum=%s)>" % (self.__class__.__name__, self.blocknum)
 
282
 
 
283
    def to_wire(self):
 
284
        return struct.pack('!HH', self.opcode, self.blocknum)
 
285
 
 
286
class ERRORDatagram(TFTPDatagram):
 
287
    """An ERROR datagram.
 
288
 
 
289
    @ivar errorcode: A valid TFTP error code
 
290
    @type errorcode: C{int}
 
291
 
 
292
    @ivar errmsg: An error message, describing the error condition in which this
 
293
    datagram was produced
 
294
    @type errmsg: C{str}
 
295
 
 
296
    """
 
297
    opcode = OP_ERROR
 
298
 
 
299
    @classmethod
 
300
    def from_wire(cls, payload):
 
301
        """Parse the payload and return a L{ERRORDatagram} object.
 
302
 
 
303
        This method violates the standard a bit - if the error string was not
 
304
        extracted, a default error string is generated, based on the error code.
 
305
 
 
306
        @param payload: Binary representation of the payload (without the opcode)
 
307
        @type payload: C{str}
 
308
 
 
309
        @return: An L{ERRORDatagram} object
 
310
        @rtype: L{ERRORDatagram}
 
311
 
 
312
        @raise PayloadDecodeError: if the format of payload is incorrect
 
313
        @raise InvalidErrorcodeError: a more specific exception, that is raised
 
314
        if the error code was successfully, extracted, but it does not correspond
 
315
        to any known/standartized error code values.
 
316
 
 
317
        """
 
318
        try:
 
319
            errorcode = struct.unpack('!H', payload[:2])[0]
 
320
        except struct.error:
 
321
            raise PayloadDecodeError("Unable to extract the error code")
 
322
        if not errorcode in errors:
 
323
            raise InvalidErrorcodeError(errorcode)
 
324
        errmsg = payload[2:].split('\x00')[0]
 
325
        if not errmsg:
 
326
            errmsg = errors[errorcode]
 
327
        return cls(errorcode, errmsg)
 
328
 
 
329
    @classmethod
 
330
    def from_code(cls, errorcode, errmsg=None):
 
331
        """Create an L{ERRORDatagram}, given an error code and, optionally, an
 
332
        error message to go with it. If not provided, default error message for
 
333
        the given error code is used.
 
334
 
 
335
        @param errorcode: An error code (one of L{errors})
 
336
        @type errorcode: C{int}
 
337
 
 
338
        @param errmsg: An error message (optional)
 
339
        @type errmsg: C{str} or C{NoneType}
 
340
 
 
341
        @raise InvalidErrorcodeError: if the error code is not known
 
342
 
 
343
        @return: an L{ERRORDatagram}
 
344
        @rtype: L{ERRORDatagram}
 
345
 
 
346
        """
 
347
        if not errorcode in errors:
 
348
            raise InvalidErrorcodeError(errorcode)
 
349
        if errmsg is None:
 
350
            errmsg = errors[errorcode]
 
351
        return cls(errorcode, errmsg)
 
352
 
 
353
 
 
354
    def __init__(self, errorcode, errmsg):
 
355
        self.errorcode = errorcode
 
356
        self.errmsg = errmsg
 
357
 
 
358
    def to_wire(self):
 
359
        return ''.join((struct.pack('!HH', self.opcode, self.errorcode),
 
360
                        self.errmsg, '\x00'))
 
361
 
 
362
class _TFTPDatagramFactory(object):
 
363
    """Encapsulates the creation of datagrams based on the opcode"""
 
364
    _dgram_classes = {
 
365
        OP_RRQ: RRQDatagram,
 
366
        OP_WRQ: WRQDatagram,
 
367
        OP_DATA: DATADatagram,
 
368
        OP_ACK: ACKDatagram,
 
369
        OP_ERROR: ERRORDatagram,
 
370
        OP_OACK: OACKDatagram
 
371
    }
 
372
 
 
373
    def __call__(self, opcode, payload):
 
374
        """Create a datagram, given an opcode and payload.
 
375
 
 
376
        Errors, that occur during datagram creation are propagated as-is.
 
377
 
 
378
        @param opcode: opcode
 
379
        @type opcode: C{int}
 
380
 
 
381
        @param payload: payload
 
382
        @type payload: C{str}
 
383
 
 
384
        @return: datagram object
 
385
        @rtype: L{TFTPDatagram}
 
386
 
 
387
        @raise InvalidOpcodeError: if the opcode is not recognized
 
388
 
 
389
        """
 
390
        try:
 
391
            datagram_class = self._dgram_classes[opcode]
 
392
        except KeyError:
 
393
            raise InvalidOpcodeError(opcode)
 
394
        return datagram_class.from_wire(payload)
 
395
 
 
396
TFTPDatagramFactory = _TFTPDatagramFactory()