<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1 +1,3 @@
 *.pyc
+*.swp
+tags</diff>
      <filename>.gitignore</filename>
    </modified>
    <modified>
      <diff>@@ -21,7 +21,7 @@ def main():
                       '--upload',
                       help='filename to upload')
     parser.add_option('-b',
-                      '--blocksize',
+                      '--blksize',
                       help='udp packet size to use (default: 512)',
                       default=512)
     parser.add_option('-o',
@@ -76,11 +76,11 @@ def main():
     else:
         tftpy.setLogLevel(logging.INFO)
 
-    progresshook = Progress(tftpy.logger.info).progresshook
+    progresshook = Progress(tftpy.log.info).progresshook
 
     tftp_options = {}
-    if options.blocksize:
-        tftp_options['blksize'] = int(options.blocksize)
+    if options.blksize:
+        tftp_options['blksize'] = int(options.blksize)
     if options.tsize:
         tftp_options['tsize'] = 0
 
@@ -103,6 +103,8 @@ def main():
     except tftpy.TftpException, err:
         sys.stderr.write(&quot;%s\n&quot; % str(err))
         sys.exit(1)
+    except KeyboardInterrupt:
+        pass
 
 if __name__ == '__main__':
     main()</diff>
      <filename>bin/tftpy_client.py</filename>
    </modified>
    <modified>
      <diff>@@ -20,8 +20,8 @@ def main():
     parser.add_option('-r',
                       '--root',
                       type='string',
-                      help='path to serve from (default: /tftpboot)',
-                      default=&quot;/tftpboot&quot;)
+                      help='path to serve from',
+                      default=None)
     parser.add_option('-d',
                       '--debug',
                       action='store_true',
@@ -34,9 +34,16 @@ def main():
     else:
         tftpy.setLogLevel(logging.INFO)
 
+    if not options.root:
+        parser.print_help()
+        sys.exit(1)
+
     server = tftpy.TftpServer(options.root)
     try:
         server.listen(options.ip, options.port)
+    except tftpy.TftpException, err:
+        sys.stderr.write(&quot;%s\n&quot; % str(err))
+        sys.exit(1)
     except KeyboardInterrupt:
         pass
 </diff>
      <filename>bin/tftpy_server.py</filename>
    </modified>
    <modified>
      <diff>@@ -35,6 +35,11 @@ class TftpClient(TftpSession):
         SOCK_TIMEOUT setting, which is the amount of time that the client will
         wait for a receive packet to arrive.&quot;&quot;&quot;
         # We're downloading.
+        log.debug(&quot;Creating download context with the following params:&quot;)
+        log.debug(&quot;host = %s, port = %s, filename = %s, output = %s&quot;
+            % (self.host, self.iport, filename, output))
+        log.debug(&quot;options = %s, packethook = %s, timeout = %s&quot;
+            % (self.options, packethook, timeout))
         self.context = TftpContextClientDownload(self.host,
                                                  self.iport,
                                                  filename,</diff>
      <filename>tftpy/TftpClient.py</filename>
    </modified>
    <modified>
      <diff>@@ -4,22 +4,8 @@ from TftpShared import *
 class TftpSession(object):
     &quot;&quot;&quot;This class is the base class for the tftp client and server. Any shared
     code should be in this class.&quot;&quot;&quot;
-
-    def __init__(self):
-        &quot;&quot;&quot;Class constructor.&quot;&quot;&quot;
-        self.options = None
-        self.state = None
-        self.dups = 0
-        self.errors = 0
-        
-    def senderror(self, sock, errorcode, address, port):
-        &quot;&quot;&quot;This method uses the socket passed, and uses the errorcode, address
-        and port to compose and send an error packet.&quot;&quot;&quot;
-        log.debug(&quot;In senderror, being asked to send error %d to %s:%s&quot;
-                % (errorcode, address, port))
-        errpkt = TftpPacketERR()
-        errpkt.errorcode = errorcode
-        sock.sendto(errpkt.encode().buffer, (address, port))
+    # FIXME: do we need this anymore?
+    pass
 
 class TftpPacketWithOptions(object):
     &quot;&quot;&quot;This class exists to permit some TftpPacket subclasses to share code</diff>
      <filename>tftpy/TftpPacketTypes.py</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,7 @@ import select
 from TftpShared import *
 from TftpPacketTypes import *
 from TftpPacketFactory import *
+from TftpStates import *
 
 class TftpServer(TftpSession):
     &quot;&quot;&quot;This class implements a tftp server object.&quot;&quot;&quot;
@@ -19,9 +20,9 @@ class TftpServer(TftpSession):
         # FIXME: What about multiple roots?
         self.root = os.path.abspath(tftproot)
         self.dyn_file_func = dyn_file_func
-        # A dict of handlers, where each session is keyed by a string like
+        # A dict of sessions, where each session is keyed by a string like
         # ip:tid for the remote end.
-        self.handlers = {}
+        self.sessions = {}
 
         if os.path.exists(self.root):
             log.debug(&quot;tftproot %s does exist&quot; % self.root)
@@ -89,8 +90,8 @@ class TftpServer(TftpSession):
                     log.debug(&quot;Read %d bytes&quot; % len(buffer))
 
                     recvpkt = tftp_factory.parse(buffer)
-                    # FIXME: Is this the best way to do a session key? What
-                    # about symmetric udp?
+                    # Forge a session key based on the client's IP and port,
+                    # which should safely work through NAT.
                     key = &quot;%s:%s&quot; % (raddress, rport)
 
                     if not self.sessions.has_key(key):
@@ -108,36 +109,37 @@ class TftpServer(TftpSession):
 
                 else:
                     # Must find the owner of this traffic.
-                    for key in self.session:
-                        if readysock == self.session[key].sock:
+                    for key in self.sessions:
+                        if readysock == self.sessions[key].sock:
+                            log.info(&quot;Matched input to session key %s&quot;
+                                % key)
                             try:
-                                self.session[key].cycle()
-                                if self.session[key].state == None:
+                                self.sessions[key].cycle()
+                                if self.sessions[key].state == None:
                                     log.info(&quot;Successful transfer.&quot;)
                                     deletion_list.append(key)
-                                break
                             except TftpException, err:
                                 deletion_list.append(key)
                                 log.error(&quot;Fatal exception thrown from &quot;
-                                          &quot;handler: %s&quot; % str(err))
+                                          &quot;session %s: %s&quot;
+                                          % (key, str(err)))
+                            break
 
                     else:
                         log.error(&quot;Can't find the owner for this packet. &quot;
                                   &quot;Discarding.&quot;)
 
-            log.debug(&quot;Looping on all handlers to check for timeouts&quot;)
+            log.debug(&quot;Looping on all sessions to check for timeouts&quot;)
             now = time.time()
             for key in self.sessions:
                 try:
                     self.sessions[key].checkTimeout(now)
                 except TftpException, err:
-                    log.error(&quot;Fatal exception thrown from handler: %s&quot;
-                            % str(err))
+                    log.error(str(err))
                     deletion_list.append(key)
 
             log.debug(&quot;Iterating deletion list.&quot;)
             for key in deletion_list:
                 if self.sessions.has_key(key):
-                    log.debug(&quot;Deleting handler %s&quot; % key)
+                    log.debug(&quot;Deleting session %s&quot; % key)
                     del self.sessions[key]
-            deletion_list = []</diff>
      <filename>tftpy/TftpServer.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 from TftpShared import *
 from TftpPacketTypes import *
 from TftpPacketFactory import *
-import socket, time
+import socket, time, os
 
 ###############################################################################
 # Utility classes
@@ -39,10 +39,10 @@ class TftpMetrics(object):
         &quot;&quot;&quot;This method adds a dup for a block number to the metrics.&quot;&quot;&quot;
         log.debug(&quot;Recording a dup for block %d&quot; % blocknumber)
         if self.dups.has_key(blocknumber):
-            self.dups[pkt.blocknumber] += 1
+            self.dups[blocknumber] += 1
         else:
-            self.dups[pkt.blocknumber] = 1
-        tftpassert(self.dups[pkt.blocknumber] &lt; MAX_DUPS,
+            self.dups[blocknumber] = 1
+        tftpassert(self.dups[blocknumber] &lt; MAX_DUPS,
             &quot;Max duplicates for block %d reached&quot; % blocknumber)
 
 ###############################################################################
@@ -73,10 +73,14 @@ class TftpContext(object):
         self.metrics = TftpMetrics()
         # Flag when the transfer is pending completion.
         self.pending_complete = False
+        # Time when this context last received any traffic.
+        self.last_update = 0
 
     def checkTimeout(self, now):
-        # FIXME
-        pass
+        &quot;&quot;&quot;Compare current time with last_update time, and raise an exception
+        if we're over SOCK_TIMEOUT time.&quot;&quot;&quot;
+        if now - self.last_update &gt; SOCK_TIMEOUT:
+            raise TftpException, &quot;Timeout waiting for traffic&quot;
 
     def start(self):
         return NotImplementedError, &quot;Abstract method&quot;
@@ -126,6 +130,8 @@ class TftpContext(object):
         # Ok, we've received a packet. Log it.
         log.debug(&quot;Received %d bytes from %s:%s&quot;
                         % (len(buffer), raddress, rport))
+        # And update our last updated time.
+        self.last_update = time.time()
 
         # Decode it.
         recvpkt = self.factory.parse(buffer)
@@ -160,17 +166,23 @@ class TftpContextServer(TftpContext):
                              timeout)
         # At this point we have no idea if this is a download or an upload. We
         # need to let the start state determine that.
-        self.state = TftpStateServerStart()
+        self.state = TftpStateServerStart(self)
         self.root = root
         self.dyn_file_func = dyn_file_func
+        # In a server, the tidport is the same as the port. This is also true
+        # with symmetric UDP, which we haven't implemented yet.
+        self.tidport = port
 
     def start(self, buffer):
         &quot;&quot;&quot;Start the state cycle. Note that the server context receives an
-        initial packet in its start method.&quot;&quot;&quot;
-        log.debug(&quot;TftpContextServer.start() - pkt = %s&quot; % pkt)
-
+        initial packet in its start method. Also note that the server does not
+        loop on cycle(), as it expects the TftpServer object to manage
+        that.&quot;&quot;&quot;
+        log.debug(&quot;In TftpContextServer.start&quot;)
         self.metrics.start_time = time.time()
         log.debug(&quot;set metrics.start_time to %s&quot; % self.metrics.start_time)
+        # And update our last updated time.
+        self.last_update = time.time()
 
         pkt = self.factory.parse(buffer)
         log.debug(&quot;TftpContextServer.start() - factory returned a %s&quot; % pkt)
@@ -181,16 +193,19 @@ class TftpContextServer(TftpContext):
                                        self.host,
                                        self.port)
 
-        try:
-            while self.state:
-                log.debug(&quot;state is %s&quot; % self.state)
-                self.cycle()
-        finally:
-            self.fileobj.close()
+        # FIXME
+        # How do we ensure that the server closes files, even on error?
 
 class TftpContextClientUpload(TftpContext):
     &quot;&quot;&quot;The upload context for the client during an upload.&quot;&quot;&quot;
-    def __init__(self, host, port, filename, input, options, packethook, timeout):
+    def __init__(self,
+                 host,
+                 port,
+                 filename,
+                 input,
+                 options,
+                 packethook,
+                 timeout):
         TftpContext.__init__(self,
                              host,
                              port,
@@ -234,14 +249,22 @@ class TftpContextClientUpload(TftpContext):
 
 class TftpContextClientDownload(TftpContext):
     &quot;&quot;&quot;The download context for the client during a download.&quot;&quot;&quot;
-    def __init__(self, host, port, filename, output, options, packethook, timeout):
+    def __init__(self,
+                 host,
+                 port,
+                 filename,
+                 output,
+                 options,
+                 packethook,
+                 timeout):
         TftpContext.__init__(self,
                              host,
                              port,
-                             filename,
-                             options,
-                             packethook,
                              timeout)
+        # FIXME: should we refactor setting of these params?
+        self.file_to_transfer = filename
+        self.options = options
+        self.packethook = packethook
         # FIXME - need to support alternate return formats than files?
         # File-like objects would be ideal, ala duck-typing.
         self.fileobj = open(output, &quot;wb&quot;)
@@ -327,12 +350,21 @@ class TftpState(object):
             if option == 'blksize':
                 # Make sure it's valid.
                 if int(options[option]) &gt; MAX_BLKSIZE:
+                    log.info(&quot;Client requested blksize greater than %d &quot;
+                             &quot;setting to maximum&quot; % MAX_BLKSIZE)
                     accepted_options[option] = MAX_BLKSIZE
-                elif option == 'tsize':
-                    log.debug(&quot;tsize option is set&quot;)
-                    accepted_options['tsize'] = 1
+                elif int(options[option]) &lt; MIN_BLKSIZE:
+                    log.info(&quot;Client requested blksize less than %d &quot;
+                             &quot;setting to minimum&quot; % MIN_BLKSIZE)
+                    accepted_options[option] = MIN_BLKSIZE
                 else:
-                    log.info(&quot;Dropping unsupported option '%s'&quot; % option)
+                    accepted_options[option] = options[option]
+            elif option == 'tsize':
+                log.debug(&quot;tsize option is set&quot;)
+                accepted_options['tsize'] = 1
+            else:
+                log.info(&quot;Dropping unsupported option '%s'&quot; % option)
+        log.debug(&quot;returning these accepted options: %s&quot; % accepted_options)
         return accepted_options
 
     def serverInitial(self, pkt, raddress, rport):
@@ -388,15 +420,16 @@ class TftpState(object):
         finished.&quot;&quot;&quot;
         finished = False
         blocknumber = self.context.next_block
+        tftpassert( blocknumber &gt; 0, &quot;There is no block zero!&quot; )
         if not resend:
             blksize = int(self.context.options['blksize'])
             buffer = self.context.fileobj.read(blksize)
             log.debug(&quot;Read %d bytes into buffer&quot; % len(buffer))
             if len(buffer) &lt; blksize:
-                log.info(&quot;Reached EOF on file %s&quot; % self.context.input)
+                log.info(&quot;Reached EOF on file %s&quot;
+                    % self.context.file_to_transfer)
                 finished = True
-            self.context.next_block += 1
-            self.bytes += len(buffer)
+            self.context.metrics.bytes += len(buffer)
         else:
             log.warn(&quot;Resending block number %d&quot; % blocknumber)
         dat = TftpPacketDAT()
@@ -413,7 +446,8 @@ class TftpState(object):
         &quot;&quot;&quot;This method sends an ack packet to the block number specified. If
         none is specified, it defaults to the next_block property in the
         parent context.&quot;&quot;&quot;
-        if not blocknumber:
+        log.debug(&quot;in sendACK, blocknumber is %s&quot; % blocknumber)
+        if blocknumber is None:
             blocknumber = self.context.next_block
         log.info(&quot;sending ack to block %d&quot; % blocknumber)
         ackpkt = TftpPacketACK()
@@ -435,9 +469,9 @@ class TftpState(object):
     def sendOACK(self):
         &quot;&quot;&quot;This method sends an OACK packet with the options from the current
         context.&quot;&quot;&quot;
-        log.debug(&quot;In sendOACK with options %s&quot; % options)
+        log.debug(&quot;In sendOACK with options %s&quot; % self.context.options)
         pkt = TftpPacketOACK()
-        pkt.options = self.options
+        pkt.options = self.context.options
         self.context.sock.sendto(pkt.encode().buffer,
                                  (self.context.host,
                                   self.context.tidport))
@@ -464,6 +498,10 @@ class TftpState(object):
                 return None
 
         elif pkt.blocknumber &lt; self.context.next_block:
+            if pkt.blocknumber == 0:
+                log.warn(&quot;There is no block zero!&quot;)
+                self.sendError(TftpErrors.IllegalTftpOp)
+                raise TftpException, &quot;There is no block zero!&quot;
             log.warn(&quot;dropping duplicate block %d&quot; % pkt.blocknumber)
             self.context.metrics.add_dup(pkt.blocknumber)
             log.debug(&quot;ACKing block %d again, just in case&quot; % pkt.blocknumber)
@@ -502,12 +540,17 @@ class TftpStateServerRecvRRQ(TftpState):
 
         # Options negotiation.
         if sendoack:
+            # Note, next_block is 0 here since that's the proper
+            # acknowledgement to an OACK.
+            # FIXME: perhaps we do need a TftpStateExpectOACK class...
             self.sendOACK()
-            return TftpStateServerOACK(self.context)
         else:
+            self.context.next_block = 1
             log.debug(&quot;No requested options, starting send...&quot;)
             self.context.pending_complete = self.sendDAT()
-            return TftpStateExpectACK(self.context)
+        # Note, we expect an ack regardless of whether we sent a DAT or an
+        # OACK.
+        return TftpStateExpectACK(self.context)
 
         # Note, we don't have to check any other states in this method, that's
         # up to the caller.
@@ -579,6 +622,9 @@ class TftpStateExpectACK(TftpState):
                     return None
                 else:
                     log.debug(&quot;Good ACK, sending next DAT&quot;)
+                    self.context.next_block += 1
+                    log.debug(&quot;Incremented next_block to %d&quot;
+                        % (self.context.next_block))
                     self.context.pending_complete = self.sendDAT()
 
             elif pkt.blocknumber &lt; self.context.next_block:</diff>
      <filename>tftpy/TftpStates.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>62b22fb562eff64a6d6bb6c1a1a3c194d668d9a1</id>
    </parent>
  </parents>
  <author>
    <name>Michael P. Soulier</name>
    <email>msoulier@digitaltorque.ca</email>
  </author>
  <url>http://github.com/msoulier/tftpy/commit/a6a18c178b4b60d49baa42c55fb7948d955de263</url>
  <id>a6a18c178b4b60d49baa42c55fb7948d955de263</id>
  <committed-date>2009-08-16T16:57:11-07:00</committed-date>
  <authored-date>2009-08-16T16:44:57-07:00</authored-date>
  <message>First successful download with both client and server.</message>
  <tree>c499eae963c59073eb1494847166ebb8222118c9</tree>
  <committer>
    <name>Michael P. Soulier</name>
    <email>msoulier@digitaltorque.ca</email>
  </committer>
</commit>
