This file is indexed.

/usr/share/perl5/Device/Modem/Protocol/Xmodem.pm is in libdevice-modem-perl 1.47-2.1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
# Device::Modem::Protocol::Xmodem - Xmodem file transfer protocol for Device::Modem class
#
# Initial revision: 1 Oct 2003
#
# Copyright (C) 2003-2005 Cosimo Streppone, cosimo@cpan.org
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Additionally, this is ALPHA software, still needs extensive
# testing and support for generic AT commads, so use it at your own risk,
# and without ANY warranty! Have fun.
#
# This Xmodem protocol version is indeed very alpha code,
# probably does not work at all, so stay tuned...
#
# $Id: Xmodem.pm,v 1.7 2005/11/15 22:28:42 cosimo Exp $

package Xmodem::Constants;

# Define constants used in xmodem blocks
sub nul        () { 0x00 } # ^@
sub soh        () { 0x01 } # ^A
sub stx        () { 0x02 } # ^B
sub eot        () { 0x04 } # ^D
sub ack        () { 0x06 } # ^E
sub nak        () { 0x15 } # ^U
sub can        () { 0x18 } # ^X
sub C          () { 0x43 }
sub ctrl_z     () { 0x1A } # ^Z

sub CHECKSUM   () { 1 }
sub CRC16      () { 2 }
sub CRC32      () { 3 }

sub XMODEM     () { 0x01 }
sub XMODEM_1K  () { 0x02 }
sub XMODEM_CRC () { 0x03 }

#sub YMODEM     () { 0x04 }
#sub ZMODEM     () { 0x05 }

package Xmodem::Block;

use overload q[""] => \&to_string;

# Create a new block object
sub new {
    my($proto, $num, $data, $length) = @_;
    my $class = ref $proto || $proto;

    # Define block type (128 or 1k chars) if not specified
    $length ||= ( length $data > 128 ? 1024 : 128 );

    # Define structure of a Xmodem transfer block object
    my $self = {
        number  => defined $num ? $num : 0,
        'length'=> $length,
        data    => defined $data ? substr($data, 0, $length) : "",      # Blocks are limited to 128 or 1024 chars
      };

    bless $self, $class;
}

# Calculate checksum of current block data
sub checksum {
    my $self = $_[0];
    my $sum  = 0;
    foreach my $c ( $self->data() ) {
        $sum += ord $c;
        $sum %= 256;
    }
    return $sum % 256;
}

# Calculate CRC 16 bit on block data
sub crc16 {
    my $self = $_[0];
    return unpack('%C16*' => $self->data()) % 65536;
}

# Calculate CRC 32 bit on block data
sub crc32 {
    my $self = $_[0];
    return unpack('%C32' => $self->data());
}

# Return data one char at a time
sub data {
    my $self = $_[0];
    return wantarray
      ? split(//, $self->{data})
      : substr($self->{data}, 0, $self->{'length'})
}

sub number {
    my $self = $_[0];
    return $self->{number};
}

# Calculate checksum/crc for the current block and stringify block for transfer
sub to_string {
    my $self = $_[0];
    my $block_num = $self->number();

    # Assemble block to be transferred
    my $xfer = pack(

        'cccA'.$self->{'length'}.'c',

        $self->{'length'} == 128
        ? Xmodem::Constants::soh   # Start Of Header (block size = 128)
        : Xmodem::Constants::stx,  # Start Of Text   (block size = 1024)

        $block_num,                    # Block number

        $block_num ^ 0xFF,             # 2's complement of block number

        scalar $self->data,            # Data chars

        $self->checksum()              # Final checksum (or crc16 or crc32)
          # TODO crc16, crc32 ?
      );

    return $xfer;
}

#
# verify( type, value )
# ex.: verify( 'checksum', 0x7F )
# ex.: verify( 'crc16', 0x8328 )
#
sub verify {
    my($self, $type, $value) = @_;

    # Detect type of value to be checked

    # TODO use new constants

    $type = 'checksum' unless defined $type;

    if( $type eq 'checksum' ) {
        $good_value = $self->checksum();
    } elsif( $type eq 'crc16' ) {
        $good_value = $self->crc16();
    } elsif( $type eq 'crc32' ) {
        $good_value = $self->crc32();
    } else {
        $good_value = $self->checksum();
    }
    print 'value:', $value, 'goodvalue:', $good_value;
    return $good_value == $value;
}

# ----------------------------------------------------------------

package Xmodem::Buffer;

sub new {
    my($proto, $num, $data) = @_;
    my $class = ref $proto || $proto;

    # Define structure of a Xmodem transfer buffer
    my $self = [];
    bless($self);
    return $self;
}

# Push, pop, operations on buffer
sub push {
    my $self  = $_[0];
    my $block = $_[1];
    push @$self, $block;
}

sub pop {
    my $self = $_[0];
    pop @$self
}

# Get last block on buffer (to retransmit / re-receive)
sub last {
    my $self = $_[0];
    return $self->[ $#$self ];
}

sub blocks {
    return @{$_[0]};
}

#
# Replace n-block with given block object
#
sub replace {
    my $self  = $_[0];
    my $num   = $_[1];
    my $block = $_[2];

    $self->[$num] = $block;
}

sub dump {
    my $self = $_[0];
    my $output;

    # Join all blocks into string
    for (my $pos = 0; $pos < scalar($self->blocks()); $pos++) {
        $output .= $self->[$pos]->data();
    }

    # Clean out any end of file markers (^Z) in data
    $output =~ s/\x1A*$//;

    return $output;
}

# ----------------------------------------------------------------

package Xmodem::Receiver;

# Define default timeouts for CRC handshaking stage and checksum normal procedure
sub TIMEOUT_CRC      () {  3 };
sub TIMEOUT_CHECKSUM () { 10 };

our $TIMEOUT = TIMEOUT_CRC;
our $DEBUG   = 1;

sub abort_transfer {
    my $self = $_[0];

    # Send a cancel char to abort transfer
    _log('aborting transfer');
    $self->modem->atsend( chr(Xmodem::Constants::can) );
    $self->modem->port->write_drain() unless $self->modem->ostype() eq 'windoze';
    $self->{aborted} = 1;
    return 1;
}

#
# TODO protocol management
#
sub new {
    my $proto = shift;
    my %opt   = @_;
    my $class = ref $proto || $proto;

    # Create `modem' object if does not exist
    _log('opt{modem} = ', $opt{modem});
    if( ! exists $opt{modem} ) {
        require Device::Modem;
        $opt{modem} = Device::Modem->new();
    }

    my $self = {
        _modem    => $opt{modem},
        _filename => $opt{filename} || 'received.dat',
        current_block => 0,
        timeouts  => 0,
      };

    bless $self, $class;
}

# Get `modem' Device::SerialPort member
sub modem {
    $_[0]->{_modem};
}

#
# Try to receive a block. If receive is correct, push a new block on buffer
#
sub receive_message {
    my $self = $_[0];
    my $message_type;
    my $message_number = 0;
    my $message_complement = 0;
    my $message_data;
    my $message_checksum;

    # Receive answer
    #my $received = $self->modem->answer( undef, 1000 );
    #my $received = $self->modem->answer( "/.{132}/", 1000 );
    # Had problems dropping bytes from block messages  that caused the checksum
    # to be missing on rare occasions.
    ($count_in, $received) = $self->modem->port->read(132);

    _log('[receive_message][', $count_in, '] received [', unpack('H*',$received), '] data');

    # Get Message Type
    $message_type = ord(substr($received, 0, 1));

    # If this is a block extract data from message
    if( $message_type eq Xmodem::Constants::soh ) {

        # Check block number and its 2's complement
        ($message_number, $message_complement) = ( ord(substr($received,1,1)), ord(substr($received,2,1)) );

        # Extract data string from message
        $message_data = substr($received,3,128);

        # Extract checksum from message
        $message_checksum = ord(substr($received, 131, 1));
    }

    my %message = (
        type       => $message_type,        # Message Type
        number     => $message_number,      # Message Sequence Number
        complement => $message_complement,  # Message Number's Complement
        data       => $message_data,        # Message Data String
        checksum   => $message_checksum,    # Message Data Checksum
      );

    return %message;
}

sub run {
    my $self  = $_[0];
    my $modem = $self->{_modem};
    my $file  = $_[1] || $self->{_filename};
    my $protocol = $_[2] || Xmodem::Constants::XMODEM;

    _log('[run] checking modem[', $modem, '] or file[', $file, '] members');
    return 0 unless $modem and $file;

    # Initialize transfer
    $self->{current_block} = 0;
    $self->{timeouts}      = 0;

    # Initialize a receiving buffer
    _log('[run] creating new receive buffer');

    my $buffer = Xmodem::Buffer->new();

    # Stage 1: handshaking for xmodem standard version
    _log('[run] sending first timeout');
    $self->send_timeout();

    my $file_complete = 0;

    $self->{current_block} = Xmodem::Block->new(0);

    # Open output file
    return undef unless open OUTFILE, '>'.$file;

    # Main receive cycle (subsequent timeout cycles)
    do {

        # Try to receive a message
        my %message = $self->receive_message();

        if ( $message{type} eq Xmodem::Constants::nul ) {

            # Nothing received yet, do nothing
            _log('[run] <NUL>', $message{type});
        } elsif ( $message{type} eq Xmodem::Constants::eot ) {

            # If last block transmitted mark complete and close file
            _log('[run] <EOT>', $message{type});

            # Acknoledge we received <EOT>
            $self->send_ack();
            $file_complete = 1;

            # Write buffer data to file
            print(OUTFILE $buffer->dump());

            close OUTFILE;
        } elsif ( $message{type} eq Xmodem::Constants::soh ) {

            # If message header, check integrity and build block
            _log('[run] <SOH>', $message{type});
            my $message_status = 1;

            # Check block number
            if ( (255 - $message{complement}) != $message{number} ) {
                _log('[run] bad block number: ', $message{number}, ' != (255 - ', $message{complement}, ')' );
                $message_status = 0;
            }

            # Check block numbers for out of sequence blocks
            if ( $message{number} < $self->{current_block}->number() || $message{number} > ($self->{current_block}->number() + 1) ) {
                _log('[run] bad block sequence');
                $self->abort_transfer();
            }

            # Instance a new "block" object from message data received
            my $new_block = Xmodem::Block->new( $message{number}, $message{data} );

            # Check block against checksum
            if (!( defined $new_block && $new_block->verify( 'checksum', $message{checksum}) )) {
                _log('[run] bad block checksum');
                $message_status = 0;
            }

        # This message block was good, update current_block and push onto buffer
            if ($message_status) {
                _log('[run] received block ', $new_block->number());

                # Update current block to the one received
                $self->{current_block} = $new_block;

                # Push block onto buffer
                $buffer->push($self->{current_block});

                # Acknoledge we successfully received block
                $self->send_ack();

            } else {

                # Send nak since did not receive block successfully
                _log('[run] message_status = 0, sending <NAK>');
                $self->send_nak();
            }
        } else {
            _log('[run] neither types found, sending timingout');
            $self->send_timeout();
        }

      } until $file_complete or $self->timeouts() >= 10;
}

sub send_ack {
    my $self = $_[0];
    _log('sending ack');
    $self->modem->atsend( chr(Xmodem::Constants::ack) );
    $self->modem->port->write_drain();
    $self->{timeouts} = 0;
    return 1;
}

sub send_nak {
    my $self = $_[0];
    _log('sending timeout (', $self->{timeouts}, ')');
    $self->modem->atsend( chr(Xmodem::Constants::nak) );

    my $received = $self->modem->answer( undef, TIMEOUT_CHECKSUM );

    _log('[nak_dump] received [', unpack('H*',$received), '] data');

    $self->modem->port->write_drain();
    $self->{timeouts}++;
    return 1;
}

sub send_timeout {
    my $self = $_[0];
    _log('sending timeout (', $self->{timeouts}, ')');
    $self->modem->atsend( chr(Xmodem::Constants::nak) );
    $self->modem->port->write_drain();
    $self->{timeouts}++;
    return 1;
}

sub timeouts {
    my $self = $_[0];
    $self->{timeouts};
}

sub _log {
    print STDERR @_, "\n" if $DEBUG
}

1;

=head1 NAME

Device::Modem::Protocol::Xmodem

=head1 Xmodem::Block

Class that represents a single Xmodem data block.

=head2 Synopsis

	my $b = Xmodem::Block->new( 1, 'My Data...<until-128-chars>...' );
	if( defined $b ) {
		# Ok, block instanced, verify its checksum
		if( $b->verify( 'checksum', <my_chksum> ) ) {
			...
		} else {
			...
		}
	} else {
		# No block
	}

	# Calculate checksum, crc16, 32, ...
	$crc16 = $b->crc16();
	$crc32 = $b->crc32();
	$chksm = $b->checksum();

=head1 Xmodem::Buffer

Class that implements an Xmodem receive buffer of data blocks. Every block of data
is represented by a C<Xmodem::Block> object.

Blocks can be B<push>ed and B<pop>ped from the buffer. You can retrieve the B<last>
block, or the list of B<blocks> from buffer.

=head2 Synopsis

	my $buf = Xmodem::Buffer->new();
	my $b1  = Xmodem::Block->new(1, 'Data...');

	$buf->push($b1);

	my $b2  = Xmodem::Block->new(2, 'More data...');
	$buf->push($b2);

	my $last_block = $buf->last();

	print 'now I have ', scalar($buf->blocks()), ' in the buffer';

	# TODO document replace() function ???

=head1 Xmodem::Constants

Package that contains all useful Xmodem protocol constants used in handshaking and
data blocks encoding procedures

=head2 Synopsis

	Xmodem::Constants::soh ........... 'start of header'
	Xmodem::Constants::eot ........... 'end of trasmission'
	Xmodem::Constants::ack ........... 'acknowlegded'
	Xmodem::Constants::nak ........... 'not acknowledged'
	Xmodem::Constants::can ........... 'cancel'
	Xmodem::Constants::C   ........... `C' ASCII char

	Xmodem::Constants::XMODEM ........ basic xmodem protocol
	Xmodem::Constants::XMODEM_1K ..... xmodem protocol with 1k blocks
	Xmodem::Constants::XMODEM_CRC .... xmodem protocol with CRC checks

	Xmodem::Constants::CHECKSUM ...... type of block checksum
	Xmodem::Constants::CRC16 ......... type of block crc16
	Xmodem::Constants::CRC32 ......... type of block crc32
	
=head1 Xmodem::Receiver

Control class to initiate and complete a C<X-modem> file transfer in receive mode

=head2 Synopsis

	my $recv = Xmodem::Receiver->new(
 		modem    => {Device::Modem object},
		filename => 'name of file',
		XXX protocol => 'xmodem' | 'xmodem-crc', | 'xmodem-1k'
	);

	$recv->run();

=head2 Object methods

=over 4

=item abort_transfer()

Sends a B<cancel> char (C<can>), that signals to sender that transfer is aborted. This is
issued if we receive a bad block number, which usually means we got a bad line.

=item modem()

Returns the underlying L<Device::Modem> object.

=item receive_message()

Retreives message from modem and if a block is detected it breaks it into appropriate
parts.

=item run()

Starts a new transfer until file receive is complete. The only parameter accepted
is the (optional) local filename to be written.

=item send_ack()

Sends an acknowledge (C<ack>) char, to signal that we received and stored a correct block
Resets count of timeouts and returns the C<Xmodem::Block> object of the data block
received.

=item send_timeout()

Sends a B<timeout> (C<nak>) char, to signal that we received a bad block header (either
a bad start char or a bad block number), or a bad data checksum. Increments count
of timeouts and at ten timeouts, aborts transfer.

=back

=head2 See also

=over 4

=item - L<Device::Modem>

=back