File Coverage

blib/lib/Device/Modem/Protocol/Xmodem.pm
Criterion Covered Total %
statement 3 157 1.9
branch 0 44 0.0
condition 0 31 0.0
subroutine 1 26 3.8
pod n/a
total 4 258 1.5


line stmt bran cond sub pod time code
1             # Device::Modem::Protocol::Xmodem - Xmodem file transfer protocol for Device::Modem class
2             #
3             # Initial revision: 1 Oct 2003
4             #
5             # Copyright (C) 2003-2005 Cosimo Streppone, cosimo@cpan.org
6             #
7             # This program is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9             #
10             # Additionally, this is ALPHA software, still needs extensive
11             # testing and support for generic AT commads, so use it at your own risk,
12             # and without ANY warranty! Have fun.
13             #
14             # This Xmodem protocol version is indeed very alpha code,
15             # probably does not work at all, so stay tuned...
16             #
17             # $Id$
18              
19             package Xmodem::Constants;
20              
21             # Define constants used in xmodem blocks
22             sub nul () { 0x00 } # ^@
23             sub soh () { 0x01 } # ^A
24             sub stx () { 0x02 } # ^B
25             sub eot () { 0x04 } # ^D
26             sub ack () { 0x06 } # ^E
27             sub nak () { 0x15 } # ^U
28             sub can () { 0x18 } # ^X
29             sub C () { 0x43 }
30             sub ctrl_z () { 0x1A } # ^Z
31              
32             sub CHECKSUM () { 1 }
33             sub CRC16 () { 2 }
34             sub CRC32 () { 3 }
35              
36             sub XMODEM () { 0x01 }
37             sub XMODEM_1K () { 0x02 }
38             sub XMODEM_CRC () { 0x03 }
39              
40             #sub YMODEM () { 0x04 }
41             #sub ZMODEM () { 0x05 }
42              
43             package Xmodem::Block;
44              
45 1     1   2445 use overload q[""] => \&to_string;
  1         981  
  1         7  
46              
47             # Create a new block object
48             sub new {
49 0     0     my($proto, $num, $data, $length) = @_;
50 0   0       my $class = ref $proto || $proto;
51              
52             # Define block type (128 or 1k chars) if not specified
53 0 0 0       $length ||= ( length $data > 128 ? 1024 : 128 );
54              
55             # Define structure of a Xmodem transfer block object
56 0 0         my $self = {
    0          
57             number => defined $num ? $num : 0,
58             'length'=> $length,
59             data => defined $data ? substr($data, 0, $length) : "", # Blocks are limited to 128 or 1024 chars
60             };
61              
62 0           bless $self, $class;
63             }
64              
65             # Calculate checksum of current block data
66             sub checksum {
67 0     0     my $self = $_[0];
68 0           my $sum = 0;
69 0           foreach my $c ( $self->data() ) {
70 0           $sum += ord $c;
71 0           $sum %= 256;
72             }
73 0           return $sum % 256;
74             }
75              
76             # Calculate CRC 16 bit on block data
77             sub crc16 {
78 0     0     my $self = $_[0];
79 0           return unpack('%C16*' => $self->data()) % 65536;
80             }
81              
82             # Calculate CRC 32 bit on block data
83             sub crc32 {
84 0     0     my $self = $_[0];
85 0           return unpack('%C32' => $self->data());
86             }
87              
88             # Return data one char at a time
89             sub data {
90 0     0     my $self = $_[0];
91             return wantarray
92             ? split(//, $self->{data})
93 0 0         : substr($self->{data}, 0, $self->{'length'})
94             }
95              
96             sub number {
97 0     0     my $self = $_[0];
98 0           return $self->{number};
99             }
100              
101             # Calculate checksum/crc for the current block and stringify block for transfer
102             sub to_string {
103 0     0     my $self = $_[0];
104 0           my $block_num = $self->number();
105              
106             # Assemble block to be transferred
107             my $xfer = pack(
108              
109             'cccA'.$self->{'length'}.'c',
110              
111 0 0         $self->{'length'} == 128
112             ? Xmodem::Constants::soh # Start Of Header (block size = 128)
113             : Xmodem::Constants::stx, # Start Of Text (block size = 1024)
114              
115             $block_num, # Block number
116              
117             $block_num ^ 0xFF, # 2's complement of block number
118              
119             scalar $self->data, # Data chars
120              
121             $self->checksum() # Final checksum (or crc16 or crc32)
122             # TODO crc16, crc32 ?
123             );
124              
125 0           return $xfer;
126             }
127              
128             #
129             # verify( type, value )
130             # ex.: verify( 'checksum', 0x7F )
131             # ex.: verify( 'crc16', 0x8328 )
132             #
133             sub verify {
134 0     0     my($self, $type, $value) = @_;
135              
136             # Detect type of value to be checked
137              
138             # TODO use new constants
139              
140 0 0         $type = 'checksum' unless defined $type;
141              
142 0 0         if( $type eq 'checksum' ) {
    0          
    0          
143 0           $good_value = $self->checksum();
144             } elsif( $type eq 'crc16' ) {
145 0           $good_value = $self->crc16();
146             } elsif( $type eq 'crc32' ) {
147 0           $good_value = $self->crc32();
148             } else {
149 0           $good_value = $self->checksum();
150             }
151 0           print 'value:', $value, 'goodvalue:', $good_value;
152 0           return $good_value == $value;
153             }
154              
155             # ----------------------------------------------------------------
156              
157             package Xmodem::Buffer;
158              
159             sub new {
160 0     0     my($proto, $num, $data) = @_;
161 0   0       my $class = ref $proto || $proto;
162              
163             # Define structure of a Xmodem transfer buffer
164 0           my $self = [];
165 0           bless($self);
166 0           return $self;
167             }
168              
169             # Push, pop, operations on buffer
170             sub push {
171 0     0     my $self = $_[0];
172 0           my $block = $_[1];
173 0           push @$self, $block;
174             }
175              
176             sub pop {
177 0     0     my $self = $_[0];
178 0           pop @$self
179             }
180              
181             # Get last block on buffer (to retransmit / re-receive)
182             sub last {
183 0     0     my $self = $_[0];
184 0           return $self->[ $#$self ];
185             }
186              
187             sub blocks {
188 0     0     return @{$_[0]};
  0            
189             }
190              
191             #
192             # Replace n-block with given block object
193             #
194             sub replace {
195 0     0     my $self = $_[0];
196 0           my $num = $_[1];
197 0           my $block = $_[2];
198              
199 0           $self->[$num] = $block;
200             }
201              
202             sub dump {
203 0     0     my $self = $_[0];
204 0           my $output;
205              
206             # Join all blocks into string
207 0           for (my $pos = 0; $pos < scalar($self->blocks()); $pos++) {
208 0           $output .= $self->[$pos]->data();
209             }
210              
211             # Clean out any end of file markers (^Z) in data
212 0           $output =~ s/\x1A*$//;
213              
214 0           return $output;
215             }
216              
217             # ----------------------------------------------------------------
218              
219             package Xmodem::Receiver;
220              
221             # Define default timeouts for CRC handshaking stage and checksum normal procedure
222             sub TIMEOUT_CRC () { 3 };
223             sub TIMEOUT_CHECKSUM () { 10 };
224              
225             our $TIMEOUT = TIMEOUT_CRC;
226             our $DEBUG = 1;
227              
228             sub abort_transfer {
229 0     0     my $self = $_[0];
230              
231             # Send a cancel char to abort transfer
232 0           _log('aborting transfer');
233 0           $self->modem->atsend( chr(Xmodem::Constants::can) );
234 0 0         $self->modem->port->write_drain() unless $self->modem->ostype() eq 'windoze';
235 0           $self->{aborted} = 1;
236 0           return 1;
237             }
238              
239             #
240             # TODO protocol management
241             #
242             sub new {
243 0     0     my $proto = shift;
244 0           my %opt = @_;
245 0   0       my $class = ref $proto || $proto;
246              
247             # Create `modem' object if does not exist
248 0           _log('opt{modem} = ', $opt{modem});
249 0 0         if( ! exists $opt{modem} ) {
250 0           require Device::Modem;
251 0           $opt{modem} = Device::Modem->new();
252             }
253              
254             my $self = {
255             _modem => $opt{modem},
256 0   0       _filename => $opt{filename} || 'received.dat',
257             current_block => 0,
258             timeouts => 0,
259             };
260              
261 0           bless $self, $class;
262             }
263              
264             # Get `modem' Device::SerialPort member
265             sub modem {
266 0     0     $_[0]->{_modem};
267             }
268              
269             #
270             # Try to receive a block. If receive is correct, push a new block on buffer
271             #
272             sub receive_message {
273 0     0     my $self = $_[0];
274 0           my $message_type;
275 0           my $message_number = 0;
276 0           my $message_complement = 0;
277 0           my $message_data;
278             my $message_checksum;
279              
280             # Receive answer
281             #my $received = $self->modem->answer( undef, 1000 );
282             #my $received = $self->modem->answer( "/.{132}/", 1000 );
283             # Had problems dropping bytes from block messages that caused the checksum
284             # to be missing on rare occasions.
285 0           ($count_in, $received) = $self->modem->port->read(132);
286              
287 0           _log('[receive_message][', $count_in, '] received [', unpack('H*',$received), '] data');
288              
289             # Get Message Type
290 0           $message_type = ord(substr($received, 0, 1));
291              
292             # If this is a block extract data from message
293 0 0         if( $message_type eq Xmodem::Constants::soh ) {
294              
295             # Check block number and its 2's complement
296 0           ($message_number, $message_complement) = ( ord(substr($received,1,1)), ord(substr($received,2,1)) );
297              
298             # Extract data string from message
299 0           $message_data = substr($received,3,128);
300              
301             # Extract checksum from message
302 0           $message_checksum = ord(substr($received, 131, 1));
303             }
304              
305 0           my %message = (
306             type => $message_type, # Message Type
307             number => $message_number, # Message Sequence Number
308             complement => $message_complement, # Message Number's Complement
309             data => $message_data, # Message Data String
310             checksum => $message_checksum, # Message Data Checksum
311             );
312              
313 0           return %message;
314             }
315              
316             sub run {
317 0     0     my $self = $_[0];
318 0           my $modem = $self->{_modem};
319 0   0       my $file = $_[1] || $self->{_filename};
320 0   0       my $protocol = $_[2] || Xmodem::Constants::XMODEM;
321              
322 0           _log('[run] checking modem[', $modem, '] or file[', $file, '] members');
323 0 0 0       return 0 unless $modem and $file;
324              
325             # Initialize transfer
326 0           $self->{current_block} = 0;
327 0           $self->{timeouts} = 0;
328              
329             # Initialize a receiving buffer
330 0           _log('[run] creating new receive buffer');
331              
332 0           my $buffer = Xmodem::Buffer->new();
333              
334             # Stage 1: handshaking for xmodem standard version
335 0           _log('[run] sending first timeout');
336 0           $self->send_timeout();
337              
338 0           my $file_complete = 0;
339              
340 0           $self->{current_block} = Xmodem::Block->new(0);
341              
342             # Open output file
343 0 0         return undef unless open OUTFILE, '>'.$file;
344              
345             # Main receive cycle (subsequent timeout cycles)
346 0   0       do {
347              
348             # Try to receive a message
349 0           my %message = $self->receive_message();
350              
351 0 0         if ( $message{type} eq Xmodem::Constants::nul ) {
    0          
    0          
352              
353             # Nothing received yet, do nothing
354 0           _log('[run] ', $message{type});
355             } elsif ( $message{type} eq Xmodem::Constants::eot ) {
356              
357             # If last block transmitted mark complete and close file
358 0           _log('[run] ', $message{type});
359              
360             # Acknoledge we received
361 0           $self->send_ack();
362 0           $file_complete = 1;
363              
364             # Write buffer data to file
365 0           print(OUTFILE $buffer->dump());
366              
367 0           close OUTFILE;
368             } elsif ( $message{type} eq Xmodem::Constants::soh ) {
369              
370             # If message header, check integrity and build block
371 0           _log('[run] ', $message{type});
372 0           my $message_status = 1;
373              
374             # Check block number
375 0 0         if ( (255 - $message{complement}) != $message{number} ) {
376 0           _log('[run] bad block number: ', $message{number}, ' != (255 - ', $message{complement}, ')' );
377 0           $message_status = 0;
378             }
379              
380             # Check block numbers for out of sequence blocks
381 0 0 0       if ( $message{number} < $self->{current_block}->number() || $message{number} > ($self->{current_block}->number() + 1) ) {
382 0           _log('[run] bad block sequence');
383 0           $self->abort_transfer();
384             }
385              
386             # Instance a new "block" object from message data received
387 0           my $new_block = Xmodem::Block->new( $message{number}, $message{data} );
388              
389             # Check block against checksum
390 0 0 0       if (!( defined $new_block && $new_block->verify( 'checksum', $message{checksum}) )) {
391 0           _log('[run] bad block checksum');
392 0           $message_status = 0;
393             }
394              
395             # This message block was good, update current_block and push onto buffer
396 0 0         if ($message_status) {
397 0           _log('[run] received block ', $new_block->number());
398              
399             # Update current block to the one received
400 0           $self->{current_block} = $new_block;
401              
402             # Push block onto buffer
403 0           $buffer->push($self->{current_block});
404              
405             # Acknoledge we successfully received block
406 0           $self->send_ack();
407              
408             } else {
409              
410             # Send nak since did not receive block successfully
411 0           _log('[run] message_status = 0, sending ');
412 0           $self->send_nak();
413             }
414             } else {
415 0           _log('[run] neither types found, sending timingout');
416 0           $self->send_timeout();
417             }
418              
419             } until $file_complete or $self->timeouts() >= 10;
420             }
421              
422             sub send_ack {
423 0     0     my $self = $_[0];
424 0           _log('sending ack');
425 0           $self->modem->atsend( chr(Xmodem::Constants::ack) );
426 0           $self->modem->port->write_drain();
427 0           $self->{timeouts} = 0;
428 0           return 1;
429             }
430              
431             sub send_nak {
432 0     0     my $self = $_[0];
433 0           _log('sending timeout (', $self->{timeouts}, ')');
434 0           $self->modem->atsend( chr(Xmodem::Constants::nak) );
435              
436 0           my $received = $self->modem->answer( undef, TIMEOUT_CHECKSUM );
437              
438 0           _log('[nak_dump] received [', unpack('H*',$received), '] data');
439              
440 0           $self->modem->port->write_drain();
441 0           $self->{timeouts}++;
442 0           return 1;
443             }
444              
445             sub send_timeout {
446 0     0     my $self = $_[0];
447 0           _log('sending timeout (', $self->{timeouts}, ')');
448 0           $self->modem->atsend( chr(Xmodem::Constants::nak) );
449 0           $self->modem->port->write_drain();
450 0           $self->{timeouts}++;
451 0           return 1;
452             }
453              
454             sub timeouts {
455 0     0     my $self = $_[0];
456 0           $self->{timeouts};
457             }
458              
459             sub _log {
460 0 0   0     print STDERR @_, "\n" if $DEBUG
461             }
462              
463             1;
464              
465             =head1 NAME
466              
467             Device::Modem::Protocol::Xmodem
468              
469             =head1 Xmodem::Block
470              
471             Class that represents a single Xmodem data block.
472              
473             =head2 Synopsis
474              
475             my $b = Xmodem::Block->new( 1, 'My Data......' );
476             if( defined $b ) {
477             # Ok, block instanced, verify its checksum
478             if( $b->verify( 'checksum', ) ) {
479             ...
480             } else {
481             ...
482             }
483             } else {
484             # No block
485             }
486              
487             # Calculate checksum, crc16, 32, ...
488             $crc16 = $b->crc16();
489             $crc32 = $b->crc32();
490             $chksm = $b->checksum();
491              
492             =head1 Xmodem::Buffer
493              
494             Class that implements an Xmodem receive buffer of data blocks. Every block of data
495             is represented by a C object.
496              
497             Blocks can be Bed and Bped from the buffer. You can retrieve the B
498             block, or the list of B from buffer.
499              
500             =head2 Synopsis
501              
502             my $buf = Xmodem::Buffer->new();
503             my $b1 = Xmodem::Block->new(1, 'Data...');
504              
505             $buf->push($b1);
506              
507             my $b2 = Xmodem::Block->new(2, 'More data...');
508             $buf->push($b2);
509              
510             my $last_block = $buf->last();
511              
512             print 'now I have ', scalar($buf->blocks()), ' in the buffer';
513              
514             # TODO document replace() function ???
515              
516             =head1 Xmodem::Constants
517              
518             Package that contains all useful Xmodem protocol constants used in handshaking and
519             data blocks encoding procedures
520              
521             =head2 Synopsis
522              
523             Xmodem::Constants::soh ........... 'start of header'
524             Xmodem::Constants::eot ........... 'end of trasmission'
525             Xmodem::Constants::ack ........... 'acknowlegded'
526             Xmodem::Constants::nak ........... 'not acknowledged'
527             Xmodem::Constants::can ........... 'cancel'
528             Xmodem::Constants::C ........... `C' ASCII char
529              
530             Xmodem::Constants::XMODEM ........ basic xmodem protocol
531             Xmodem::Constants::XMODEM_1K ..... xmodem protocol with 1k blocks
532             Xmodem::Constants::XMODEM_CRC .... xmodem protocol with CRC checks
533              
534             Xmodem::Constants::CHECKSUM ...... type of block checksum
535             Xmodem::Constants::CRC16 ......... type of block crc16
536             Xmodem::Constants::CRC32 ......... type of block crc32
537              
538             =head1 Xmodem::Receiver
539              
540             Control class to initiate and complete a C file transfer in receive mode
541              
542             =head2 Synopsis
543              
544             my $recv = Xmodem::Receiver->new(
545             modem => {Device::Modem object},
546             filename => 'name of file',
547             XXX protocol => 'xmodem' | 'xmodem-crc', | 'xmodem-1k'
548             );
549              
550             $recv->run();
551              
552             =head2 Object methods
553              
554             =over 4
555              
556             =item abort_transfer()
557              
558             Sends a B char (C), that signals to sender that transfer is aborted. This is
559             issued if we receive a bad block number, which usually means we got a bad line.
560              
561             =item modem()
562              
563             Returns the underlying L object.
564              
565             =item receive_message()
566              
567             Retrieves message from modem and if a block is detected it breaks it into appropriate
568             parts.
569              
570             =item run()
571              
572             Starts a new transfer until file receive is complete. The only parameter accepted
573             is the (optional) local filename to be written.
574              
575             =item send_ack()
576              
577             Sends an acknowledge (C) char, to signal that we received and stored a correct block
578             Resets count of timeouts and returns the C object of the data block
579             received.
580              
581             =item send_timeout()
582              
583             Sends a B (C) char, to signal that we received a bad block header (either
584             a bad start char or a bad block number), or a bad data checksum. Increments count
585             of timeouts and at ten timeouts, aborts transfer.
586              
587             =back
588              
589             =head2 See also
590              
591             =over 4
592              
593             =item - L
594              
595             =back