File Coverage

blib/lib/Mojo/TFTPd/Connection.pm
Criterion Covered Total %
statement 98 131 74.8
branch 40 68 58.8
condition 21 36 58.3
subroutine 17 19 89.4
pod 8 9 88.8
total 184 263 69.9


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