Class: Net::TFTP

Inherits:
Object
  • Object
show all
Defined in:
lib/net/tftp.rb

Constant Summary collapse

VERSION =
"0.1.0"
DEFAULTS =
{
  :port => (Socket.getservbyname("tftp", "udp") rescue 69),
  :timeout => 5,
}
MINSIZE =
4
MAXSIZE =
516
DATABLOCK =
512
ERROR_DESCRIPTION =

Errors

[
  "Custom error",
  "File not found",
  "Access violation",
  "Disk full",
  "Illegal TFP operation",
  "Unknown transfer ID",
  "File already exists",
  "No such user",
]
ERROR_UNDEF =
0
ERROR_FILE_NOT_FOUND =
1
ERROR_ACCESS_VIOLATION =
2
ERROR_DISK_FULL =
3
ERROR_ILLEGAL_OPERATION =
4
ERROR_UNKNOWN_TRANSFER_ID =
5
ERROR_FILE_ALREADY_EXISTS =
6
ERROR_NO_SUCH_USER =
7
OP_RRQ =

Opcodes

1
OP_WRQ =
2
OP_DATA =
3
OP_ACK =
4
OP_ERROR =
5

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, params = {}) ⇒ TFTP

Create a TFTP connection object to a host. Note that no actual network connection is made. This methods never fails. Parameters:

:port

The UDP port. See DEFAULTS

:timeout

Timeout in second for each ack packet. See DEFAULTS



113
114
115
116
117
# File 'lib/net/tftp.rb', line 113

def initialize(host, params = {})
  @host = host
  @port = params[:port] || DEFAULTS[:port]
  @timeout = params[:timeout] || DEFAULTS[:timeout]
end

Instance Attribute Details

#hostObject

Returns the value of attribute host.



106
107
108
# File 'lib/net/tftp.rb', line 106

def host
  @host
end

#timeoutObject

Returns the value of attribute timeout.



106
107
108
# File 'lib/net/tftp.rb', line 106

def timeout
  @timeout
end

Class Method Details

.open(host) ⇒ Object

Alias for new



94
95
96
# File 'lib/net/tftp.rb', line 94

def open(host)
  new(host)
end

.size_in_blocks(size) ⇒ Object

Return the number of blocks to send size bytes.



99
100
101
102
103
# File 'lib/net/tftp.rb', line 99

def size_in_blocks(size)
  s = size / DATABLOCK
  s += 1 unless (size % DATABLOCK) == 0
  s
end

Instance Method Details

#getbinary(remotefile, io, &block) ⇒ Object

Retrieve a file using binary mode and send content to an io object The optional block receives the data in the block and the sequence number of the block starting at 1.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/net/tftp.rb', line 133

def getbinary(remotefile, io, &block) # :yields: data, seq
  s = UDPSocket.new
  begin
    peer_ip = IPSocket.getaddress(@host)
  rescue
    raise TFTPError, "Cannot find host '#{@host}'"
  end

  peer_tid = nil
  seq = 1
  from = nil
  data = nil

  # Initialize request
  s.send(rrq_packet(remotefile, "octet"), 0, peer_ip, @port)
  Timeout::timeout(@timeout, TFTPTimeout) do
    loop do
      packet, from = s.recvfrom(MAXSIZE, 0)
      next unless peer_ip == from[3]
      type, block, data = scan_packet(packet)
      break if (type == OP_DATA) && (block == seq)
    end
  end
  peer_tid = from[1]

  # Get and write data to io
  loop do
    io.write(data)
    s.send(ack_packet(seq), 0, peer_ip, peer_tid)
    yield(data, seq) if block_given?
    break if data.size < DATABLOCK
    
    seq += 1
    Timeout::timeout(@timeout, TFTPTimeout) do
      loop do
        packet, from = s.recvfrom(MAXSIZE, 0)
        next unless peer_ip == from[3]
        if peer_tid != from[1]
          s.send(error_packet(ERROR_UNKNOWN_TRANSFER_ID), 
                 0, from[3], from[1])
          next
        end
        type, block, data = scan_packet(packet)
        break if (type == OP_DATA) && (block == seq)
      end
    end
  end

  return true
end

#getbinaryfile(remotefile, localfile = nil, &block) ⇒ Object

Retrieve a file using binary mode. If the localfile name is omitted, it is set to the remotefile. The optional block receives the data in the block and the sequence number of the block starting at 1.



123
124
125
126
127
128
# File 'lib/net/tftp.rb', line 123

def getbinaryfile(remotefile, localfile = nil, &block) # :yields: data, seq
  localfile ||= File.basename(remotefile)
  open(localfile, "w") do |f|
    getbinary(remotefile, f, &block)
  end
end

#putbinary(remotefile, io, &block) ⇒ Object

Send the content read from io to the remotefile. The optional block receives the data in the block and the sequence number of the block starting at 1.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/net/tftp.rb', line 198

def putbinary(remotefile, io, &block) # :yields: data, seq
  s = UDPSocket.new
  peer_ip = IPSocket.getaddress(@host)
  
  peer_tid = nil
  seq = 0
  from = nil
  data = nil
  
  # Initialize request
  s.send(wrq_packet(remotefile, "octet"), 0, peer_ip, @port)
  Timeout::timeout(@timeout, TFTPTimeout) do
    loop do
      packet, from = s.recvfrom(MAXSIZE, 0)
      next unless peer_ip == from[3]
      type, block, data = scan_packet(packet)
      break if (type == OP_ACK) && (block == seq)
    end
  end
  peer_tid = from[1]

  loop do
    data = io.read(DATABLOCK) || ""
    seq += 1
    s.send(data_packet(seq, data), 0, peer_ip, peer_tid)
    
    Timeout::timeout(@timeout, TFTPTimeout) do
      loop do
        packet, from = s.recvfrom(MAXSIZE, 0)
        next unless peer_ip == from[3]
        if peer_tid != from[1]
          s.send(error_packet(ERROR_UNKNOWN_TRANSFER_ID), 
                 0, from[3], from[1])
          next
        end
        type, block, void = scan_packet(packet)
        break if (type == OP_ACK) && (block == seq)
      end
    end

    yield(data, seq) if block_given?
    break if data.size < DATABLOCK
  end
  
  return true
end

#putbinaryfile(localfile, remotefile = nil, &block) ⇒ Object

Send a file in binary mode. The name of the remotefile is set to the name of the local file if omitted. The optional block receives the data in the block and the sequence number of the block starting at 1.



188
189
190
191
192
193
# File 'lib/net/tftp.rb', line 188

def putbinaryfile(localfile, remotefile = nil, &block) # :yields: data, seq
  remotefile ||= File.basename(localfile)
  open(localfile) do |f|
    putbinary(remotefile, f, &block)
  end
end