File Coverage

blib/lib/Mojo/TFTPd/Connection.pm
Criterion Covered Total %
statement 82 93 88.1
branch 31 40 77.5
condition 9 15 60.0
subroutine 13 13 100.0
pod 6 6 100.0
total 141 167 84.4


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 3     3   20 use Mojo::Base -base;
  3         7  
  3         26  
14 3     3   518 use Socket;
  3         7  
  3         2719  
15 3     3   19 use constant OPCODE_DATA => 3;
  3         8  
  3         1440  
16 3     3   19 use constant OPCODE_ACK => 4;
  3         5  
  3         194  
17 3     3   16 use constant OPCODE_ERROR => 5;
  3         4  
  3         129  
18 3     3   16 use constant OPCODE_OACK => 6;
  3         5  
  3         186  
19 3 50   3   14 use constant DEBUG => $ENV{MOJO_TFTPD_DEBUG} ? 1 : 0;
  3         6  
  3         31761  
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             =head1 ATTRIBUTES
35              
36             =head2 type
37              
38             Type of connection rrq or wrq
39              
40             =head2 blocksize
41              
42             The negotiated blocksize.
43             Default is 512 Byte.
44              
45             =head2 error
46              
47             Useful to check inside L events to see if anything has
48             gone wrong. Holds a string describing the error.
49              
50             =head2 file
51              
52             The filename the client requested to read or write.
53              
54             =head2 filehandle
55              
56             This must be set inside the L or L
57             event or the connection will be dropped.
58              
59             =head2 filesize
60              
61             This must be set inside the L
62             to report tsize option if client requested
63              
64             If set inside L limits maximum upload
65             Set automatically on WRQ with tsize
66              
67             Can be used inside L for uploads
68             to check if reported tsize and received data length match
69              
70             =head2 timeout
71              
72             How long a connection can stay idle before being dropped.
73              
74             =head2 lastop
75              
76             Last operation.
77              
78             =head2 mode
79              
80             Either "ascii", "octet" or empty string if unknown.
81              
82             =head2 peerhost
83              
84             The IP address of the remove client.
85              
86             =head2 peername
87              
88             Packet address of the remote client.
89              
90             =head2 retries
91              
92             Number of times L or L can be retried before the
93             connection is dropped. This value comes from L.
94              
95             =head2 socket
96              
97             The UDP handle to send data to.
98              
99             =head2 rfc
100              
101             Contains RFC 2347 options the client has provided. These options are stored
102             in an hash ref.
103              
104             =cut
105              
106             has type => undef;
107             has blocksize => 512;
108             has error => '';
109             has file => '/dev/null';
110             has filehandle => undef;
111             has filesize => undef;
112             has timeout => undef;
113             has lastop => undef;
114             has mode => '';
115             has peerhost => '';
116             has peername => '';
117             has retries => 2;
118             has rfc => sub { {} };
119             has socket => undef;
120             has _sequence_number => 1;
121              
122             =head1 METHODS
123              
124             =head2 send_data
125              
126             This method is called when the server sends DATA to the client.
127              
128             =cut
129              
130             sub send_data {
131 6     6 1 51 my $self = shift;
132 6         130 my $FH = $self->filehandle;
133 6         149 my $n = $self->_sequence_number;
134 6         40 my($data, $sent);
135              
136 6         16 $self->{timestamp} = time;
137 6         12 $self->{lastop} = OPCODE_DATA;
138              
139 6 50       132 if(not seek $FH, ($n - 1) * $self->blocksize, 0) {
140 0         0 return $self->send_error(file_not_found => "Seek: $!");
141             }
142 6 50       198 if(not defined read $FH, $data, $self->blocksize) {
143 0         0 return $self->send_error(file_not_found => "Read: $!");
144             }
145 6 100       349 if(length $data < $self->blocksize) {
146 2         19 $self->{_last_sequence_number} = $n;
147             }
148              
149 6         28 warn "[Mojo::TFTPd] >>> $self->{peerhost} data $n (@{[length $data]})\n" if DEBUG;
150              
151 6         131 $sent = $self->socket->send(
152             pack('nna*', OPCODE_DATA, $n, $data),
153             MSG_DONTWAIT,
154             $self->peername,
155             );
156              
157 6 100       223 return 0 unless length $data;
158 5 50       31 return 1 if $sent;
159 0         0 $self->error("Send: $!");
160 0         0 return $self->{retries}--;
161             }
162              
163             =head2 receive_ack
164              
165             This method is called when the client sends ACK to the server.
166              
167             =cut
168              
169             sub receive_ack {
170 6     6 1 10 my $self = shift;
171 6         12 my($n) = unpack 'n', shift;
172              
173 6         7 warn "[Mojo::TFTPd] <<< $self->{peerhost} ack $n\n" if DEBUG;
174              
175 6 50 33     19 return 1 if $n == 0 and $self->lastop eq OPCODE_OACK;
176 6 100       165 return 0 if $self->lastop eq OPCODE_ERROR;
177 5 100 66     55 return 0 if $self->{_last_sequence_number} and $n == $self->{_last_sequence_number};
178 4 100       23 return ++$self->{_sequence_number} if $n == $self->{_sequence_number};
179 2         47 $self->error('Invalid packet number');
180 2         26 return $self->{retries}--;
181             }
182              
183             =head2 receive_data
184              
185             This method is called when the client sends DATA to the server.
186              
187             =cut
188              
189             sub receive_data {
190 5     5 1 9 my $self = shift;
191 5         23 my($n, $data) = unpack 'na*', shift;
192 5         155 my $FH = $self->filehandle;
193              
194 5         29 warn "[Mojo::TFTPd] <<< $self->{peerhost} data $n (@{[length $data]})\n" if DEBUG;
195              
196 5 50       124 unless($n == $self->_sequence_number) {
197 0         0 $self->error('Invalid packet number');
198 0         0 return $self->{retries}--;
199             }
200 5 50       89 unless(print $FH $data) {
201 0         0 return $self->send_error(illegal_operation => "Write: $!");
202             };
203 5 100       125 unless(length $data == $self->blocksize) {
204 1         8 $self->{_last_sequence_number} = $n;
205             }
206              
207 5 100 100     156 return $self->send_error(disk_full => 'tsize exceeded')
208             if $self->filesize and $self->filesize < $self->blocksize * ($n-1) + length $data;
209              
210 4         168 $self->{_sequence_number}++;
211 4         35 return 1;
212             }
213              
214             =head2 send_ack
215              
216             This method is called when the server sends ACK to the client.
217              
218             =cut
219              
220             sub send_ack {
221 5     5 1 24 my $self = shift;
222 5         114 my $n = $self->_sequence_number - 1;
223 5         31 my $sent;
224              
225 5         9 $self->{timestamp} = time;
226 5         11 $self->{lastop} = OPCODE_ACK;
227 5         7 warn "[Mojo::TFTPd] >>> $self->{peerhost} ack $n\n" if DEBUG;
228              
229 5         111 $sent = $self->socket->send(
230             pack('nn', OPCODE_ACK, $n),
231             MSG_DONTWAIT,
232             $self->peername,
233             );
234              
235 5 100       200 return 0 if defined $self->{_last_sequence_number};
236 4 50       32 return 1 if $sent;
237 0         0 $self->error("Send: $!");
238 0         0 return $self->{retries}--;
239             }
240              
241             =head2 send_error
242              
243             Used to report error to the client.
244              
245             =cut
246              
247             sub send_error {
248 2     2 1 89 my($self, $name) = @_;
249 2   33     12 my $err = $ERROR_CODES{$name} || $ERROR_CODES{not_defined};
250              
251 2         8 $self->{lastop} = OPCODE_ERROR;
252 2         4 warn "[Mojo::TFTPd] >>> $self->{peerhost} error @$err\n" if DEBUG;
253              
254 2         56 $self->error($_[2]);
255 2         63 $self->socket->send(
256             pack('nnZ*', OPCODE_ERROR, @$err),
257             MSG_DONTWAIT,
258             $self->peername,
259             );
260              
261 2         87 return 0;
262             }
263              
264              
265             =head2 send_oack
266              
267             Used to send RFC 2347 OACK to client
268             Supported options are
269             RFC 2348 blksize - report $self->blocksize
270             RFC 2349 timeout - report $self->timeout
271             RFC 2349 tsize - report $self->filesize if set inside the L
272              
273             =cut
274              
275              
276             sub send_oack {
277 5     5 1 66 my $self = shift;
278 5         7 my $sent;
279              
280 5         20 $self->{timestamp} = time;
281 5         12 $self->{lastop} = OPCODE_OACK;
282              
283 5         7 my @options;
284 5 100       120 push @options, 'blksize', $self->blocksize if $self->rfc->{blksize};
285 5 100       236 push @options, 'timeout', $self->timeout if $self->rfc->{timeout};
286 5 100 66     191 push @options, 'tsize', $self->filesize if exists $self->rfc->{tsize} and $self->filesize;
287              
288 5         200 warn "[Mojo::TFTPd] >>> $self->{peerhost} oack @options\n" if DEBUG;
289              
290 5         116 $sent = $self->socket->send(
291             pack('na*', OPCODE_OACK, join "\0", @options),
292             MSG_DONTWAIT,
293             $self->peername,
294             );
295              
296 5 50       243 return 1 if $sent;
297 0           $self->error("Send: $!");
298 0           return $self->{retries}--;
299             }
300              
301              
302             =head1 AUTHOR
303              
304             Jan Henning Thorsen - C
305              
306             =cut
307              
308             1;