~ubuntu-branches/ubuntu/raring/python-tx-tftp/raring

« back to all changes in this revision

Viewing changes to .pc/logmsg-ftbfs.patch/tftp/bootstrap.py

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2012-10-09 16:42:41 UTC
  • Revision ID: package-import@ubuntu.com-20121009164241-07k003mmq90m0xhn
Tags: 0.1~bzr31-0ubuntu6
d/p/logmsg-ftbfs.patch: Added an extra log.msg() bug fix for bad string
formatting, annotated patch with upstream bug report (LP: #1062031). 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''
 
2
@author: shylent
 
3
'''
 
4
from tftp.datagram import (ACKDatagram, ERRORDatagram, ERR_TID_UNKNOWN,
 
5
    TFTPDatagramFactory, split_opcode, OP_OACK, OP_ERROR, OACKDatagram, OP_ACK,
 
6
    OP_DATA)
 
7
from tftp.session import WriteSession, MAX_BLOCK_SIZE, ReadSession
 
8
from tftp.util import SequentialCall
 
9
from twisted.internet import reactor
 
10
from twisted.internet.protocol import DatagramProtocol
 
11
from twisted.python import log
 
12
from twisted.python.util import OrderedDict
 
13
 
 
14
class TFTPBootstrap(DatagramProtocol):
 
15
    """Base class for TFTP Bootstrap classes, classes, that handle initial datagram
 
16
    exchange (option negotiation, etc), before the actual transfer is started.
 
17
 
 
18
    Why OrderedDict and not the regular one? As per
 
19
    U{RFC2347<http://tools.ietf.org/html/rfc2347>}, the order of options is, indeed,
 
20
    not important, but having them in an arbitrary order would complicate testing.
 
21
 
 
22
    @cvar supported_options: lists options, that we know how to handle
 
23
 
 
24
    @ivar session: A L{WriteSession} or L{ReadSession} object, that will handle
 
25
    the actual tranfer, after the initial handshake and option negotiation is
 
26
    complete
 
27
    @type session: L{WriteSession} or L{ReadSession}
 
28
 
 
29
    @ivar options: a mapping of options, that this protocol instance was
 
30
    initialized with. If it is empty and we are the server, usual logic (that
 
31
    doesn't involve OACK datagrams) is used.
 
32
    Default: L{OrderedDict<twisted.python.util.OrderedDict>}.
 
33
    @type options: L{OrderedDict<twisted.python.util.OrderedDict>}
 
34
 
 
35
    @ivar resultant_options: stores the last options mapping value, that was passed
 
36
    from the remote peer
 
37
    @type resultant_options: L{OrderedDict<twisted.python.util.OrderedDict>}
 
38
 
 
39
    @ivar remote: remote peer address
 
40
    @type remote: C{(str, int)}
 
41
 
 
42
    @ivar timeout_watchdog: an object, that is responsible for timing the protocol
 
43
    out. If we are initiating the transfer, it is provided by the parent protocol
 
44
 
 
45
    @ivar backend: L{IReader} or L{IWriter} provider, that is used for this transfer
 
46
    @type backend: L{IReader} or L{IWriter} provider
 
47
 
 
48
    """
 
49
    supported_options = ('blksize', 'timeout', 'tsize')
 
50
 
 
51
    def __init__(self, remote, backend, options=None, _clock=None):
 
52
        if options is None:
 
53
            self.options = OrderedDict()
 
54
        else:
 
55
            self.options = options
 
56
        self.resultant_options = OrderedDict()
 
57
        self.remote = remote
 
58
        self.timeout_watchdog = None
 
59
        self.backend = backend
 
60
        if _clock is not None:
 
61
            self._clock = _clock
 
62
        else:
 
63
            self._clock = reactor
 
64
 
 
65
    def processOptions(self, options):
 
66
        """Process options mapping, discarding malformed or unknown options.
 
67
 
 
68
        @param options: options mapping to process
 
69
        @type options: L{OrderedDict<twisted.python.util.OrderedDict>}
 
70
 
 
71
        @return: a mapping of processed options. Invalid options are discarded.
 
72
        Whether or not the values of options may be changed is decided on a per-
 
73
        option basis, according to the standard
 
74
        @rtype L{OrderedDict<twisted.python.util.OrderedDict>}
 
75
 
 
76
        """
 
77
        accepted_options = OrderedDict()
 
78
        for name, val in options.iteritems():
 
79
            norm_name = name.lower()
 
80
            if norm_name in self.supported_options:
 
81
                actual_value = getattr(self, 'option_' + norm_name)(val)
 
82
                if actual_value is not None:
 
83
                    accepted_options[name] = actual_value
 
84
        return accepted_options
 
85
 
 
86
    def option_blksize(self, val):
 
87
        """Process the block size option. Valid range is between 8 and 65464,
 
88
        inclusive. If the value is more, than L{MAX_BLOCK_SIZE}, L{MAX_BLOCK_SIZE}
 
89
        is returned instead.
 
90
 
 
91
        @param val: value of the option
 
92
        @type val: C{str}
 
93
 
 
94
        @return: accepted option value or C{None}, if it is invalid
 
95
        @rtype: C{str} or C{None}
 
96
 
 
97
        """
 
98
        try:
 
99
            int_blksize = int(val)
 
100
        except ValueError:
 
101
            return None
 
102
        if int_blksize < 8 or int_blksize > 65464:
 
103
            return None
 
104
        int_blksize = min((int_blksize, MAX_BLOCK_SIZE))
 
105
        return str(int_blksize)
 
106
 
 
107
    def option_timeout(self, val):
 
108
        """Process timeout interval option
 
109
        (U{RFC2349<http://tools.ietf.org/html/rfc2349>}). Valid range is between 1
 
110
        and 255, inclusive.
 
111
 
 
112
        @param val: value of the option
 
113
        @type val: C{str}
 
114
 
 
115
        @return: accepted option value or C{None}, if it is invalid
 
116
        @rtype: C{str} or C{None}
 
117
 
 
118
        """
 
119
        try:
 
120
            int_timeout = int(val)
 
121
        except ValueError:
 
122
            return None
 
123
        if int_timeout < 1 or int_timeout > 255:
 
124
            return None
 
125
        return str(int_timeout)
 
126
 
 
127
    def option_tsize(self, val):
 
128
        """Process tsize interval option
 
129
        (U{RFC2349<http://tools.ietf.org/html/rfc2349>}). Valid range is 0 and up.
 
130
 
 
131
        @param val: value of the option
 
132
        @type val: C{str}
 
133
 
 
134
        @return: accepted option value or C{None}, if it is invalid
 
135
        @rtype: C{str} or C{None}
 
136
 
 
137
        """
 
138
        try:
 
139
            int_tsize = int(val)
 
140
        except ValueError:
 
141
            return None
 
142
        if int_tsize < 0:
 
143
            return None
 
144
        return str(int_tsize)
 
145
 
 
146
    def applyOptions(self, session, options):
 
147
        """Apply given options mapping to the given L{WriteSession} or
 
148
        L{ReadSession} object.
 
149
 
 
150
        @param session: A session object to apply the options to
 
151
        @type session: L{WriteSession} or L{ReadSession}
 
152
 
 
153
        @param options: Options to apply to the session object
 
154
        @type options: L{OrderedDict<twisted.python.util.OrderedDict>}
 
155
 
 
156
        """
 
157
        for opt_name, opt_val in options.iteritems():
 
158
            if opt_name == 'blksize':
 
159
                session.block_size = int(opt_val)
 
160
            elif opt_name == 'timeout':
 
161
                timeout = int(opt_val)
 
162
                session.timeout = (timeout,) * 3
 
163
            elif opt_name == 'tsize':
 
164
                tsize = int(opt_val)
 
165
                session.tsize = tsize
 
166
 
 
167
    def datagramReceived(self, datagram, addr):
 
168
        if self.remote[1] != addr[1]:
 
169
            self.transport.write(ERRORDatagram.from_code(ERR_TID_UNKNOWN).to_wire())
 
170
            return# Does not belong to this transfer
 
171
        datagram = TFTPDatagramFactory(*split_opcode(datagram))
 
172
        if datagram.opcode == OP_ERROR:
 
173
            return self.tftp_ERROR(datagram)
 
174
        return self._datagramReceived(datagram)
 
175
 
 
176
    def tftp_ERROR(self, datagram):
 
177
        """Handle the L{ERRORDatagram}.
 
178
 
 
179
        @param datagram: An ERROR datagram
 
180
        @type datagram: L{ERRORDatagram}
 
181
 
 
182
        """
 
183
        log.msg("Got error: " % datagram)
 
184
        return self.cancel()
 
185
 
 
186
    def cancel(self):
 
187
        """Terminate this protocol instance. If the underlying
 
188
        L{ReadSession}/L{WriteSession} is running, delegate the call to it.
 
189
 
 
190
        """
 
191
        if self.timeout_watchdog is not None and self.timeout_watchdog.active():
 
192
            self.timeout_watchdog.cancel()
 
193
        if self.session.started:
 
194
            self.session.cancel()
 
195
        else:
 
196
            self.backend.finish()
 
197
            self.transport.stopListening()
 
198
 
 
199
    def timedOut(self):
 
200
        """This protocol instance has timed out during the initial handshake."""
 
201
        log.msg("Timed during option negotiation process")
 
202
        self.cancel()
 
203
 
 
204
 
 
205
class LocalOriginWriteSession(TFTPBootstrap):
 
206
    """Bootstraps a L{WriteSession}, that was initiated locally, - we've requested
 
207
    a read from a remote server
 
208
 
 
209
    """
 
210
    def __init__(self, remote, writer, options=None, _clock=None):
 
211
        TFTPBootstrap.__init__(self, remote, writer, options, _clock)
 
212
        self.session = WriteSession(writer, self._clock)
 
213
 
 
214
    def startProtocol(self):
 
215
        """Connect the transport and start the L{timeout_watchdog}"""
 
216
        self.transport.connect(*self.remote)
 
217
        if self.timeout_watchdog is not None:
 
218
            self.timeout_watchdog.start()
 
219
 
 
220
    def tftp_OACK(self, datagram):
 
221
        """Handle the OACK datagram
 
222
 
 
223
        @param datagram: OACK datagram
 
224
        @type datagram: L{OACKDatagram}
 
225
 
 
226
        """
 
227
        if not self.session.started:
 
228
            self.resultant_options = self.processOptions(datagram.options)
 
229
            if self.timeout_watchdog.active():
 
230
                self.timeout_watchdog.cancel()
 
231
            return self.transport.write(ACKDatagram(0).to_wire())
 
232
        else:
 
233
            log.msg("Duplicate OACK received, send back ACK and ignore")
 
234
            self.transport.write(ACKDatagram(0).to_wire())
 
235
 
 
236
    def _datagramReceived(self, datagram):
 
237
        if datagram.opcode == OP_OACK:
 
238
            return self.tftp_OACK(datagram)
 
239
        elif datagram.opcode == OP_DATA and datagram.blocknum == 1:
 
240
            if self.timeout_watchdog is not None and self.timeout_watchdog.active():
 
241
                self.timeout_watchdog.cancel()
 
242
            if not self.session.started:
 
243
                self.applyOptions(self.session, self.resultant_options)
 
244
                self.session.transport = self.transport
 
245
                self.session.startProtocol()
 
246
            return self.session.datagramReceived(datagram)
 
247
        elif self.session.started:
 
248
            return self.session.datagramReceived(datagram)
 
249
 
 
250
 
 
251
class RemoteOriginWriteSession(TFTPBootstrap):
 
252
    """Bootstraps a L{WriteSession}, that was originated remotely, - we've
 
253
    received a WRQ from a client.
 
254
 
 
255
    """
 
256
    timeout = (1, 3, 7)
 
257
 
 
258
    def __init__(self, remote, writer, options=None, _clock=None):
 
259
        TFTPBootstrap.__init__(self, remote, writer, options, _clock)
 
260
        self.session = WriteSession(writer, self._clock)
 
261
 
 
262
    def startProtocol(self):
 
263
        """Connect the transport, respond with an initial ACK or OACK (depending on
 
264
        if we were initialized with options or not).
 
265
 
 
266
        """
 
267
        self.transport.connect(*self.remote)
 
268
        if self.options:
 
269
            self.resultant_options = self.processOptions(self.options)
 
270
            bytes = OACKDatagram(self.resultant_options).to_wire()
 
271
        else:
 
272
            bytes = ACKDatagram(0).to_wire()
 
273
        self.timeout_watchdog = SequentialCall.run(
 
274
            self.timeout[:-1],
 
275
            callable=self.transport.write, callable_args=[bytes, ],
 
276
            on_timeout=lambda: self._clock.callLater(self.timeout[-1], self.timedOut),
 
277
            run_now=True,
 
278
            _clock=self._clock
 
279
        )
 
280
 
 
281
    def _datagramReceived(self, datagram):
 
282
        if datagram.opcode == OP_DATA and datagram.blocknum == 1:
 
283
            if self.timeout_watchdog.active():
 
284
                self.timeout_watchdog.cancel()
 
285
            if not self.session.started:
 
286
                self.applyOptions(self.session, self.resultant_options)
 
287
                self.session.transport = self.transport
 
288
                self.session.startProtocol()
 
289
            return self.session.datagramReceived(datagram)
 
290
        elif self.session.started:
 
291
            return self.session.datagramReceived(datagram)
 
292
 
 
293
 
 
294
class LocalOriginReadSession(TFTPBootstrap):
 
295
    """Bootstraps a L{ReadSession}, that was originated locally, - we've requested
 
296
    a write to a remote server.
 
297
 
 
298
    """
 
299
    def __init__(self, remote, reader, options=None, _clock=None):
 
300
        TFTPBootstrap.__init__(self, remote, reader, options, _clock)
 
301
        self.session = ReadSession(reader, self._clock)
 
302
 
 
303
    def startProtocol(self):
 
304
        """Connect the transport and start the L{timeout_watchdog}"""
 
305
        self.transport.connect(*self.remote)
 
306
        if self.timeout_watchdog is not None:
 
307
            self.timeout_watchdog.start()
 
308
 
 
309
    def _datagramReceived(self, datagram):
 
310
        if datagram.opcode == OP_OACK:
 
311
            return self.tftp_OACK(datagram)
 
312
        elif (datagram.opcode == OP_ACK and datagram.blocknum == 0
 
313
                    and not self.session.started):
 
314
            self.session.transport = self.transport
 
315
            self.session.startProtocol()
 
316
            if self.timeout_watchdog is not None and self.timeout_watchdog.active():
 
317
                self.timeout_watchdog.cancel()
 
318
            return self.session.nextBlock()
 
319
        elif self.session.started:
 
320
            return self.session.datagramReceived(datagram)
 
321
 
 
322
    def tftp_OACK(self, datagram):
 
323
        """Handle incoming OACK datagram, process and apply options and hand over
 
324
        control to the underlying L{ReadSession}.
 
325
 
 
326
        @param datagram: OACK datagram
 
327
        @type datagram: L{OACKDatagram}
 
328
 
 
329
        """
 
330
        if not self.session.started:
 
331
            self.resultant_options = self.processOptions(datagram.options)
 
332
            if self.timeout_watchdog is not None and self.timeout_watchdog.active():
 
333
                self.timeout_watchdog.cancel()
 
334
            self.applyOptions(self.session, self.resultant_options)
 
335
            self.session.transport = self.transport
 
336
            self.session.startProtocol()
 
337
            return self.session.nextBlock()
 
338
        else:
 
339
            log.msg("Duplicate OACK received, ignored")
 
340
 
 
341
class RemoteOriginReadSession(TFTPBootstrap):
 
342
    """Bootstraps a L{ReadSession}, that was started remotely, - we've received
 
343
    a RRQ.
 
344
 
 
345
    """
 
346
    timeout = (1, 3, 7)
 
347
 
 
348
    def __init__(self, remote, reader, options=None, _clock=None):
 
349
        TFTPBootstrap.__init__(self, remote, reader, options, _clock)
 
350
        self.session = ReadSession(reader, self._clock)
 
351
 
 
352
    def option_tsize(self, val):
 
353
        """Process tsize option.
 
354
 
 
355
        If tsize is zero, get the size of the file to be read so that it can
 
356
        be returned in the OACK datagram.
 
357
 
 
358
        @see: L{TFTPBootstrap.option_tsize}
 
359
 
 
360
        """
 
361
        val = TFTPBootstrap.option_tsize(self, val)
 
362
        if val == str(0):
 
363
            val = self.session.reader.size
 
364
            if val is not None:
 
365
                val = str(val)
 
366
        return val
 
367
 
 
368
    def startProtocol(self):
 
369
        """Start sending an OACK datagram if we were initialized with options
 
370
        or start the L{ReadSession} immediately.
 
371
 
 
372
        """
 
373
        self.transport.connect(*self.remote)
 
374
        if self.options:
 
375
            self.resultant_options = self.processOptions(self.options)
 
376
            bytes = OACKDatagram(self.resultant_options).to_wire()
 
377
            self.timeout_watchdog = SequentialCall.run(
 
378
                self.timeout[:-1],
 
379
                callable=self.transport.write, callable_args=[bytes, ],
 
380
                on_timeout=lambda: self._clock.callLater(self.timeout[-1], self.timedOut),
 
381
                run_now=True,
 
382
                _clock=self._clock
 
383
            )
 
384
        else:
 
385
            self.session.transport = self.transport
 
386
            self.session.startProtocol()
 
387
            return self.session.nextBlock()
 
388
 
 
389
    def _datagramReceived(self, datagram):
 
390
        if datagram.opcode == OP_ACK and datagram.blocknum == 0:
 
391
            return self.tftp_ACK(datagram)
 
392
        elif self.session.started:
 
393
            return self.session.datagramReceived(datagram)
 
394
 
 
395
    def tftp_ACK(self, datagram):
 
396
        """Handle incoming ACK datagram. Hand over control to the underlying
 
397
        L{ReadSession}.
 
398
 
 
399
        @param datagram: ACK datagram
 
400
        @type datagram: L{ACKDatagram}
 
401
 
 
402
        """
 
403
        if self.timeout_watchdog is not None:
 
404
            self.timeout_watchdog.cancel()
 
405
        if not self.session.started:
 
406
            self.applyOptions(self.session, self.resultant_options)
 
407
            self.session.transport = self.transport
 
408
            self.session.startProtocol()
 
409
            return self.session.nextBlock()