tftpplus 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +47 -0
 - data/README +21 -0
 - data/bin/tftp_client.rb +112 -0
 - data/lib/net/tftp+.rb +796 -0
 - data/test/test.rb +58 -0
 - metadata +49 -0
 
    
        data/ChangeLog
    ADDED
    
    | 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            2006-12-08 msoulier
         
     | 
| 
      
 2 
     | 
    
         
            +
            	* Fixed handling of remote TID.
         
     | 
| 
      
 3 
     | 
    
         
            +
            	* Added tar task to Rakefile.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            2006-10-10 msoulier
         
     | 
| 
      
 6 
     | 
    
         
            +
            	* Added site, and Rakefile entry to push site.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            2006-10-10 01:46  msoulier
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            	* trunk/ChangeLog, trunk/README, trunk/doc,
         
     | 
| 
      
 11 
     | 
    
         
            +
            	  trunk/lib/net/tftp+.rb: Adding Rakefile
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            2006-10-06 01:35  msoulier
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb: Optimizing Tftp object dispatch
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            2006-10-04 01:27  msoulier
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb: minor method rename
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            2006-09-25 02:11  msoulier
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb: Added timeouts on recvfrom
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            2006-09-24 02:39  msoulier
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb: Updated debugging output
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            2006-09-24 02:31  msoulier
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb, trunk/share, trunk/share/tftp_client.rb,
         
     | 
| 
      
 32 
     | 
    
         
            +
            	  trunk/test/test.rb: Moved client code into its own sample client.
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            2006-09-22 16:48  msoulier
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            	* trunk/lib/net/tftp+.rb: Pulling hardcoded test
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            2006-09-22 16:44  msoulier
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            	* doc, lib, test, trunk/doc, trunk/lib, trunk/test: Setting up
         
     | 
| 
      
 41 
     | 
    
         
            +
            	  branch structure.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            2006-09-22 16:42  msoulier
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            	* doc, doc/rfc1350.txt, doc/rfc2347.txt, doc/rfc2348.txt, lib,
         
     | 
| 
      
 46 
     | 
    
         
            +
            	  lib/net, lib/net/tftp+.rb, test, test/test.rb: Initial import.
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
    
        data/README
    ADDED
    
    | 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            About Tftpplus:
         
     | 
| 
      
 2 
     | 
    
         
            +
            ===============
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            A new tftp library for clients and servers that supports RFCs 1350, 2347 and
         
     | 
| 
      
 5 
     | 
    
         
            +
            2348 (ie. variable block sizes).
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Release Notes:
         
     | 
| 
      
 8 
     | 
    
         
            +
            ==============
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            About version 0.2:
         
     | 
| 
      
 11 
     | 
    
         
            +
            ==================
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            - Fixed handling of remote TID.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            About version 0.1:
         
     | 
| 
      
 16 
     | 
    
         
            +
            ==================
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            - Added timeouts on recvfrom
         
     | 
| 
      
 19 
     | 
    
         
            +
            - Updated debugging output
         
     | 
| 
      
 20 
     | 
    
         
            +
            - Moved client code into its own sample client.
         
     | 
| 
      
 21 
     | 
    
         
            +
            - Client functioning with support for variable block sizes.
         
     | 
    
        data/bin/tftp_client.rb
    ADDED
    
    | 
         @@ -0,0 +1,112 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'net/tftp+'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'ostruct'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            LogLevel = Logger::INFO
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            $tftplog = Logger.new($stderr)
         
     | 
| 
      
 12 
     | 
    
         
            +
            $tftplog.level = LogLevel
         
     | 
| 
      
 13 
     | 
    
         
            +
            $log = $tftplog
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            def parse_args
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Set up defaults
         
     | 
| 
      
 17 
     | 
    
         
            +
                options = OpenStruct.new
         
     | 
| 
      
 18 
     | 
    
         
            +
                options.filename = nil
         
     | 
| 
      
 19 
     | 
    
         
            +
                options.host = nil
         
     | 
| 
      
 20 
     | 
    
         
            +
                options.debug = false
         
     | 
| 
      
 21 
     | 
    
         
            +
                options.blksize = 512
         
     | 
| 
      
 22 
     | 
    
         
            +
                options.port = 69
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                banner =<<EOF
         
     | 
| 
      
 25 
     | 
    
         
            +
            Usage: tftp_client <options>
         
     | 
| 
      
 26 
     | 
    
         
            +
            EOF
         
     | 
| 
      
 27 
     | 
    
         
            +
                opts = nil
         
     | 
| 
      
 28 
     | 
    
         
            +
                begin
         
     | 
| 
      
 29 
     | 
    
         
            +
                    $log.debug("client") { "Parsing command line arguments" }
         
     | 
| 
      
 30 
     | 
    
         
            +
                    opts = OptionParser.new do |opts|
         
     | 
| 
      
 31 
     | 
    
         
            +
                        opts.banner = banner
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                        opts.on('-f', '--filename=MANDATORY', 'Remote filename') do |f|
         
     | 
| 
      
 34 
     | 
    
         
            +
                            options.filename = f
         
     | 
| 
      
 35 
     | 
    
         
            +
                            $log.debug('client') { "filename is #{f}" }
         
     | 
| 
      
 36 
     | 
    
         
            +
                        end
         
     | 
| 
      
 37 
     | 
    
         
            +
                        opts.on('-h', '--host=MANDATORY', 'Remote host or IP address') do |h|
         
     | 
| 
      
 38 
     | 
    
         
            +
                            options.host = h
         
     | 
| 
      
 39 
     | 
    
         
            +
                            $log.debug('client') { "host is #{h}" }
         
     | 
| 
      
 40 
     | 
    
         
            +
                        end
         
     | 
| 
      
 41 
     | 
    
         
            +
                        opts.on('-p', '--port=', 'Remote port to use (default: 69)') do |p|
         
     | 
| 
      
 42 
     | 
    
         
            +
                            options.port = p.to_i
         
     | 
| 
      
 43 
     | 
    
         
            +
                            $log.debug('client') { "port is #{p}" }
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                        opts.on('-d', '--debug', 'Debugging output on') do |d|
         
     | 
| 
      
 46 
     | 
    
         
            +
                            options.debug = d
         
     | 
| 
      
 47 
     | 
    
         
            +
                            $log.level = Logger::DEBUG
         
     | 
| 
      
 48 
     | 
    
         
            +
                            $log.debug('client') { "Debug output requested" }
         
     | 
| 
      
 49 
     | 
    
         
            +
                        end
         
     | 
| 
      
 50 
     | 
    
         
            +
                        opts.on('-b', '--blksize=', 'Blocksize option: 8-65536 bytes') do |b|
         
     | 
| 
      
 51 
     | 
    
         
            +
                            options.blksize = b.to_i
         
     | 
| 
      
 52 
     | 
    
         
            +
                            $log.debug('client') { "blksize is #{b}" }
         
     | 
| 
      
 53 
     | 
    
         
            +
                        end
         
     | 
| 
      
 54 
     | 
    
         
            +
                        opts.on_tail('-h', '--help', 'Show this message') do
         
     | 
| 
      
 55 
     | 
    
         
            +
                            puts opts
         
     | 
| 
      
 56 
     | 
    
         
            +
                            exit
         
     | 
| 
      
 57 
     | 
    
         
            +
                        end
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end.parse!
         
     | 
| 
      
 59 
     | 
    
         
            +
                    
         
     | 
| 
      
 60 
     | 
    
         
            +
                    unless options.filename and options.host
         
     | 
| 
      
 61 
     | 
    
         
            +
                        raise OptionParser::InvalidOption,
         
     | 
| 
      
 62 
     | 
    
         
            +
                            "Both --host and --filename are required"
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                    #unless options.blksize =~ /^\d+/
         
     | 
| 
      
 65 
     | 
    
         
            +
                    #    raise OptionParser::InvalidOption,
         
     | 
| 
      
 66 
     | 
    
         
            +
                    #        "blksize must be an integer"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    #end
         
     | 
| 
      
 68 
     | 
    
         
            +
                    unless options.blksize >= 8 and options.blksize <= 65536
         
     | 
| 
      
 69 
     | 
    
         
            +
                        raise OptionParser::InvalidOption,
         
     | 
| 
      
 70 
     | 
    
         
            +
                            "blksize can only be between 8 and 65536 bytes"
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                    unless options.port > 0 and options.port < 65537
         
     | 
| 
      
 73 
     | 
    
         
            +
                        raise OptionParser::InvalidOption,
         
     | 
| 
      
 74 
     | 
    
         
            +
                            "port must be positive integer between 1 and 65536"
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                rescue Exception => details
         
     | 
| 
      
 77 
     | 
    
         
            +
                    $stderr.puts details.to_s
         
     | 
| 
      
 78 
     | 
    
         
            +
                    $stderr.puts opts
         
     | 
| 
      
 79 
     | 
    
         
            +
                    exit 1
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                return options
         
     | 
| 
      
 83 
     | 
    
         
            +
            end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            def main
         
     | 
| 
      
 86 
     | 
    
         
            +
                options = parse_args
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                size = 0
         
     | 
| 
      
 89 
     | 
    
         
            +
                start = Time.now
         
     | 
| 
      
 90 
     | 
    
         
            +
                $log.info('client') { "Starting download of #{options.filename} from #{options.host}" }
         
     | 
| 
      
 91 
     | 
    
         
            +
                $log.info('client') { "Options: blksize = #{options.blksize}" }
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                client = TftpClient.new(options.host, options.port)
         
     | 
| 
      
 94 
     | 
    
         
            +
                tftp_opts = { :blksize => options.blksize.to_i }
         
     | 
| 
      
 95 
     | 
    
         
            +
                client.download(options.filename, options.filename, tftp_opts) do |pkt|
         
     | 
| 
      
 96 
     | 
    
         
            +
                    size += pkt.data.length
         
     | 
| 
      
 97 
     | 
    
         
            +
                    $log.debug('client') { "Downloaded #{size} bytes" }
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                finish = Time.now
         
     | 
| 
      
 101 
     | 
    
         
            +
                duration = finish - start
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                $log.info('client') { "" }
         
     | 
| 
      
 104 
     | 
    
         
            +
                $log.info('client') { "Started: #{start}" }
         
     | 
| 
      
 105 
     | 
    
         
            +
                $log.info('client') { "Finished: #{finish}" }
         
     | 
| 
      
 106 
     | 
    
         
            +
                $log.info('client') { "Duration: #{duration}" }
         
     | 
| 
      
 107 
     | 
    
         
            +
                $log.info('client') { "Downloaded #{size} bytes in #{duration} seconds" }
         
     | 
| 
      
 108 
     | 
    
         
            +
                $log.info('client') { "Throughput: #{(size/duration)*8} bps" }
         
     | 
| 
      
 109 
     | 
    
         
            +
                $log.info('client') { "            #{(size/duration)*8 / 1024} kbps" }
         
     | 
| 
      
 110 
     | 
    
         
            +
            end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            main
         
     | 
    
        data/lib/net/tftp+.rb
    ADDED
    
    | 
         @@ -0,0 +1,796 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # A Ruby library for Trivial File Transfer Protocol.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Supports the following RFCs
         
     | 
| 
      
 3 
     | 
    
         
            +
            # RFC 1350 - THE TFTP PROTOCOL (REVISION 2)
         
     | 
| 
      
 4 
     | 
    
         
            +
            # RFC 2347 - TFTP Option Extension
         
     | 
| 
      
 5 
     | 
    
         
            +
            # RFC 2348 - TFTP Blocksize Option
         
     | 
| 
      
 6 
     | 
    
         
            +
            # Currently, the TftpServer class is not functional.
         
     | 
| 
      
 7 
     | 
    
         
            +
            # The TftpClient class works fine. Let me know if this is not true.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require 'timeout'
         
     | 
| 
      
 11 
     | 
    
         
            +
            require 'resolv'
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            # Todo
         
     | 
| 
      
 14 
     | 
    
         
            +
            # - properly handle decoding of options ala rfc 2347
         
     | 
| 
      
 15 
     | 
    
         
            +
            # - use maxdups
         
     | 
| 
      
 16 
     | 
    
         
            +
            # - use socket timeouts
         
     | 
| 
      
 17 
     | 
    
         
            +
            # - implement variable block-sizes
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            MinBlkSize      = 8
         
     | 
| 
      
 20 
     | 
    
         
            +
            DefBlkSize      = 512
         
     | 
| 
      
 21 
     | 
    
         
            +
            MaxBlkSize      = 65536
         
     | 
| 
      
 22 
     | 
    
         
            +
            SockTimeout     = 5
         
     | 
| 
      
 23 
     | 
    
         
            +
            MaxDups         = 20
         
     | 
| 
      
 24 
     | 
    
         
            +
            Assertions      = true
         
     | 
| 
      
 25 
     | 
    
         
            +
            MaxRetry        = 5
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            # This class is a Nil logging device. It catches all of the logger calls and
         
     | 
| 
      
 28 
     | 
    
         
            +
            # does nothing with them. It is up to the client to provide a real logger
         
     | 
| 
      
 29 
     | 
    
         
            +
            # and assign it to $tftplog.
         
     | 
| 
      
 30 
     | 
    
         
            +
            class TftpNilLogger
         
     | 
| 
      
 31 
     | 
    
         
            +
                def method_missing(*args)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    # do nothing
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            # This global is the logger used by the library. By default it is an instance
         
     | 
| 
      
 37 
     | 
    
         
            +
            # of TftpNilLogger, which does nothing. Replace it with a logger if you want
         
     | 
| 
      
 38 
     | 
    
         
            +
            # one.
         
     | 
| 
      
 39 
     | 
    
         
            +
            $tftplog = TftpNilLogger.new
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            class TftpError < RuntimeError
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            # This function is a custom assertion in the library to catch unsupported
         
     | 
| 
      
 45 
     | 
    
         
            +
            # states and types. If the assertion fails, msg is raised in a TftpError
         
     | 
| 
      
 46 
     | 
    
         
            +
            # exception.
         
     | 
| 
      
 47 
     | 
    
         
            +
            def tftpassert(msg, &code)
         
     | 
| 
      
 48 
     | 
    
         
            +
                if not code.call and Assertions
         
     | 
| 
      
 49 
     | 
    
         
            +
                    raise TftpError, "Assertion Failed: #{msg}", caller
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            # This class is the root of all TftpPacket classes in the library. It should
         
     | 
| 
      
 54 
     | 
    
         
            +
            # not be instantiated directly. It exists to provide code sharing to the child
         
     | 
| 
      
 55 
     | 
    
         
            +
            # classes.
         
     | 
| 
      
 56 
     | 
    
         
            +
            class TftpPacket
         
     | 
| 
      
 57 
     | 
    
         
            +
                attr_accessor :opcode, :buffer
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                # Class constructor. This class and its children take no parameters. A
         
     | 
| 
      
 60 
     | 
    
         
            +
                # client is expected to set instance variables after instantiation.
         
     | 
| 
      
 61 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @opcode = 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @buffer = nil
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @options = {}
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                # Abstract method, must be implemented in all child classes.
         
     | 
| 
      
 68 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 69 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                # Abstract method, must be implemented in all child classes.
         
     | 
| 
      
 73 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 74 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                # This is a setter for the options hash. It ensures that the keys are
         
     | 
| 
      
 78 
     | 
    
         
            +
                # Symbols and that the values are strings. You can pass in a non-String
         
     | 
| 
      
 79 
     | 
    
         
            +
                # value as long as the .to_s method returns a good value.
         
     | 
| 
      
 80 
     | 
    
         
            +
                def options=(opts)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    myopts = {}
         
     | 
| 
      
 82 
     | 
    
         
            +
                    opts.each do |key, val|
         
     | 
| 
      
 83 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "looping on key #{key}, val #{val}" }
         
     | 
| 
      
 84 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "class of key is #{key.class}" }
         
     | 
| 
      
 85 
     | 
    
         
            +
                        tftpassert("options keys must be symbols") { key.class == Symbol }
         
     | 
| 
      
 86 
     | 
    
         
            +
                        myopts[key.to_s] = val.to_s
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @options = myopts
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                # A getter for the options hash.
         
     | 
| 
      
 92 
     | 
    
         
            +
                def options
         
     | 
| 
      
 93 
     | 
    
         
            +
                    return @options
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                protected
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                # This method takes the portion of the buffer containing the options and
         
     | 
| 
      
 99 
     | 
    
         
            +
                # decodes it, returning a hash of the option name/value pairs, with the
         
     | 
| 
      
 100 
     | 
    
         
            +
                # keys as Symbols and the values as Strings.
         
     | 
| 
      
 101 
     | 
    
         
            +
                def decode_options(buffer)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    # We need to variably decode the buffer. The buffer here is only that
         
     | 
| 
      
 103 
     | 
    
         
            +
                    # part of the original buffer containing options. We will decode the
         
     | 
| 
      
 104 
     | 
    
         
            +
                    # options here and return an options array.
         
     | 
| 
      
 105 
     | 
    
         
            +
                    nulls = 0
         
     | 
| 
      
 106 
     | 
    
         
            +
                    format = ""
         
     | 
| 
      
 107 
     | 
    
         
            +
                    # Count the nulls in the buffer, each one terminates a string.
         
     | 
| 
      
 108 
     | 
    
         
            +
                    buffer.collect do |c|
         
     | 
| 
      
 109 
     | 
    
         
            +
                        if c.to_i == 0
         
     | 
| 
      
 110 
     | 
    
         
            +
                            format += "Z*Z*"
         
     | 
| 
      
 111 
     | 
    
         
            +
                        end
         
     | 
| 
      
 112 
     | 
    
         
            +
                    end
         
     | 
| 
      
 113 
     | 
    
         
            +
                    struct = buffer.unpack(format)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                    unless struct.length % 2 == 0
         
     | 
| 
      
 116 
     | 
    
         
            +
                        raise TftpError, "packet with odd number of option/value pairs"
         
     | 
| 
      
 117 
     | 
    
         
            +
                    end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                    while not struct.empty?
         
     | 
| 
      
 120 
     | 
    
         
            +
                        name  = struct.shift
         
     | 
| 
      
 121 
     | 
    
         
            +
                        value = struct.shift
         
     | 
| 
      
 122 
     | 
    
         
            +
                        options[name.to_sym] = value
         
     | 
| 
      
 123 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "decoded option #{name} with value #{value}" }
         
     | 
| 
      
 124 
     | 
    
         
            +
                    end
         
     | 
| 
      
 125 
     | 
    
         
            +
                    return options
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
            end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
            # This class is a parent class for the RRQ and WRQ packets, as they share a
         
     | 
| 
      
 130 
     | 
    
         
            +
            # lot of code.
         
     | 
| 
      
 131 
     | 
    
         
            +
            #         2 bytes    string   1 byte     string   1 byte
         
     | 
| 
      
 132 
     | 
    
         
            +
            #         -----------------------------------------------
         
     | 
| 
      
 133 
     | 
    
         
            +
            #  RRQ/  | 01/02 |  Filename  |   0  |    Mode    |   0  |
         
     | 
| 
      
 134 
     | 
    
         
            +
            #  WRQ    -----------------------------------------------
         
     | 
| 
      
 135 
     | 
    
         
            +
            #      +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
         
     | 
| 
      
 136 
     | 
    
         
            +
            #      |  opc  |filename| 0 |  mode  | 0 | blksize| 0 | #octets| 0 |
         
     | 
| 
      
 137 
     | 
    
         
            +
            #      +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
         
     | 
| 
      
 138 
     | 
    
         
            +
            class TftpPacketInitial < TftpPacket
         
     | 
| 
      
 139 
     | 
    
         
            +
                attr_accessor :filename, :mode
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 142 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 143 
     | 
    
         
            +
                    @filename = nil
         
     | 
| 
      
 144 
     | 
    
         
            +
                    @mode = nil
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                # Encode of the packet based on the instance variables. Both the filename
         
     | 
| 
      
 148 
     | 
    
         
            +
                # and mode instance variables must be set or an exception will be thrown.
         
     | 
| 
      
 149 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 150 
     | 
    
         
            +
                    unless @opcode and @filename and @mode
         
     | 
| 
      
 151 
     | 
    
         
            +
                        raise ArgumentError, "Required arguments missing."
         
     | 
| 
      
 152 
     | 
    
         
            +
                    end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    datalist = []
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                    format = "n"
         
     | 
| 
      
 157 
     | 
    
         
            +
                    format += "a#{@filename.length}x"
         
     | 
| 
      
 158 
     | 
    
         
            +
                    datalist.push @opcode
         
     | 
| 
      
 159 
     | 
    
         
            +
                    datalist.push @filename
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                    case @mode
         
     | 
| 
      
 162 
     | 
    
         
            +
                    when "octet"
         
     | 
| 
      
 163 
     | 
    
         
            +
                        format += "a5"
         
     | 
| 
      
 164 
     | 
    
         
            +
                    else
         
     | 
| 
      
 165 
     | 
    
         
            +
                        raise ArgumentError, "Unsupported mode: #{kwargs[:mode]}"
         
     | 
| 
      
 166 
     | 
    
         
            +
                    end
         
     | 
| 
      
 167 
     | 
    
         
            +
                    datalist.push @mode
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                    format += "x"
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                    @options.each do |key, value|
         
     | 
| 
      
 172 
     | 
    
         
            +
                        format += "a#{key.length}x"
         
     | 
| 
      
 173 
     | 
    
         
            +
                        format += "a#{value.length}x"
         
     | 
| 
      
 174 
     | 
    
         
            +
                        datalist.push key
         
     | 
| 
      
 175 
     | 
    
         
            +
                        datalist.push value
         
     | 
| 
      
 176 
     | 
    
         
            +
                    end
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                    @buffer = datalist.pack(format)
         
     | 
| 
      
 179 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 180 
     | 
    
         
            +
                end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                # Decode the packet based on the contents of the buffer instance variable.
         
     | 
| 
      
 183 
     | 
    
         
            +
                # It populates the filename and mode instance variables.
         
     | 
| 
      
 184 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 185 
     | 
    
         
            +
                    unless @buffer
         
     | 
| 
      
 186 
     | 
    
         
            +
                        raise ArgumentError, "Can't decode, buffer is empty."
         
     | 
| 
      
 187 
     | 
    
         
            +
                    end
         
     | 
| 
      
 188 
     | 
    
         
            +
                    struct = @buffer.unpack("nZ*Z*")
         
     | 
| 
      
 189 
     | 
    
         
            +
                    unless struct[0] == 1 or struct[0] == 2
         
     | 
| 
      
 190 
     | 
    
         
            +
                        raise TftpError, "opcode #{struct[0]} is not a RRQ or WRQ!"
         
     | 
| 
      
 191 
     | 
    
         
            +
                    end
         
     | 
| 
      
 192 
     | 
    
         
            +
                    @filename = struct[1]
         
     | 
| 
      
 193 
     | 
    
         
            +
                    unless @filename.length > 0
         
     | 
| 
      
 194 
     | 
    
         
            +
                        raise TftpError, "filename is the null string"
         
     | 
| 
      
 195 
     | 
    
         
            +
                    end
         
     | 
| 
      
 196 
     | 
    
         
            +
                    @mode = struct[2]
         
     | 
| 
      
 197 
     | 
    
         
            +
                    unless valid_mode? @mode
         
     | 
| 
      
 198 
     | 
    
         
            +
                        raise TftpError, "mode #{@mode} is not valid"
         
     | 
| 
      
 199 
     | 
    
         
            +
                    end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                    # We need to find the point at which the opcode, filename and mode
         
     | 
| 
      
 202 
     | 
    
         
            +
                    # have ended and the options begin.
         
     | 
| 
      
 203 
     | 
    
         
            +
                    offset = 0
         
     | 
| 
      
 204 
     | 
    
         
            +
                    nulls = []
         
     | 
| 
      
 205 
     | 
    
         
            +
                    @buffer.each_byte do |c|
         
     | 
| 
      
 206 
     | 
    
         
            +
                        nulls.push offset if c == 0
         
     | 
| 
      
 207 
     | 
    
         
            +
                        offset += 1
         
     | 
| 
      
 208 
     | 
    
         
            +
                    end
         
     | 
| 
      
 209 
     | 
    
         
            +
                    # There should be at least 3, the 0 in the opcode, the terminator for
         
     | 
| 
      
 210 
     | 
    
         
            +
                    # the filename, and the terminator for the mode. If there are more,
         
     | 
| 
      
 211 
     | 
    
         
            +
                    # then there are options.
         
     | 
| 
      
 212 
     | 
    
         
            +
                    if nulls.length < 3
         
     | 
| 
      
 213 
     | 
    
         
            +
                        raise TftpError, "Failed to parse nulls looking for options"
         
     | 
| 
      
 214 
     | 
    
         
            +
                    elsif nulls.length > 3
         
     | 
| 
      
 215 
     | 
    
         
            +
                        lower_bound = nulls[2] + 1
         
     | 
| 
      
 216 
     | 
    
         
            +
                        @options = decode_options(@buffer[lower_bound..-1])
         
     | 
| 
      
 217 
     | 
    
         
            +
                    end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                protected
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                # This method is a boolean validator that returns true if the blocksize
         
     | 
| 
      
 225 
     | 
    
         
            +
                # passed is valid, and false otherwise.
         
     | 
| 
      
 226 
     | 
    
         
            +
                def valid_blocksize?(blksize)
         
     | 
| 
      
 227 
     | 
    
         
            +
                    blksize = blksize.to_i
         
     | 
| 
      
 228 
     | 
    
         
            +
                    if blksize >= 8 and blksize <= 65464
         
     | 
| 
      
 229 
     | 
    
         
            +
                        return true
         
     | 
| 
      
 230 
     | 
    
         
            +
                    else
         
     | 
| 
      
 231 
     | 
    
         
            +
                        return false
         
     | 
| 
      
 232 
     | 
    
         
            +
                    end
         
     | 
| 
      
 233 
     | 
    
         
            +
                end
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                # This method is a boolean validator that returns true of the mode passed
         
     | 
| 
      
 236 
     | 
    
         
            +
                # is valid, and false otherwise. The modes of 'netascii', 'octet' and
         
     | 
| 
      
 237 
     | 
    
         
            +
                # 'mail' are valid, even though only 'octet' is currently implemented.
         
     | 
| 
      
 238 
     | 
    
         
            +
                def valid_mode?(mode)
         
     | 
| 
      
 239 
     | 
    
         
            +
                    case mode
         
     | 
| 
      
 240 
     | 
    
         
            +
                    when "netascii", "octet", "mail"
         
     | 
| 
      
 241 
     | 
    
         
            +
                        return true
         
     | 
| 
      
 242 
     | 
    
         
            +
                    else
         
     | 
| 
      
 243 
     | 
    
         
            +
                        return false
         
     | 
| 
      
 244 
     | 
    
         
            +
                    end
         
     | 
| 
      
 245 
     | 
    
         
            +
                end
         
     | 
| 
      
 246 
     | 
    
         
            +
            end
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
            # The RRQ packet to request a download.
         
     | 
| 
      
 249 
     | 
    
         
            +
            class TftpPacketRRQ < TftpPacketInitial
         
     | 
| 
      
 250 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 251 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 252 
     | 
    
         
            +
                    @opcode = 1
         
     | 
| 
      
 253 
     | 
    
         
            +
                end
         
     | 
| 
      
 254 
     | 
    
         
            +
            end
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
            # The WRQ packet to request an upload.
         
     | 
| 
      
 257 
     | 
    
         
            +
            class TftpPacketWRQ < TftpPacketInitial
         
     | 
| 
      
 258 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 259 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 260 
     | 
    
         
            +
                    @opcode = 2
         
     | 
| 
      
 261 
     | 
    
         
            +
                end
         
     | 
| 
      
 262 
     | 
    
         
            +
            end
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
      
 264 
     | 
    
         
            +
            #          2 bytes    2 bytes       n bytes
         
     | 
| 
      
 265 
     | 
    
         
            +
            #          ---------------------------------
         
     | 
| 
      
 266 
     | 
    
         
            +
            #   DATA  | 03    |   Block #  |    Data    |
         
     | 
| 
      
 267 
     | 
    
         
            +
            #          ---------------------------------
         
     | 
| 
      
 268 
     | 
    
         
            +
            class TftpPacketDAT < TftpPacket
         
     | 
| 
      
 269 
     | 
    
         
            +
                attr_accessor :data, :buffer, :blocknumber
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 272 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 273 
     | 
    
         
            +
                    @opcode = 3
         
     | 
| 
      
 274 
     | 
    
         
            +
                    @blocknumber = 0
         
     | 
| 
      
 275 
     | 
    
         
            +
                    @data = nil
         
     | 
| 
      
 276 
     | 
    
         
            +
                    @buffer = nil
         
     | 
| 
      
 277 
     | 
    
         
            +
                end
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 280 
     | 
    
         
            +
                    unless @opcode and @blocknumber and @data
         
     | 
| 
      
 281 
     | 
    
         
            +
                        raise ArgumentError, "Required fields missing!"
         
     | 
| 
      
 282 
     | 
    
         
            +
                    end
         
     | 
| 
      
 283 
     | 
    
         
            +
                    # FIXME - check block size
         
     | 
| 
      
 284 
     | 
    
         
            +
                    #@buffer = [@opcode, @blocknumber, @data].pack('nnC#{@data.length}')
         
     | 
| 
      
 285 
     | 
    
         
            +
                    @buffer = [@opcode, @blocknumber].pack('nn')
         
     | 
| 
      
 286 
     | 
    
         
            +
                    @buffer += @data
         
     | 
| 
      
 287 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 288 
     | 
    
         
            +
                end
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 291 
     | 
    
         
            +
                    unless @buffer
         
     | 
| 
      
 292 
     | 
    
         
            +
                        raise ArgumentError, "Can't decode, buffer is empty."
         
     | 
| 
      
 293 
     | 
    
         
            +
                    end
         
     | 
| 
      
 294 
     | 
    
         
            +
                    struct = @buffer[0..3].unpack('nn')
         
     | 
| 
      
 295 
     | 
    
         
            +
                    unless struct[0] == 3
         
     | 
| 
      
 296 
     | 
    
         
            +
                        raise ArgumentError, "opcode #{struct[0]} is not a DAT!"
         
     | 
| 
      
 297 
     | 
    
         
            +
                    end
         
     | 
| 
      
 298 
     | 
    
         
            +
                    @blocknumber = struct[1]
         
     | 
| 
      
 299 
     | 
    
         
            +
                    @data = @buffer[4..-1]
         
     | 
| 
      
 300 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 301 
     | 
    
         
            +
                end
         
     | 
| 
      
 302 
     | 
    
         
            +
            end
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
            #         2 bytes    2 bytes
         
     | 
| 
      
 305 
     | 
    
         
            +
            #         -------------------
         
     | 
| 
      
 306 
     | 
    
         
            +
            #  ACK   | 04    |   Block #  |
         
     | 
| 
      
 307 
     | 
    
         
            +
            #         --------------------
         
     | 
| 
      
 308 
     | 
    
         
            +
            class TftpPacketACK < TftpPacket
         
     | 
| 
      
 309 
     | 
    
         
            +
                attr_accessor :blocknumber, :buffer
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
      
 311 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 312 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 313 
     | 
    
         
            +
                    @opcode = 4
         
     | 
| 
      
 314 
     | 
    
         
            +
                    @blocknumber = 0
         
     | 
| 
      
 315 
     | 
    
         
            +
                    @buffer = nil
         
     | 
| 
      
 316 
     | 
    
         
            +
                end
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 319 
     | 
    
         
            +
                    unless @blocknumber
         
     | 
| 
      
 320 
     | 
    
         
            +
                        raise ArgumentError, "blocknumber required"
         
     | 
| 
      
 321 
     | 
    
         
            +
                    end
         
     | 
| 
      
 322 
     | 
    
         
            +
                    @buffer = [@opcode, @blocknumber].pack('nn')
         
     | 
| 
      
 323 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 324 
     | 
    
         
            +
                end
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
      
 326 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 327 
     | 
    
         
            +
                    unless @buffer
         
     | 
| 
      
 328 
     | 
    
         
            +
                        raise ArgumentError, "Can't decode, buffer is empty."
         
     | 
| 
      
 329 
     | 
    
         
            +
                    end
         
     | 
| 
      
 330 
     | 
    
         
            +
                    struct = @buffer.unpack('nn')
         
     | 
| 
      
 331 
     | 
    
         
            +
                    unless struct[0] == 4
         
     | 
| 
      
 332 
     | 
    
         
            +
                        raise ArgumentError, "opcode #{struct[0]} is not an ACK!"
         
     | 
| 
      
 333 
     | 
    
         
            +
                    end
         
     | 
| 
      
 334 
     | 
    
         
            +
                    @blocknumber = struct[1]
         
     | 
| 
      
 335 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 336 
     | 
    
         
            +
                end
         
     | 
| 
      
 337 
     | 
    
         
            +
            end
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
            #          2 bytes  2 bytes        string    1 byte
         
     | 
| 
      
 340 
     | 
    
         
            +
            #          ----------------------------------------
         
     | 
| 
      
 341 
     | 
    
         
            +
            #  ERROR | 05    |  ErrorCode |   ErrMsg   |   0  |
         
     | 
| 
      
 342 
     | 
    
         
            +
            #          ----------------------------------------
         
     | 
| 
      
 343 
     | 
    
         
            +
            #      Error Codes
         
     | 
| 
      
 344 
     | 
    
         
            +
            # 
         
     | 
| 
      
 345 
     | 
    
         
            +
            #      Value     Meaning
         
     | 
| 
      
 346 
     | 
    
         
            +
            # 
         
     | 
| 
      
 347 
     | 
    
         
            +
            #      0         Not defined, see error message (if any).
         
     | 
| 
      
 348 
     | 
    
         
            +
            #      1         File not found.
         
     | 
| 
      
 349 
     | 
    
         
            +
            #      2         Access violation.
         
     | 
| 
      
 350 
     | 
    
         
            +
            #      3         Disk full or allocation exceeded.
         
     | 
| 
      
 351 
     | 
    
         
            +
            #      4         Illegal TFTP operation.
         
     | 
| 
      
 352 
     | 
    
         
            +
            #      5         Unknown transfer ID.
         
     | 
| 
      
 353 
     | 
    
         
            +
            #      6         File already exists.
         
     | 
| 
      
 354 
     | 
    
         
            +
            #      7         No such user.
         
     | 
| 
      
 355 
     | 
    
         
            +
            #      8         Failed negotiation
         
     | 
| 
      
 356 
     | 
    
         
            +
            class TftpPacketERR < TftpPacket
         
     | 
| 
      
 357 
     | 
    
         
            +
                attr_reader :extended_errmsg
         
     | 
| 
      
 358 
     | 
    
         
            +
                attr_accessor :errorcode, :errmsg, :buffer
         
     | 
| 
      
 359 
     | 
    
         
            +
                ErrMsgs = [
         
     | 
| 
      
 360 
     | 
    
         
            +
                    'Not defined, see error message (if any).',
         
     | 
| 
      
 361 
     | 
    
         
            +
                    'File not found.',
         
     | 
| 
      
 362 
     | 
    
         
            +
                    'Access violation.',
         
     | 
| 
      
 363 
     | 
    
         
            +
                    'Disk full or allocation exceeded.',
         
     | 
| 
      
 364 
     | 
    
         
            +
                    'Illegal TFTP operation.',
         
     | 
| 
      
 365 
     | 
    
         
            +
                    'Unknown transfer ID.',
         
     | 
| 
      
 366 
     | 
    
         
            +
                    'File already exists.',
         
     | 
| 
      
 367 
     | 
    
         
            +
                    'No such user.',
         
     | 
| 
      
 368 
     | 
    
         
            +
                    'Failed negotiation.'
         
     | 
| 
      
 369 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
      
 371 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 372 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 373 
     | 
    
         
            +
                    @opcode = 5
         
     | 
| 
      
 374 
     | 
    
         
            +
                    @errorcode = 0
         
     | 
| 
      
 375 
     | 
    
         
            +
                    @errmsg = nil
         
     | 
| 
      
 376 
     | 
    
         
            +
                    @extended_errmsg = nil
         
     | 
| 
      
 377 
     | 
    
         
            +
                    @buffer = nil
         
     | 
| 
      
 378 
     | 
    
         
            +
                end
         
     | 
| 
      
 379 
     | 
    
         
            +
             
     | 
| 
      
 380 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 381 
     | 
    
         
            +
                    unless @opcode and @errorcode
         
     | 
| 
      
 382 
     | 
    
         
            +
                        raise ArgumentError, "Required params missing."
         
     | 
| 
      
 383 
     | 
    
         
            +
                    end
         
     | 
| 
      
 384 
     | 
    
         
            +
                    @errmsg = ErrMsgs[@errorcode] unless @errmsg
         
     | 
| 
      
 385 
     | 
    
         
            +
                    format = 'nn' + "a#{@errmsg.length}" + 'x'
         
     | 
| 
      
 386 
     | 
    
         
            +
                    @buffer = [@opcode, @errorcode, @errmsg].pack(format)
         
     | 
| 
      
 387 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 388 
     | 
    
         
            +
                end
         
     | 
| 
      
 389 
     | 
    
         
            +
             
     | 
| 
      
 390 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 391 
     | 
    
         
            +
                    unless @buffer
         
     | 
| 
      
 392 
     | 
    
         
            +
                        raise ArgumentError, "Can't decode, buffer is empty."
         
     | 
| 
      
 393 
     | 
    
         
            +
                    end
         
     | 
| 
      
 394 
     | 
    
         
            +
                    struct = @buffer.unpack("nnZ*")
         
     | 
| 
      
 395 
     | 
    
         
            +
                    unless struct[0] == 5
         
     | 
| 
      
 396 
     | 
    
         
            +
                        raise ArgumentError, "opcode #{struct[0]} is not an ERR"
         
     | 
| 
      
 397 
     | 
    
         
            +
                    end
         
     | 
| 
      
 398 
     | 
    
         
            +
                    @errorcode = struct[1]
         
     | 
| 
      
 399 
     | 
    
         
            +
                    @errmsg = struct[2]
         
     | 
| 
      
 400 
     | 
    
         
            +
                    @extended_errmsg = ErrMsgs[@errorcode]
         
     | 
| 
      
 401 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 402 
     | 
    
         
            +
                end
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
            end
         
     | 
| 
      
 405 
     | 
    
         
            +
             
     | 
| 
      
 406 
     | 
    
         
            +
            #  +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
         
     | 
| 
      
 407 
     | 
    
         
            +
            #  |  opc  |  opt1  | 0 | value1 | 0 |  optN  | 0 | valueN | 0 |
         
     | 
| 
      
 408 
     | 
    
         
            +
            #  +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
         
     | 
| 
      
 409 
     | 
    
         
            +
            class TftpPacketOACK < TftpPacket
         
     | 
| 
      
 410 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 411 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 412 
     | 
    
         
            +
                    @opcode = 6
         
     | 
| 
      
 413 
     | 
    
         
            +
                end
         
     | 
| 
      
 414 
     | 
    
         
            +
             
     | 
| 
      
 415 
     | 
    
         
            +
                def encode
         
     | 
| 
      
 416 
     | 
    
         
            +
                    datalist = [@opcode]
         
     | 
| 
      
 417 
     | 
    
         
            +
                    format = 'n'
         
     | 
| 
      
 418 
     | 
    
         
            +
                    options.each do |key, val|
         
     | 
| 
      
 419 
     | 
    
         
            +
                        format += "a#{key.to_s.length}x"
         
     | 
| 
      
 420 
     | 
    
         
            +
                        format += "a#{val.to_s.length}x"
         
     | 
| 
      
 421 
     | 
    
         
            +
                        datalist.push key
         
     | 
| 
      
 422 
     | 
    
         
            +
                        datalist.push val
         
     | 
| 
      
 423 
     | 
    
         
            +
                    end
         
     | 
| 
      
 424 
     | 
    
         
            +
                    @buffer = datalist.pack(format)
         
     | 
| 
      
 425 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 426 
     | 
    
         
            +
                end
         
     | 
| 
      
 427 
     | 
    
         
            +
             
     | 
| 
      
 428 
     | 
    
         
            +
                def decode
         
     | 
| 
      
 429 
     | 
    
         
            +
                    opcode = @buffer[0..1].unpack('n')[0]
         
     | 
| 
      
 430 
     | 
    
         
            +
                    unless opcode == @opcode
         
     | 
| 
      
 431 
     | 
    
         
            +
                        raise ArgumentError, "opcode #{opcode} is not an OACK"
         
     | 
| 
      
 432 
     | 
    
         
            +
                    end
         
     | 
| 
      
 433 
     | 
    
         
            +
             
     | 
| 
      
 434 
     | 
    
         
            +
                    @options = decode_options(@buffer[2..-1])
         
     | 
| 
      
 435 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 436 
     | 
    
         
            +
                end
         
     | 
| 
      
 437 
     | 
    
         
            +
            end
         
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
      
 439 
     | 
    
         
            +
            class TftpPacketFactory
         
     | 
| 
      
 440 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 441 
     | 
    
         
            +
                end
         
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
      
 443 
     | 
    
         
            +
                def create(opcode)
         
     | 
| 
      
 444 
     | 
    
         
            +
                    return case opcode
         
     | 
| 
      
 445 
     | 
    
         
            +
                        when 1 then TftpPacketRRQ
         
     | 
| 
      
 446 
     | 
    
         
            +
                        when 2 then TftpPacketWRQ
         
     | 
| 
      
 447 
     | 
    
         
            +
                        when 3 then TftpPacketDAT
         
     | 
| 
      
 448 
     | 
    
         
            +
                        when 4 then TftpPacketACK
         
     | 
| 
      
 449 
     | 
    
         
            +
                        when 5 then TftpPacketERR
         
     | 
| 
      
 450 
     | 
    
         
            +
                        when 6 then TftpPacketOACK
         
     | 
| 
      
 451 
     | 
    
         
            +
                        else raise ArgumentError, "Unsupported opcode: #{opcode}"
         
     | 
| 
      
 452 
     | 
    
         
            +
                    end.new
         
     | 
| 
      
 453 
     | 
    
         
            +
                end
         
     | 
| 
      
 454 
     | 
    
         
            +
             
     | 
| 
      
 455 
     | 
    
         
            +
                def parse(buffer)
         
     | 
| 
      
 456 
     | 
    
         
            +
                    unless buffer
         
     | 
| 
      
 457 
     | 
    
         
            +
                        raise ArgumentError, "buffer cannot be empty"
         
     | 
| 
      
 458 
     | 
    
         
            +
                    end
         
     | 
| 
      
 459 
     | 
    
         
            +
                    opcode = buffer[0..1].unpack('n')[0]
         
     | 
| 
      
 460 
     | 
    
         
            +
                    packet = create(opcode)
         
     | 
| 
      
 461 
     | 
    
         
            +
                    packet.buffer = buffer
         
     | 
| 
      
 462 
     | 
    
         
            +
                    packet.decode
         
     | 
| 
      
 463 
     | 
    
         
            +
                    return packet
         
     | 
| 
      
 464 
     | 
    
         
            +
                end
         
     | 
| 
      
 465 
     | 
    
         
            +
            end
         
     | 
| 
      
 466 
     | 
    
         
            +
             
     | 
| 
      
 467 
     | 
    
         
            +
            class TftpSession
         
     | 
| 
      
 468 
     | 
    
         
            +
                attr_accessor :options, :state
         
     | 
| 
      
 469 
     | 
    
         
            +
                attr_reader :dups, :errors
         
     | 
| 
      
 470 
     | 
    
         
            +
             
     | 
| 
      
 471 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 472 
     | 
    
         
            +
                    # Agreed upon session options
         
     | 
| 
      
 473 
     | 
    
         
            +
                    @options = {}
         
     | 
| 
      
 474 
     | 
    
         
            +
                    # State of the session, can be one of
         
     | 
| 
      
 475 
     | 
    
         
            +
                    # nil   - No state yet
         
     | 
| 
      
 476 
     | 
    
         
            +
                    # :rrq  - Just sent rrq, waiting for response
         
     | 
| 
      
 477 
     | 
    
         
            +
                    # :wrq  - Just sent wrq, waiting for response
         
     | 
| 
      
 478 
     | 
    
         
            +
                    # :dat  - transferring data
         
     | 
| 
      
 479 
     | 
    
         
            +
                    # :oack - Received oack, negotiating options
         
     | 
| 
      
 480 
     | 
    
         
            +
                    # :ack  - Acknowledged oack, waiting for response
         
     | 
| 
      
 481 
     | 
    
         
            +
                    # :err  - Fatal problems, giving up
         
     | 
| 
      
 482 
     | 
    
         
            +
                    # :done - Session is over, file transferred
         
     | 
| 
      
 483 
     | 
    
         
            +
                    @state = nil
         
     | 
| 
      
 484 
     | 
    
         
            +
                    @dups = 0
         
     | 
| 
      
 485 
     | 
    
         
            +
                    @errors = 0
         
     | 
| 
      
 486 
     | 
    
         
            +
                    @blksize = DefBlkSize
         
     | 
| 
      
 487 
     | 
    
         
            +
                end
         
     | 
| 
      
 488 
     | 
    
         
            +
            end
         
     | 
| 
      
 489 
     | 
    
         
            +
             
     | 
| 
      
 490 
     | 
    
         
            +
            class TftpServer < TftpSession
         
     | 
| 
      
 491 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 492 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 493 
     | 
    
         
            +
                    @iface = nil
         
     | 
| 
      
 494 
     | 
    
         
            +
                    @port = nil
         
     | 
| 
      
 495 
     | 
    
         
            +
                    @root = nil
         
     | 
| 
      
 496 
     | 
    
         
            +
                    @sessions = []
         
     | 
| 
      
 497 
     | 
    
         
            +
                end
         
     | 
| 
      
 498 
     | 
    
         
            +
             
     | 
| 
      
 499 
     | 
    
         
            +
                # This method starts a server listening on a given port, to serve up files
         
     | 
| 
      
 500 
     | 
    
         
            +
                # at a given path. It takes an optional ip to bind to, which defaults to
         
     | 
| 
      
 501 
     | 
    
         
            +
                # localhost (127.0.0.1).
         
     | 
| 
      
 502 
     | 
    
         
            +
                def listen(port, path, iface="127.0.0.1")
         
     | 
| 
      
 503 
     | 
    
         
            +
                    @iface = iface
         
     | 
| 
      
 504 
     | 
    
         
            +
                    @port = port
         
     | 
| 
      
 505 
     | 
    
         
            +
                    @root = path
         
     | 
| 
      
 506 
     | 
    
         
            +
                    sock = UDPSocket.new
         
     | 
| 
      
 507 
     | 
    
         
            +
                    sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
         
     | 
| 
      
 508 
     | 
    
         
            +
                    #sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, SockTimeout)
         
     | 
| 
      
 509 
     | 
    
         
            +
                    sock.bind(iface, port)
         
     | 
| 
      
 510 
     | 
    
         
            +
                    $tftplog.info('tftp+') { "Bound to #{iface} on port #{port}" }
         
     | 
| 
      
 511 
     | 
    
         
            +
             
     | 
| 
      
 512 
     | 
    
         
            +
                    factory = TftpPacketFactory.new
         
     | 
| 
      
 513 
     | 
    
         
            +
                    retry_count = 0
         
     | 
| 
      
 514 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 515 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "Waiting for incoming datagram..." }
         
     | 
| 
      
 516 
     | 
    
         
            +
                        msg = sender = nil
         
     | 
| 
      
 517 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 518 
     | 
    
         
            +
                            status = Timeout::timeout(SockTimeout) {
         
     | 
| 
      
 519 
     | 
    
         
            +
                                msg, sender = sock.recvfrom(MaxBlkSize)
         
     | 
| 
      
 520 
     | 
    
         
            +
                            }
         
     | 
| 
      
 521 
     | 
    
         
            +
                        rescue Timeout::Error => details
         
     | 
| 
      
 522 
     | 
    
         
            +
                            retry_count += 1
         
     | 
| 
      
 523 
     | 
    
         
            +
                            if retry_count > MaxRetry
         
     | 
| 
      
 524 
     | 
    
         
            +
                                msg = "Timeout! Max retries exceeded. Giving up."
         
     | 
| 
      
 525 
     | 
    
         
            +
                                $tftplog.error('tftp+') { msg }
         
     | 
| 
      
 526 
     | 
    
         
            +
                                raise TftpError, msg
         
     | 
| 
      
 527 
     | 
    
         
            +
                            else
         
     | 
| 
      
 528 
     | 
    
         
            +
                                $tftplog.warn('tftp+') { "Timeout! Lets try again." }
         
     | 
| 
      
 529 
     | 
    
         
            +
                                next
         
     | 
| 
      
 530 
     | 
    
         
            +
                            end
         
     | 
| 
      
 531 
     | 
    
         
            +
                        end
         
     | 
| 
      
 532 
     | 
    
         
            +
                        prot, rport, rhost, rip = sender
         
     | 
| 
      
 533 
     | 
    
         
            +
             
     | 
| 
      
 534 
     | 
    
         
            +
                        pkt = factory.parse(msg)
         
     | 
| 
      
 535 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "pkt is #{pkt}" }
         
     | 
| 
      
 536 
     | 
    
         
            +
             
     | 
| 
      
 537 
     | 
    
         
            +
                        key = "#{rip}-#{rport}"
         
     | 
| 
      
 538 
     | 
    
         
            +
                        handler = nil
         
     | 
| 
      
 539 
     | 
    
         
            +
                        unless @sessions.has_key? key
         
     | 
| 
      
 540 
     | 
    
         
            +
                            handler = TftpServerHandler.new(rhost, rport, key, root)
         
     | 
| 
      
 541 
     | 
    
         
            +
                        else
         
     | 
| 
      
 542 
     | 
    
         
            +
                            handler = @sessions[key]
         
     | 
| 
      
 543 
     | 
    
         
            +
                        end
         
     | 
| 
      
 544 
     | 
    
         
            +
                        handler.handle(pkt)
         
     | 
| 
      
 545 
     | 
    
         
            +
                    end
         
     | 
| 
      
 546 
     | 
    
         
            +
                end
         
     | 
| 
      
 547 
     | 
    
         
            +
            end
         
     | 
| 
      
 548 
     | 
    
         
            +
             
     | 
| 
      
 549 
     | 
    
         
            +
            class TftpServerHandler < TftpSession
         
     | 
| 
      
 550 
     | 
    
         
            +
                def initialize(rhost, rport, key, root)
         
     | 
| 
      
 551 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 552 
     | 
    
         
            +
                    @host = rhost
         
     | 
| 
      
 553 
     | 
    
         
            +
                    @port = rport
         
     | 
| 
      
 554 
     | 
    
         
            +
                    @key = key
         
     | 
| 
      
 555 
     | 
    
         
            +
                    @root = root
         
     | 
| 
      
 556 
     | 
    
         
            +
                end
         
     | 
| 
      
 557 
     | 
    
         
            +
             
     | 
| 
      
 558 
     | 
    
         
            +
                def handle(pkt)
         
     | 
| 
      
 559 
     | 
    
         
            +
                end
         
     | 
| 
      
 560 
     | 
    
         
            +
            end
         
     | 
| 
      
 561 
     | 
    
         
            +
             
     | 
| 
      
 562 
     | 
    
         
            +
            class TftpClient < TftpSession
         
     | 
| 
      
 563 
     | 
    
         
            +
                attr_reader :host, :port
         
     | 
| 
      
 564 
     | 
    
         
            +
             
     | 
| 
      
 565 
     | 
    
         
            +
                def initialize(host, port)
         
     | 
| 
      
 566 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 567 
     | 
    
         
            +
                    @host = host
         
     | 
| 
      
 568 
     | 
    
         
            +
                    # Force the port to a string type.
         
     | 
| 
      
 569 
     | 
    
         
            +
                    @iport = port.to_s
         
     | 
| 
      
 570 
     | 
    
         
            +
                    @port = nil
         
     | 
| 
      
 571 
     | 
    
         
            +
                    # FIXME - move host and port args to download method
         
     | 
| 
      
 572 
     | 
    
         
            +
             
     | 
| 
      
 573 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 574 
     | 
    
         
            +
                        @address = Resolv::IPv4.create(@host)
         
     | 
| 
      
 575 
     | 
    
         
            +
                    rescue ArgumentError => details
         
     | 
| 
      
 576 
     | 
    
         
            +
                        # So, @host doesn't look like an IP. Resolve it.
         
     | 
| 
      
 577 
     | 
    
         
            +
                        # A Resolv::ResolvError exception could be raised here, let it
         
     | 
| 
      
 578 
     | 
    
         
            +
                        # filter up.
         
     | 
| 
      
 579 
     | 
    
         
            +
                        @address = Resolv::DNS.new.getaddress(@host)
         
     | 
| 
      
 580 
     | 
    
         
            +
                    end
         
     | 
| 
      
 581 
     | 
    
         
            +
                end
         
     | 
| 
      
 582 
     | 
    
         
            +
             
     | 
| 
      
 583 
     | 
    
         
            +
                # FIXME - this method is too big
         
     | 
| 
      
 584 
     | 
    
         
            +
                def download(filename, output, options={})
         
     | 
| 
      
 585 
     | 
    
         
            +
                    @blksize = options[:blksize] if options.has_key? :blksize
         
     | 
| 
      
 586 
     | 
    
         
            +
                    $tftplog.debug('tftp+') { "Opening output file #{output}" }
         
     | 
| 
      
 587 
     | 
    
         
            +
                    fout = File.open(output, "w")
         
     | 
| 
      
 588 
     | 
    
         
            +
                    sock = UDPSocket.new
         
     | 
| 
      
 589 
     | 
    
         
            +
                   
         
     | 
| 
      
 590 
     | 
    
         
            +
                    pkt = TftpPacketRRQ.new
         
     | 
| 
      
 591 
     | 
    
         
            +
                    pkt.filename = filename
         
     | 
| 
      
 592 
     | 
    
         
            +
                    pkt.mode = 'octet' # FIXME - shouldn't hardcode this
         
     | 
| 
      
 593 
     | 
    
         
            +
                    pkt.options = options
         
     | 
| 
      
 594 
     | 
    
         
            +
                    $tftplog.info('tftp+') { "Sending download request for #{filename}" }
         
     | 
| 
      
 595 
     | 
    
         
            +
                    $tftplog.info('tftp+') { "host = #{@host}, port = #{@iport}" }
         
     | 
| 
      
 596 
     | 
    
         
            +
                    sock.send(pkt.encode.buffer, 0, @host, @iport)
         
     | 
| 
      
 597 
     | 
    
         
            +
                    @state = :rrq
         
     | 
| 
      
 598 
     | 
    
         
            +
             
     | 
| 
      
 599 
     | 
    
         
            +
                    factory = TftpPacketFactory.new
         
     | 
| 
      
 600 
     | 
    
         
            +
             
     | 
| 
      
 601 
     | 
    
         
            +
                    blocknumber = 1
         
     | 
| 
      
 602 
     | 
    
         
            +
                    retry_count = 0
         
     | 
| 
      
 603 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 604 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "Waiting for incoming datagram..." }
         
     | 
| 
      
 605 
     | 
    
         
            +
                        msg = sender = nil
         
     | 
| 
      
 606 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 607 
     | 
    
         
            +
                            status = Timeout::timeout(SockTimeout) {
         
     | 
| 
      
 608 
     | 
    
         
            +
                                msg, sender = sock.recvfrom(MaxBlkSize)
         
     | 
| 
      
 609 
     | 
    
         
            +
                            }
         
     | 
| 
      
 610 
     | 
    
         
            +
                        rescue Timeout::Error => details
         
     | 
| 
      
 611 
     | 
    
         
            +
                            retry_count += 1
         
     | 
| 
      
 612 
     | 
    
         
            +
                            if retry_count > MaxRetry
         
     | 
| 
      
 613 
     | 
    
         
            +
                                msg = "Timeout! Max retries exceeded. Giving up."
         
     | 
| 
      
 614 
     | 
    
         
            +
                                $tftplog.error('tftp+') { msg }
         
     | 
| 
      
 615 
     | 
    
         
            +
                                raise TftpError, msg
         
     | 
| 
      
 616 
     | 
    
         
            +
                            else
         
     | 
| 
      
 617 
     | 
    
         
            +
                                $tftplog.debug('tftp+') { "Timeout! Lets try again." }
         
     | 
| 
      
 618 
     | 
    
         
            +
                                next
         
     | 
| 
      
 619 
     | 
    
         
            +
                            end
         
     | 
| 
      
 620 
     | 
    
         
            +
                        end
         
     | 
| 
      
 621 
     | 
    
         
            +
                        prot, rport, rhost, rip = sender
         
     | 
| 
      
 622 
     | 
    
         
            +
                        $tftplog.info('tftp+') { "Received #{msg.length} byte packet" }
         
     | 
| 
      
 623 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "Remote port is #{rport} and remote host is #{rhost}" }
         
     | 
| 
      
 624 
     | 
    
         
            +
             
     | 
| 
      
 625 
     | 
    
         
            +
                        if @address.to_s != rip
         
     | 
| 
      
 626 
     | 
    
         
            +
                            # Skip it
         
     | 
| 
      
 627 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 628 
     | 
    
         
            +
                            $stderr.write "It is a rogue packet! #{sender[1]} #{sender[2]}\n"
         
     | 
| 
      
 629 
     | 
    
         
            +
                            next
         
     | 
| 
      
 630 
     | 
    
         
            +
                        elsif @port and @port != rport.to_s
         
     | 
| 
      
 631 
     | 
    
         
            +
                            # Skip it
         
     | 
| 
      
 632 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 633 
     | 
    
         
            +
                            $stderr.write "It is a rogue packet! #{sender[1]} #{sender[2]}\n"
         
     | 
| 
      
 634 
     | 
    
         
            +
                            next
         
     | 
| 
      
 635 
     | 
    
         
            +
                        else not @port
         
     | 
| 
      
 636 
     | 
    
         
            +
                            # Set this as our TID
         
     | 
| 
      
 637 
     | 
    
         
            +
                            $tftplog.info('tftp+') { "Set remote TID to #{@port}" }
         
     | 
| 
      
 638 
     | 
    
         
            +
                            @port = rport.to_s
         
     | 
| 
      
 639 
     | 
    
         
            +
                        end
         
     | 
| 
      
 640 
     | 
    
         
            +
             
     | 
| 
      
 641 
     | 
    
         
            +
                        pkt = factory.parse(msg)
         
     | 
| 
      
 642 
     | 
    
         
            +
                        $tftplog.debug('tftp+') { "pkt is #{pkt}" }
         
     | 
| 
      
 643 
     | 
    
         
            +
             
     | 
| 
      
 644 
     | 
    
         
            +
                        # FIXME - Refactor this into separate methods to handle each case.
         
     | 
| 
      
 645 
     | 
    
         
            +
                        if pkt.is_a? TftpPacketRRQ
         
     | 
| 
      
 646 
     | 
    
         
            +
                            # Skip it, but info('tftp+')rm the sender.
         
     | 
| 
      
 647 
     | 
    
         
            +
                            err = TftpPacketERR.new
         
     | 
| 
      
 648 
     | 
    
         
            +
                            err.errorcode = 4 # illegal op
         
     | 
| 
      
 649 
     | 
    
         
            +
                            sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 650 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 651 
     | 
    
         
            +
                            $stderr.write "It is a RRQ packet in download, state #{@state}\n"
         
     | 
| 
      
 652 
     | 
    
         
            +
             
     | 
| 
      
 653 
     | 
    
         
            +
                        elsif pkt.is_a? TftpPacketWRQ
         
     | 
| 
      
 654 
     | 
    
         
            +
                            # Skip it, but info('tftp+')rm the sender.
         
     | 
| 
      
 655 
     | 
    
         
            +
                            err = TftpPacketERR.new
         
     | 
| 
      
 656 
     | 
    
         
            +
                            err.errorcode = 4 # illegal op
         
     | 
| 
      
 657 
     | 
    
         
            +
                            sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 658 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 659 
     | 
    
         
            +
                            $stderr.write "It is a WRQ packet in download, state #{@state}\n"
         
     | 
| 
      
 660 
     | 
    
         
            +
             
     | 
| 
      
 661 
     | 
    
         
            +
                        elsif pkt.is_a? TftpPacketACK
         
     | 
| 
      
 662 
     | 
    
         
            +
                            # Skip it, but info('tftp+')rm the sender.
         
     | 
| 
      
 663 
     | 
    
         
            +
                            err = TftpPacketERR.new
         
     | 
| 
      
 664 
     | 
    
         
            +
                            err.errorcode = 4 # illegal op
         
     | 
| 
      
 665 
     | 
    
         
            +
                            sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 666 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 667 
     | 
    
         
            +
                            $stderr.write "It is a ACK packet in download, state #{@state}\n"
         
     | 
| 
      
 668 
     | 
    
         
            +
             
     | 
| 
      
 669 
     | 
    
         
            +
                        elsif pkt.is_a? TftpPacketERR
         
     | 
| 
      
 670 
     | 
    
         
            +
                            @errors += 1
         
     | 
| 
      
 671 
     | 
    
         
            +
                            raise TftpError, "ERR packet: #{pkt.errmsg}"
         
     | 
| 
      
 672 
     | 
    
         
            +
             
     | 
| 
      
 673 
     | 
    
         
            +
                        elsif pkt.is_a? TftpPacketOACK
         
     | 
| 
      
 674 
     | 
    
         
            +
                            unless @state == :rrq
         
     | 
| 
      
 675 
     | 
    
         
            +
                                @errors += 1
         
     | 
| 
      
 676 
     | 
    
         
            +
                                $stderr.write "It is a OACK in state #{@state}"
         
     | 
| 
      
 677 
     | 
    
         
            +
                                next
         
     | 
| 
      
 678 
     | 
    
         
            +
                            end
         
     | 
| 
      
 679 
     | 
    
         
            +
             
     | 
| 
      
 680 
     | 
    
         
            +
                            @state = :oack
         
     | 
| 
      
 681 
     | 
    
         
            +
                            # Are the acknowledged options the same as ours?
         
     | 
| 
      
 682 
     | 
    
         
            +
                            # FIXME - factor this into the OACK class?
         
     | 
| 
      
 683 
     | 
    
         
            +
                            if pkt.options
         
     | 
| 
      
 684 
     | 
    
         
            +
                                pkt.options do |optname, optval|
         
     | 
| 
      
 685 
     | 
    
         
            +
                                    case optname
         
     | 
| 
      
 686 
     | 
    
         
            +
                                    when :blksize
         
     | 
| 
      
 687 
     | 
    
         
            +
                                        # The blocksize can be <= what we proposed.
         
     | 
| 
      
 688 
     | 
    
         
            +
                                        unless options.has_key? :blksize
         
     | 
| 
      
 689 
     | 
    
         
            +
                                            # Hey, we didn't ask for a blocksize option...
         
     | 
| 
      
 690 
     | 
    
         
            +
                                            err = TftpPacketERR.new
         
     | 
| 
      
 691 
     | 
    
         
            +
                                            err.errorcode = 8 # failed negotiation
         
     | 
| 
      
 692 
     | 
    
         
            +
                                            sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 693 
     | 
    
         
            +
                                            raise TftpError, "It is a OACK with blocksize when we didn't ask for one."
         
     | 
| 
      
 694 
     | 
    
         
            +
                                        end
         
     | 
| 
      
 695 
     | 
    
         
            +
             
     | 
| 
      
 696 
     | 
    
         
            +
                                        if optval <= options[:blksize] and optval >= MinBlkSize
         
     | 
| 
      
 697 
     | 
    
         
            +
                                            # Valid. Lets use it.
         
     | 
| 
      
 698 
     | 
    
         
            +
                                            options[:blksize] = optval
         
     | 
| 
      
 699 
     | 
    
         
            +
                                        end
         
     | 
| 
      
 700 
     | 
    
         
            +
                                    else
         
     | 
| 
      
 701 
     | 
    
         
            +
                                        # FIXME - refactor err packet handling from above...
         
     | 
| 
      
 702 
     | 
    
         
            +
                                        # Nothing that we don't know of should be in the
         
     | 
| 
      
 703 
     | 
    
         
            +
                                        # oack packet.
         
     | 
| 
      
 704 
     | 
    
         
            +
                                        err = TftpPacketERR.new
         
     | 
| 
      
 705 
     | 
    
         
            +
                                        err.errorcode = 8 # failed negotiation
         
     | 
| 
      
 706 
     | 
    
         
            +
                                        sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 707 
     | 
    
         
            +
                                        raise TftpError, "Failed to negotiate options: #{pkt.options}"
         
     | 
| 
      
 708 
     | 
    
         
            +
                                    end
         
     | 
| 
      
 709 
     | 
    
         
            +
                                end
         
     | 
| 
      
 710 
     | 
    
         
            +
                                # SUCCESSFUL NEGOTIATION
         
     | 
| 
      
 711 
     | 
    
         
            +
                                # If we're here, then we're happy with the options in the
         
     | 
| 
      
 712 
     | 
    
         
            +
                                # OACK. Send an ACK of block 0 to ACK the OACK.
         
     | 
| 
      
 713 
     | 
    
         
            +
                                # FIXME - further negotiation required here?
         
     | 
| 
      
 714 
     | 
    
         
            +
                                ack = TftpPacketACK.new
         
     | 
| 
      
 715 
     | 
    
         
            +
                                ack.blocknumber = 0
         
     | 
| 
      
 716 
     | 
    
         
            +
                                sock.send(ack.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 717 
     | 
    
         
            +
                                @state = :ack
         
     | 
| 
      
 718 
     | 
    
         
            +
                            else
         
     | 
| 
      
 719 
     | 
    
         
            +
                                # OACK with no options?
         
     | 
| 
      
 720 
     | 
    
         
            +
                                err = TftpPacketERR.new
         
     | 
| 
      
 721 
     | 
    
         
            +
                                err.errorcode = 8 # failed negotiation
         
     | 
| 
      
 722 
     | 
    
         
            +
                                sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 723 
     | 
    
         
            +
                                raise TftpError, "OACK with no options"
         
     | 
| 
      
 724 
     | 
    
         
            +
                            end
         
     | 
| 
      
 725 
     | 
    
         
            +
             
     | 
| 
      
 726 
     | 
    
         
            +
                            # Done parsing. If we didn't raise an exception, then we need
         
     | 
| 
      
 727 
     | 
    
         
            +
                            # to send an ACK to the server, with block number 0.
         
     | 
| 
      
 728 
     | 
    
         
            +
                            ack = TftpPacketACK.new
         
     | 
| 
      
 729 
     | 
    
         
            +
                            ack.blocknumber = 0
         
     | 
| 
      
 730 
     | 
    
         
            +
                            $tftplog.info('tftp+') { "Sending ACK to OACK" }
         
     | 
| 
      
 731 
     | 
    
         
            +
                            sock.send(ack.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 732 
     | 
    
         
            +
                            @state = :ack
         
     | 
| 
      
 733 
     | 
    
         
            +
             
     | 
| 
      
 734 
     | 
    
         
            +
                        elsif pkt.is_a? TftpPacketDAT
         
     | 
| 
      
 735 
     | 
    
         
            +
                            # If the state is :rrq, and we sent options, then the
         
     | 
| 
      
 736 
     | 
    
         
            +
                            # server didn't send us an oack, and the options were refused.
         
     | 
| 
      
 737 
     | 
    
         
            +
                            # FIXME - we need to handle all possible options and set them
         
     | 
| 
      
 738 
     | 
    
         
            +
                            # back to their defaults here, not just blocksize.
         
     | 
| 
      
 739 
     | 
    
         
            +
                            if @state == :rrq and options.has_key? :blksize
         
     | 
| 
      
 740 
     | 
    
         
            +
                                @blksize = DefBlkSize
         
     | 
| 
      
 741 
     | 
    
         
            +
                            end
         
     | 
| 
      
 742 
     | 
    
         
            +
             
     | 
| 
      
 743 
     | 
    
         
            +
                            @state = :dat
         
     | 
| 
      
 744 
     | 
    
         
            +
                            $tftplog.info('tftp+') { "It is a DAT packet, block #{pkt.blocknumber}" }
         
     | 
| 
      
 745 
     | 
    
         
            +
                            $tftplog.debug('tftp+') { "DAT size is #{pkt.data.length}" }
         
     | 
| 
      
 746 
     | 
    
         
            +
             
     | 
| 
      
 747 
     | 
    
         
            +
                            ack = TftpPacketACK.new
         
     | 
| 
      
 748 
     | 
    
         
            +
                            ack.blocknumber = pkt.blocknumber
         
     | 
| 
      
 749 
     | 
    
         
            +
             
     | 
| 
      
 750 
     | 
    
         
            +
                            $tftplog.info('tftp+') { "Sending ACK to block #{ack.blocknumber}" }
         
     | 
| 
      
 751 
     | 
    
         
            +
                            sock.send(ack.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 752 
     | 
    
         
            +
             
     | 
| 
      
 753 
     | 
    
         
            +
                            # Check for dups
         
     | 
| 
      
 754 
     | 
    
         
            +
                            if pkt.blocknumber <= blocknumber
         
     | 
| 
      
 755 
     | 
    
         
            +
                                $tftplog.warn('tftp+') { "It is a DUP for block #{blocknumber}" }
         
     | 
| 
      
 756 
     | 
    
         
            +
                                @dups += 1
         
     | 
| 
      
 757 
     | 
    
         
            +
                            elsif pkt.blocknumber = blocknumber+1
         
     | 
| 
      
 758 
     | 
    
         
            +
                                $tftplog.debug('tftp+') { "It is a properly ordered DAT packet" }
         
     | 
| 
      
 759 
     | 
    
         
            +
                                blocknumber += 1
         
     | 
| 
      
 760 
     | 
    
         
            +
                            else
         
     | 
| 
      
 761 
     | 
    
         
            +
                                # Skip it, but info('tftp+')rm the sender.
         
     | 
| 
      
 762 
     | 
    
         
            +
                                err = TftpPacketERR.new
         
     | 
| 
      
 763 
     | 
    
         
            +
                                err.errorcode = 4 # illegal op
         
     | 
| 
      
 764 
     | 
    
         
            +
                                sock.send(err.encode.buffer, 0, @host, @port)
         
     | 
| 
      
 765 
     | 
    
         
            +
                                @errors += 1
         
     | 
| 
      
 766 
     | 
    
         
            +
                                $stderr.write "It is a future packet!\n"
         
     | 
| 
      
 767 
     | 
    
         
            +
                            end
         
     | 
| 
      
 768 
     | 
    
         
            +
             
     | 
| 
      
 769 
     | 
    
         
            +
                            # Call any block passed.
         
     | 
| 
      
 770 
     | 
    
         
            +
                            if block_given?
         
     | 
| 
      
 771 
     | 
    
         
            +
                                yield pkt
         
     | 
| 
      
 772 
     | 
    
         
            +
                            end
         
     | 
| 
      
 773 
     | 
    
         
            +
             
     | 
| 
      
 774 
     | 
    
         
            +
                            # Write the data to the file.
         
     | 
| 
      
 775 
     | 
    
         
            +
                            fout.print pkt.data
         
     | 
| 
      
 776 
     | 
    
         
            +
                            # If the size is less than our blocksize, we're done.
         
     | 
| 
      
 777 
     | 
    
         
            +
                            $tftplog.debug('tftp+') { "pkt.data.length is #{pkt.data.length}" }
         
     | 
| 
      
 778 
     | 
    
         
            +
                            if pkt.data.length < @blksize
         
     | 
| 
      
 779 
     | 
    
         
            +
                                $tftplog.info('tftp+') { "It is a last packet." }
         
     | 
| 
      
 780 
     | 
    
         
            +
                                fout.close
         
     | 
| 
      
 781 
     | 
    
         
            +
                                @state = :done
         
     | 
| 
      
 782 
     | 
    
         
            +
                                break
         
     | 
| 
      
 783 
     | 
    
         
            +
                            end
         
     | 
| 
      
 784 
     | 
    
         
            +
                        else
         
     | 
| 
      
 785 
     | 
    
         
            +
                            msg = "It is an unknown packet: #{pkt}"
         
     | 
| 
      
 786 
     | 
    
         
            +
                            $tftplog.error('tftp+') { msg }
         
     | 
| 
      
 787 
     | 
    
         
            +
                            raise TftpError, msg
         
     | 
| 
      
 788 
     | 
    
         
            +
                        end
         
     | 
| 
      
 789 
     | 
    
         
            +
                    end
         
     | 
| 
      
 790 
     | 
    
         
            +
                end
         
     | 
| 
      
 791 
     | 
    
         
            +
            end
         
     | 
| 
      
 792 
     | 
    
         
            +
             
     | 
| 
      
 793 
     | 
    
         
            +
            # If invoked directly...
         
     | 
| 
      
 794 
     | 
    
         
            +
            if __FILE__ == $0
         
     | 
| 
      
 795 
     | 
    
         
            +
                # Simple client maybe?
         
     | 
| 
      
 796 
     | 
    
         
            +
            end
         
     | 
    
        data/test/test.rb
    ADDED
    
    | 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'net/tftp+'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'test/unit'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            class TestTftp < Test::Unit::TestCase
         
     | 
| 
      
 8 
     | 
    
         
            +
                def test_simple
         
     | 
| 
      
 9 
     | 
    
         
            +
                    rrq = TftpPacketRRQ.new
         
     | 
| 
      
 10 
     | 
    
         
            +
                    rrq.filename = 'myfilename'
         
     | 
| 
      
 11 
     | 
    
         
            +
                    rrq.mode = 'octet'
         
     | 
| 
      
 12 
     | 
    
         
            +
                    rrq.encode
         
     | 
| 
      
 13 
     | 
    
         
            +
                    assert_equal("\000\001myfilename\000octet\000", rrq.buffer)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    assert_equal(1, rrq.opcode)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    rrq.decode
         
     | 
| 
      
 16 
     | 
    
         
            +
                    assert_equal('myfilename', rrq.filename)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    assert_equal('octet', rrq.mode)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    wrq = TftpPacketWRQ.new
         
     | 
| 
      
 20 
     | 
    
         
            +
                    wrq.buffer = "\000\002myfilename\000octet\000"
         
     | 
| 
      
 21 
     | 
    
         
            +
                    wrq.decode
         
     | 
| 
      
 22 
     | 
    
         
            +
                    assert_equal('myfilename', wrq.filename)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    assert_equal('octet', wrq.mode)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    assert_equal(2, wrq.opcode)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    wrq.encode.decode
         
     | 
| 
      
 26 
     | 
    
         
            +
                    assert_equal('myfilename', wrq.filename)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    assert_equal('octet', wrq.mode)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    assert_equal(2, wrq.opcode)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    dat = TftpPacketDAT.new
         
     | 
| 
      
 31 
     | 
    
         
            +
                    sampledat = "\000\001\002\003\004\005"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    dat.data = sampledat
         
     | 
| 
      
 33 
     | 
    
         
            +
                    dat.encode.decode
         
     | 
| 
      
 34 
     | 
    
         
            +
                    assert_equal(sampledat, dat.data)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    assert_equal(6, dat.data.length)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    assert_equal(3, dat.opcode)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    ack = TftpPacketACK.new
         
     | 
| 
      
 39 
     | 
    
         
            +
                    ack.blocknumber = 5
         
     | 
| 
      
 40 
     | 
    
         
            +
                    assert_equal(4, ack.opcode)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    assert_equal(5, ack.encode.decode.blocknumber)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    err = TftpPacketERR.new
         
     | 
| 
      
 44 
     | 
    
         
            +
                    err.errorcode = 3
         
     | 
| 
      
 45 
     | 
    
         
            +
                    assert_equal('Disk full or allocation exceeded.',
         
     | 
| 
      
 46 
     | 
    
         
            +
                                 err.encode.decode.errmsg)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    assert_equal(5, err.opcode)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    oack = TftpPacketOACK.new
         
     | 
| 
      
 50 
     | 
    
         
            +
                    oack_options = {
         
     | 
| 
      
 51 
     | 
    
         
            +
                        :blksize => 4096
         
     | 
| 
      
 52 
     | 
    
         
            +
                    }
         
     | 
| 
      
 53 
     | 
    
         
            +
                    oack.options = oack_options
         
     | 
| 
      
 54 
     | 
    
         
            +
                    oack.encode.decode
         
     | 
| 
      
 55 
     | 
    
         
            +
                    assert_equal('4096', oack.options[:blksize])
         
     | 
| 
      
 56 
     | 
    
         
            +
                    assert_equal(6, oack.opcode)
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            rubygems_version: 0.9.0
         
     | 
| 
      
 3 
     | 
    
         
            +
            specification_version: 1
         
     | 
| 
      
 4 
     | 
    
         
            +
            name: tftpplus
         
     | 
| 
      
 5 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 6 
     | 
    
         
            +
              version: "0.2"
         
     | 
| 
      
 7 
     | 
    
         
            +
            date: 2006-12-08 00:00:00 -05:00
         
     | 
| 
      
 8 
     | 
    
         
            +
            summary: A pure tftp implementation with support for variable block sizes
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 11 
     | 
    
         
            +
            email: msoulier@digitaltorque.ca
         
     | 
| 
      
 12 
     | 
    
         
            +
            homepage: http://tftpplus.rubyforge.org
         
     | 
| 
      
 13 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 14 
     | 
    
         
            +
            description: A new tftp library for clients and servers that supports RFCs 1350, 2347 and 2348 (ie. variable block sizes). It includes a sample client implementation, and will eventually include a multi-threaded server as well.
         
     | 
| 
      
 15 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 16 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 17 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 18 
     | 
    
         
            +
            has_rdoc: false
         
     | 
| 
      
 19 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Version::Requirement 
         
     | 
| 
      
 20 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 21 
     | 
    
         
            +
              - - ">"
         
     | 
| 
      
 22 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 23 
     | 
    
         
            +
                  version: 0.0.0
         
     | 
| 
      
 24 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 25 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 26 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 27 
     | 
    
         
            +
            cert_chain: 
         
     | 
| 
      
 28 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 29 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 30 
     | 
    
         
            +
            - Michael P. Soulier
         
     | 
| 
      
 31 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 32 
     | 
    
         
            +
            - lib/net/tftp+.rb
         
     | 
| 
      
 33 
     | 
    
         
            +
            - README
         
     | 
| 
      
 34 
     | 
    
         
            +
            - ChangeLog
         
     | 
| 
      
 35 
     | 
    
         
            +
            - test/test.rb
         
     | 
| 
      
 36 
     | 
    
         
            +
            test_files: 
         
     | 
| 
      
 37 
     | 
    
         
            +
            - test/test.rb
         
     | 
| 
      
 38 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            executables: 
         
     | 
| 
      
 43 
     | 
    
         
            +
            - tftp_client.rb
         
     | 
| 
      
 44 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            dependencies: []
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     |