Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
  • 3 commits
  • 6 files changed
  • 2 commit comments
  • 1 contributor
View
5 README.markdown
@@ -12,9 +12,8 @@ A Twisted-based TFTP implementation
- netascii transfer mode.
- [RFC2347](http://tools.ietf.org/html/rfc2347) (TFTP Option
Extension) support. *blksize*
-([RFC2348](http://tools.ietf.org/html/rfc2348)) and *timeout* (partial
-support for [RFC2349](http://tools.ietf.org/html/rfc2349)) options are
-supported.
+([RFC2348](http://tools.ietf.org/html/rfc2348)), *timeout* and *tsize*
+([RFC2349](http://tools.ietf.org/html/rfc2349)) options are supported.
- An actual TFTP server.
- Plugin for twistd.
- Tests
View
11 tftp/backend.py
@@ -62,6 +62,9 @@ def get_writer(file_name):
class IReader(interface.Interface):
"""An object, that performs reads on request of the TFTP protocol"""
+ size = interface.Attribute(
+ "The size of the file to be read, or C{None} if it's not known.")
+
def read(size):
"""Attempt to read C{size} number of bytes.
@@ -130,6 +133,14 @@ def __init__(self, file_path):
raise FileNotFound(self.file_path)
self.state = 'active'
+ @property
+ def size(self):
+ """
+ @see: L{IReader.size}
+
+ """
+ return self.file_path.getsize()
+
def read(self, size):
"""
@see: L{IReader.read}
View
16 tftp/bootstrap.py
@@ -350,6 +350,22 @@ def __init__(self, remote, reader, options=None, _clock=None):
TFTPBootstrap.__init__(self, remote, reader, options, _clock)
self.session = ReadSession(reader, self._clock)
+ def option_tsize(self, val):
+ """Process tsize option.
+
+ If tsize is zero, get the size of the file to be read so that it can
+ be returned in the OACK datagram.
+
+ @see: L{TFTPBootstrap.option_tsize}
+
+ """
+ val = TFTPBootstrap.option_tsize(self, val)
+ if val == str(0):
+ val = self.session.reader.size
+ if val is not None:
+ val = str(val)
+ return val
+
def startProtocol(self):
"""Start sending an OACK datagram if we were initialized with options
or start the L{ReadSession} immediately.
View
4 tftp/test/test_backend.py
@@ -73,6 +73,10 @@ def test_read_existing_file(self):
"The file has been exhausted and should be in the closed state")
self.assertEqual(ostring, self.test_data)
+ def test_size(self):
+ r = FilesystemReader(self.temp_dir.child('foo'))
+ self.assertEqual(len(self.test_data), r.size)
+
def test_cancel(self):
r = FilesystemReader(self.temp_dir.child('foo'))
r.read(3)
View
22 tftp/test/test_bootstrap.py
@@ -571,14 +571,17 @@ def setUp(self):
temp_fd.write(self.test_data)
self.reader = DelayedReader(self.target, _clock=self.clock, delay=2)
self.transport = FakeTransport(hostAddress=('127.0.0.1', self.port))
+ self.options = OrderedDict()
+ self.options['blksize'] = '9'
+ self.options['tsize'] = '34'
self.rs = RemoteOriginReadSession(('127.0.0.1', 65465), self.reader,
- options={'blksize':'9'}, _clock=self.clock)
+ options=self.options, _clock=self.clock)
self.rs.transport = self.transport
def test_option_normal(self):
self.rs.startProtocol()
self.clock.advance(0.1)
- oack_datagram = OACKDatagram({'blksize':'9'}).to_wire()
+ oack_datagram = OACKDatagram(self.options).to_wire()
self.assertEqual(self.transport.value(), oack_datagram)
self.clock.advance(3)
self.assertEqual(self.transport.value(), oack_datagram * 2)
@@ -593,7 +596,7 @@ def test_option_normal(self):
def test_option_timeout(self):
self.rs.startProtocol()
self.clock.advance(0.1)
- oack_datagram = OACKDatagram({'blksize':'9'}).to_wire()
+ oack_datagram = OACKDatagram(self.options).to_wire()
self.assertEqual(self.transport.value(), oack_datagram)
self.failIf(self.transport.disconnecting)
@@ -609,5 +612,18 @@ def test_option_timeout(self):
self.assertEqual(self.transport.value(), oack_datagram * 3)
self.failUnless(self.transport.disconnecting)
+ def test_option_tsize(self):
+ # A tsize option of 0 sent as part of a read session prompts a tsize
+ # response with the actual size of the file.
+ self.options['tsize'] = '0'
+ self.rs.startProtocol()
+ self.clock.advance(0.1)
+ self.transport.clear()
+ self.clock.advance(3)
+ # The response contains the size of the test data.
+ self.options['tsize'] = str(len(self.test_data))
+ oack_datagram = OACKDatagram(self.options).to_wire()
+ self.assertEqual(self.transport.value(), oack_datagram)
+
def tearDown(self):
shutil.rmtree(self.tmp_dir_path)
View
2  tftp/test/test_sessions.py
@@ -54,6 +54,8 @@ def c(ign):
class FailingReader(object):
interface.implements(IReader)
+ size = None
+
def read(self, size):
raise IOError('A failure')

Showing you all comments on commits in this comparison.

@shylent

To be honest, I would never make it a property. Why? Because it looks like attribute access, but it actually results in a filesystem hit (for the first time only, though, due to behaviour of t.p.filepath.FilePath.getsize). I would rather have it as a method.

Still, it is not that much of a problem, I think. More of a style thing.

@allenap
Owner

You're right. I've just hit a problem with having it as a property too: it may need to return a Deferred. I shall change this to be a function.

Something went wrong with that request. Please try again.