File Coverage

blib/lib/Mojo/TFTPd/Connection.pm
Criterion Covered Total %
statement 103 135 76.3
branch 42 70 60.0
condition 17 30 56.6
subroutine 16 18 88.8
pod 8 9 88.8
total 186 262 70.9


line stmt bran cond sub pod time code
1             package Mojo::TFTPd::Connection;
2              
3             =head1 NAME
4              
5             Mojo::TFTPd::Connection - A connection class for Mojo::TFTPd
6              
7             =head1 SYNOPSIS
8              
9             See L
10              
11             =cut
12              
13 6     6   24 use Mojo::Base -base;
  6         8  
  6         37  
14 6     6   646 use Socket();
  6         10  
  6         107  
15 6     6   22 use constant OPCODE_DATA => 3;
  6         6  
  6         296  
16 6     6   22 use constant OPCODE_ACK => 4;
  6         9  
  6         290  
17 6     6   23 use constant OPCODE_ERROR => 5;
  6         7  
  6         269  
18 6     6   22 use constant OPCODE_OACK => 6;
  6         6  
  6         269  
19 6 50   6   21 use constant DEBUG => $ENV{MOJO_TFTPD_DEBUG} ? 1 : 0;
  6         7  
  6         937  
20              
21             our %ERROR_CODES = (
22             not_defined => [0, 'Not defined, see error message'],
23             unknown_opcode => [0, 'Unknown opcode: %s'],
24             no_connection => [0, 'No connection'],
25             file_not_found => [1, 'File not found'],
26             access_violation => [2, 'Access violation'],
27             disk_full => [3, 'Disk full or allocation exceeded'],
28             illegal_operation => [4, 'Illegal TFTP operation'],
29             unknown_transfer_id => [5, 'Unknown transfer ID'],
30             file_exists => [6, 'File already exists'],
31             no_such_user => [7, 'No such user'],
32             );
33              
34             BEGIN {
35             # do not use MSG_DONTWAIT on platforms that do not support it (Win32)
36 6     6   12 my $msg_dontwait = 0;
37 6         7 eval { $msg_dontwait = Socket::MSG_DONTWAIT };
  6         840  
38 131114     131114 0 2489396 sub MSG_DONTWAIT() { $msg_dontwait };
39             }
40              
41              
42             =head1 ATTRIBUTES
43              
44             =head2 type
45              
46             Type of connection rrq or wrq
47              
48             =head2 blocksize
49              
50             The negotiated blocksize.
51             Default is 512 Byte.
52              
53             =head2 error
54              
55             Useful to check inside L events to see if anything has
56             gone wrong. Holds a string describing the error.
57              
58             =head2 file
59              
60             The filename the client requested to read or write.
61              
62             =head2 filehandle
63              
64             This must be set inside the L or L
65             event or the connection will be dropped.
66             Can be either L or filehandle.
67              
68             =head2 filesize
69              
70             This must be set inside the L
71             to report "tsize" option if client requested.
72              
73             If set inside L limits maximum upload size.
74             Set automatically on WRQ with "tsize" option.
75              
76             Can be used inside L for uploads
77             to check if reported "tsize" and received data length match.
78              
79             =head2 timeout
80              
81             Retransmit/Inactive timeout.
82              
83             =head2 lastop
84              
85             Last operation.
86              
87             =head2 mode
88              
89             Either "netascii", "octet" or empty string if unknown.
90              
91             =head2 peerhost
92              
93             The IP address of the remote client.
94              
95             =head2 peername
96              
97             Packet address of the remote client.
98              
99             =head2 retries
100              
101             Number of times L, L or L can be retried before the
102             connection is dropped.
103             This value comes from L or set inside L or L
104             events.
105              
106             =head2 retransmit
107              
108             Number of times last operation (L, L or L)
109             to be retransmitted on timeout before the connection is dropped.
110             This value comes from L or set inside L or L
111             events.
112              
113             Retransmits are disabled if set to 0.
114              
115             =head2 socket
116              
117             The UDP handle to send data to.
118              
119             =head2 rfc
120              
121             Contains RFC 2347 options the client has provided. These options are stored
122             in an hash ref.
123              
124             =cut
125              
126             has type => undef;
127             has blocksize => 512;
128             has error => '';
129             has file => '/dev/null';
130             has filehandle => undef;
131             has filesize => undef;
132             has timeout => undef;
133             has lastop => undef;
134             has mode => '';
135             has peerhost => '';
136             has peername => '';
137             has retries => 2;
138             has retransmit => 0;
139             has rfc => sub { {} };
140             has socket => undef;
141             has _attempt => 0;
142             has _sequence_number => 1;
143              
144 6     6   27 use constant ROLLOVER => 256 * 256;
  6         6  
  6         11051  
145              
146             =head1 METHODS
147              
148             =head2 send_data
149              
150             This method is called when the server sends DATA to the client.
151              
152             =cut
153              
154             sub send_data {
155 65555     65555 1 56450 my $self = shift;
156 65555         1054335 my $FH = $self->filehandle;
157 65555         1145123 my $n = $self->_sequence_number;
158 65555         269558 my $seq = $n % ROLLOVER;
159 65555         48538 my($data, $sent);
160              
161 65555         63529 $self->{lastop} = OPCODE_DATA;
162              
163 65555 100       173262 if (UNIVERSAL::isa($FH, 'Mojo::Asset')) {
164 65545         1026639 $data = $FH->get_chunk(($n - 1) * $self->blocksize, $self->blocksize);
165 65545 50       3067999 return $self->send_error(file_not_found => 'Unable to read chunk') unless defined $data;
166             }
167             else {
168 10 50       154 if(not seek $FH, ($n - 1) * $self->blocksize, 0) {
169 0         0 return $self->send_error(file_not_found => "Seek: $!");
170             }
171 10 50       220 if(not defined read $FH, $data, $self->blocksize) {
172 0         0 return $self->send_error(file_not_found => "Read: $!");
173             }
174             }
175              
176 65555 100       1014025 if(length $data < $self->blocksize) {
177 4         26 $self->{_last_sequence_number} = $n;
178             }
179              
180 65555         314838 warn "[Mojo::TFTPd] >>> $self->{peerhost} data $seq (@{[length $data]})" .
181             ($self->_attempt ? " retransmit $self->{_attempt}" : '') . "\n" if DEBUG;
182              
183 65555         982582 $sent = $self->socket->send(
184             pack('nna*', OPCODE_DATA, $seq, $data),
185             MSG_DONTWAIT,
186             $self->peername,
187             );
188              
189 65555 100       506084 return 0 unless length $data;
190 65553 50 33     230335 return 1 if $sent or $self->{retries}--;
191 0         0 $self->error("Send: $!");
192 0         0 return 0;
193             }
194              
195             =head2 receive_ack
196              
197             This method is called when the client sends ACK to the server.
198              
199             =cut
200              
201             sub receive_ack {
202 65558     65558 1 67013 my $self = shift;
203 65558         120462 my($n) = unpack 'n', shift;
204 65558         1140980 my $seq = $self->_sequence_number % ROLLOVER;
205              
206 65558         283443 warn "[Mojo::TFTPd] <<< $self->{peerhost} ack $n" .
207             ($n && $n != $seq ? " expected $seq" : '') . "\n" if DEBUG;
208              
209 65558 100 100     134200 return $self->send_data if $n == 0 and $self->lastop eq OPCODE_OACK;
210 65556 100       1024871 return 0 if $self->lastop eq OPCODE_ERROR;
211 65553 100 66     395019 return 0 if $self->{_last_sequence_number} and $n == $self->{_last_sequence_number} % ROLLOVER;
212 65551 100       88441 if ($n == $seq) {
213 65543         59182 $self->{_attempt} = 0;
214 65543         55194 $self->{_sequence_number}++;
215 65543         106766 return $self->send_data;
216             }
217              
218 8 100 100     123 return 1 if $self->retransmit and $n < $seq;
219              
220 6 100       54 return $self->send_data if $self->{retries}--;
221 2         56 $self->error('Invalid packet number');
222 2         11 return 0;
223             }
224              
225             =head2 receive_data
226              
227             This method is called when the client sends DATA to the server.
228              
229             =cut
230              
231             sub receive_data {
232 65546     65546 1 66468 my $self = shift;
233 65546         149739 my($n, $data) = unpack 'na*', shift;
234 65546         1069379 my $FH = $self->filehandle;
235 65546         1102820 my $seq = $self->_sequence_number % ROLLOVER;
236              
237 65546         257848 warn "[Mojo::TFTPd] <<< $self->{peerhost} data $n (@{[length $data]})" .
238             ($n != $seq ? " expected $seq" : '') . "\n" if DEBUG;
239              
240 65546 50       97547 unless ($n == $seq) {
241 0 0 0     0 return 1 if $self->retransmit and $n < $seq;
242 0 0       0 return $self->send_ack if $self->{retries}--;
243 0         0 $self->error('Invalid packet number');
244 0         0 return 0;
245             }
246              
247 65546 100       153938 if (UNIVERSAL::isa($FH, 'Mojo::Asset')) {
248 65541         128908 local $!;
249 65541         58440 eval { $FH->add_chunk($data) };
  65541         132903  
250 65541 50       1497854 return $self->send_error(illegal_operation => "Unable to add chunk $!") if $!;
251             }
252             else {
253 5 50       29 unless(print $FH $data) {
254 0         0 return $self->send_error(illegal_operation => "Write: $!");
255             }
256             }
257              
258 65546 100       966378 unless(length $data == $self->blocksize) {
259 2         13 $self->{_last_sequence_number} = $n;
260             }
261              
262 65546 100 100     1169703 return $self->send_error(disk_full => 'tsize exceeded')
263             if $self->filesize and $self->filesize < $self->blocksize * ($n-1) + length $data;
264              
265 65545         300294 $self->{_sequence_number}++;
266 65545         94954 return $self->send_ack;
267             }
268              
269             =head2 send_ack
270              
271             This method is called when the server sends ACK to the client.
272              
273             =cut
274              
275             sub send_ack {
276 65547     65547 1 59037 my $self = shift;
277 65547         909137 my $n = $self->_sequence_number - 1;
278 65547         267923 my $seq = $n % ROLLOVER;
279 65547         47555 my $sent;
280              
281 65547         65738 $self->{lastop} = OPCODE_ACK;
282 65547         44568 warn "[Mojo::TFTPd] >>> $self->{peerhost} ack $seq" .
283             ($self->_attempt ? " retransmit $self->{_attempt}" : '') . "\n" if DEBUG;
284              
285 65547         925772 $sent = $self->socket->send(
286             pack('nn', OPCODE_ACK, $seq),
287             MSG_DONTWAIT,
288             $self->peername,
289             );
290              
291 65547 100       484966 return 0 if defined $self->{_last_sequence_number};
292 65545 50 33     212851 return 1 if $sent or $self->{retries}--;
293 0         0 $self->error("Send: $!");
294 0         0 return 0;
295             }
296              
297             =head2 receive_error
298              
299             This method is called when the client sends ERROR to the server.
300              
301             =cut
302              
303             sub receive_error {
304 0     0 1 0 my $self = shift;
305 0         0 my($code, $msg) = unpack 'nZ*', shift;
306              
307 0         0 warn "[Mojo::TFTPd] <<< $self->{peerhost} error $code $msg\n" if DEBUG;
308              
309 0         0 $self->error("($code) $msg");
310 0         0 return 0;
311             }
312              
313              
314             =head2 send_error
315              
316             Used to report error to the client.
317              
318             =cut
319              
320             sub send_error {
321 4     4 1 105 my($self, $name) = @_;
322 4   33     17 my $err = $ERROR_CODES{$name} || $ERROR_CODES{not_defined};
323              
324 4         13 $self->{lastop} = OPCODE_ERROR;
325 4         7 warn "[Mojo::TFTPd] >>> $self->{peerhost} error @$err\n" if DEBUG;
326              
327 4         69 $self->error($_[2]);
328 4         108 $self->socket->send(
329             pack('nnZ*', OPCODE_ERROR, @$err),
330             MSG_DONTWAIT,
331             $self->peername,
332             );
333              
334 4         37 return 0;
335             }
336              
337              
338             =head2 send_oack
339              
340             Used to send RFC 2347 OACK to client
341              
342             Supported options are
343              
344             =over
345              
346             =item RFC 2348 blksize - report $self->blocksize
347              
348             =item RFC 2349 timeout - report $self->timeout
349              
350             =item RFC 2349 tsize - report $self->filesize if set inside the L
351              
352             =back
353              
354             =cut
355              
356             sub send_oack {
357 8     8 1 77 my $self = shift;
358 8         9 my $sent;
359              
360 8         13 $self->{lastop} = OPCODE_OACK;
361              
362 8         8 my @options;
363 8 100       133 push @options, 'blksize', $self->blocksize if $self->rfc->{blksize};
364 8 100       242 push @options, 'timeout', $self->timeout if $self->rfc->{timeout};
365 8 100 66     159 push @options, 'tsize', $self->filesize if exists $self->rfc->{tsize} and $self->filesize;
366              
367 8         177 warn "[Mojo::TFTPd] >>> $self->{peerhost} oack @options" .
368             ($self->_attempt ? " retransmit $self->{_attempt}" : '') . "\n" if DEBUG;
369              
370 8         115 $sent = $self->socket->send(
371             pack('na*', OPCODE_OACK, join "\0", @options),
372             MSG_DONTWAIT,
373             $self->peername,
374             );
375              
376 8 50 33     112 return 1 if $sent or $self->{retries}--;
377 0           $self->error("Send: $!");
378 0           return 0;
379             }
380              
381             =head2 send_retransmit
382              
383             Used to retransmit last packet to the client.
384              
385             =cut
386              
387             sub send_retransmit {
388 0     0 1   my $self = shift;
389              
390 0 0         return 0 unless $self->lastop;
391              
392 0 0         unless ($self->retransmit) {
393 0           $self->error('Inactive timeout');
394 0           return 0;
395             }
396              
397             # Errors are not retransmitted
398 0 0         return 0 if $self->lastop == OPCODE_ERROR;
399              
400 0 0         if ($self->_attempt >= $self->retransmit) {
401 0           $self->error('Retransmit timeout');
402 0           return 0;
403             }
404              
405 0           $self->{_attempt}++;
406              
407 0 0         return $self->send_oack if $self->lastop eq OPCODE_OACK;
408 0 0         return $self->send_ack if $self->lastop eq OPCODE_ACK;
409 0 0         return $self->send_data if $self->lastop eq OPCODE_DATA;
410              
411 0           return 0;
412             }
413              
414              
415             =head1 AUTHOR
416              
417             Jan Henning Thorsen - C
418              
419             =cut
420              
421             1;