/[RFID]/3m-810.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /3m-810.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 46 - (show annotations)
Tue Jun 23 13:50:13 2009 UTC (14 years, 10 months ago) by dpavlin
File MIME type: text/plain
File size: 17679 byte(s)
parse all get variables

1 #!/usr/bin/perl
2
3 use Device::SerialPort qw (:STAT);
4 use strict;
5 use warnings;
6
7 use Data::Dump qw/dump/;
8 use Carp qw/confess/;
9 use Getopt::Long;
10 use File::Slurp;
11 use JSON;
12
13 use IO::Socket::INET;
14
15 my $meteor_server = '192.168.1.13:4671';
16 my $meteor_fh;
17
18 sub meteor {
19 my @a = @_;
20 push @a, scalar localtime() if $a[0] =~ m{^info};
21
22 if ( ! defined $meteor_fh ) {
23 if ( $meteor_fh =
24 IO::Socket::INET->new(
25 PeerAddr => $meteor_server,
26 Timeout => 1,
27 )
28 ) {
29 warn "# meteor connected to $meteor_server";
30 } else {
31 warn "can't connect to meteor $meteor_server: $!";
32 $meteor_fh = 0;
33 }
34 }
35
36 if ( $meteor_fh ) {
37 warn ">> meteor ",dump( @a );
38 print $meteor_fh "ADDMESSAGE test ",join('|',@a),"\n"
39 }
40 }
41
42 my $listen_port = 9000; # pick something not in use
43 sub http_server {
44
45 my $server = IO::Socket::INET->new(
46 Proto => 'tcp',
47 LocalPort => $listen_port,
48 Listen => SOMAXCONN,
49 Reuse => 1
50 );
51
52 die "can't setup server" unless $server;
53
54 print "Server $0 accepting clients at http://localhost:$listen_port/\n";
55
56 sub static {
57 my ($client,$path) = @_;
58
59 $path = "www/$path";
60
61 return unless -e $path;
62
63 my $type = 'text/plain';
64 $type = 'text/html' if $path =~ m{\.htm};
65 $type = 'application/javascript' if $path =~ m{\.js};
66
67 print $client "HTTP/1.0 200 OK\r\nContent-Type: $type\r\n\r\n";
68 open(my $html, $path);
69 while(<$html>) {
70 print $client $_;
71 }
72 close($html);
73
74 return $path;
75 }
76
77 while (my $client = $server->accept()) {
78 $client->autoflush(1);
79 my $request = <$client>;
80
81 warn "<< $request\n";
82
83 if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
84 my $method = $1;
85 my $param;
86 if ( $method =~ s{\?(.+)}{} ) {
87 foreach my $p ( split(/[&;]/, $1) ) {
88 my ($n,$v) = split(/=/, $p, 2);
89 $param->{$n} = $v;
90 }
91 warn "<< param: ",dump( $param );
92 }
93 if ( my $path = static( $client,$1 ) ) {
94 warn ">> $path";
95 } elsif ( $method =~ m{/scan} ) {
96 my $tags = scan_for_tags();
97 my $json = {};
98 map {
99 my $d = decode_tag($_);
100 $d->{sid} = $_;
101 push @{ $json->{tags} }, $d;
102 } keys %$tags;
103 print $client "HTTP/1.0 200 OK\r\nContent-Type: application/x-javascript\r\n\r\n",
104 $param->{callback}, "(", to_json($json), ")\r\n";
105 } else {
106 print $client "HTTP/1.0 404 Unkown method\r\n";
107 }
108 } else {
109 print $client "HTTP/1.0 500 No method\r\n";
110 }
111 close $client;
112 }
113
114 die "server died";
115 }
116
117 my $debug = 0;
118
119 my $device = "/dev/ttyUSB0";
120 my $baudrate = "19200";
121 my $databits = "8";
122 my $parity = "none";
123 my $stopbits = "1";
124 my $handshake = "none";
125
126 my $program_path = './program/';
127 my $secure_path = './secure/';
128
129 # http server
130 my $http_server = 1;
131
132 # 3M defaults: 8,4
133 my $max_rfid_block = 16;
134 my $read_blocks = 8;
135
136 my $response = {
137 'd500090400110a0500027250' => 'version?',
138 'd60007fe00000500c97b' => 'no tag in range',
139
140 'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
141 'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
142 'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
143
144 'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
145 'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
146 };
147
148 GetOptions(
149 'd|debug+' => \$debug,
150 'device=s' => \$device,
151 'baudrate=i' => \$baudrate,
152 'databits=i' => \$databits,
153 'parity=s' => \$parity,
154 'stopbits=i' => \$stopbits,
155 'handshake=s' => \$handshake,
156 'meteor=s' => \$meteor_server,
157 'http-server!' => \$http_server,
158 ) or die $!;
159
160 my $verbose = $debug > 0 ? $debug-- : 0;
161
162 =head1 NAME
163
164 3m-810 - support for 3M 810 RFID reader
165
166 =head1 SYNOPSIS
167
168 3m-810.pl --device /dev/ttyUSB0
169
170 =head1 DESCRIPTION
171
172 Communicate with 3M 810 RFID reader and document it's protocol
173
174 =head1 SEE ALSO
175
176 L<Device::SerialPort(3)>
177
178 L<perl(1)>
179
180 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
181
182 =head1 AUTHOR
183
184 Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
185
186 =head1 COPYRIGHT AND LICENSE
187
188 This program is free software; you may redistribute it and/or modify
189 it under the same terms ans Perl itself.
190
191 =cut
192
193 my $tags_data;
194 my $visible_tags;
195
196 my $item_type = {
197 1 => 'Book',
198 6 => 'CD/CD ROM',
199 2 => 'Magazine',
200 13 => 'Book with Audio Tape',
201 9 => 'Book with CD/CD ROM',
202 0 => 'Other',
203
204 5 => 'Video',
205 4 => 'Audio Tape',
206 3 => 'Bound Journal',
207 8 => 'Book with Diskette',
208 7 => 'Diskette',
209 };
210
211 warn "## known item type: ",dump( $item_type ) if $debug;
212
213 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
214 warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
215 $handshake=$port->handshake($handshake);
216 $baudrate=$port->baudrate($baudrate);
217 $databits=$port->databits($databits);
218 $parity=$port->parity($parity);
219 $stopbits=$port->stopbits($stopbits);
220
221 print "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
222
223 # Just in case: reset our timing and buffers
224 $port->lookclear();
225 $port->read_const_time(100);
226 $port->read_char_time(5);
227
228 # Turn on parity checking:
229 #$port->stty_inpck(1);
230 #$port->stty_istrip(1);
231
232 # initial hand-shake with device
233
234 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
235 'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
236 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
237 print "hardware version $hw_ver\n";
238 meteor( 'info', "Found reader hardware $hw_ver" );
239 });
240
241 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
242 'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
243
244 sub scan_for_tags {
245
246 my @tags;
247
248 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags, retry $_",
249 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
250 my $rest = shift || die "no rest?";
251 my $nr = ord( substr( $rest, 0, 1 ) );
252
253 if ( ! $nr ) {
254 print "no tags in range\n";
255 update_visible_tags();
256 meteor( 'info-none-in-range' );
257 $tags_data = {};
258 } else {
259
260 my $tags = substr( $rest, 1 );
261
262 my $tl = length( $tags );
263 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
264
265 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
266 warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
267 print "$nr tags in range: ", join(',', @tags ) , "\n";
268
269 meteor( 'info-in-range', join(' ',@tags));
270
271 update_visible_tags( @tags );
272 }
273 }
274 );
275
276 warn "## tags: ",dump( @tags );
277 return $tags_data;
278
279 }
280
281 # start scanning for tags
282
283 if ( $http_server ) {
284 http_server;
285 } else {
286 scan_for_tags while 1;
287 }
288
289 die "over and out";
290
291 sub update_visible_tags {
292 my @tags = @_;
293
294 my $last_visible_tags = $visible_tags;
295 $visible_tags = {};
296
297 foreach my $tag ( @tags ) {
298 if ( ! defined $last_visible_tags->{$tag} ) {
299 if ( defined $tags_data->{$tag} ) {
300 # meteor( 'in-range', $tag );
301 } else {
302 meteor( 'read', $tag );
303 read_tag( $tag );
304 }
305 $visible_tags->{$tag}++;
306 } else {
307 warn "## using cached data for $tag" if $debug;
308 }
309 delete $last_visible_tags->{$tag}; # leave just missing tags
310
311 if ( -e "$program_path/$tag" ) {
312 meteor( 'write', $tag );
313 write_tag( $tag );
314 }
315 if ( -e "$secure_path/$tag" ) {
316 meteor( 'secure', $tag );
317 secure_tag( $tag );
318 }
319 }
320
321 foreach my $tag ( keys %$last_visible_tags ) {
322 my $data = delete $tags_data->{$tag};
323 print "removed tag $tag with data ",dump( $data ),"\n";
324 meteor( 'removed', $tag );
325 }
326
327 warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
328 }
329
330 my $tag_data_block;
331
332 sub read_tag_data {
333 my ($start_block,$rest) = @_;
334 die "no rest?" unless $rest;
335
336 my $last_block = 0;
337
338 warn "## DATA [$start_block] ", dump( $rest ) if $debug;
339 my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
340 my $blocks = ord(substr($rest,8,1));
341 $rest = substr($rest,9); # leave just data blocks
342 foreach my $nr ( 0 .. $blocks - 1 ) {
343 my $block = substr( $rest, $nr * 6, 6 );
344 warn "## block ",as_hex( $block ) if $debug;
345 my $ord = unpack('v',substr( $block, 0, 2 ));
346 my $expected_ord = $nr + $start_block;
347 warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
348 my $data = substr( $block, 2 );
349 die "data payload should be 4 bytes" if length($data) != 4;
350 warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
351 $tag_data_block->{$tag}->[ $ord ] = $data;
352 $last_block = $ord;
353 }
354 $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
355
356 my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
357 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
358
359 return $last_block + 1;
360 }
361
362 sub decode_tag {
363 my $tag = shift;
364
365 my $data = $tags_data->{$tag} || die "no data for $tag";
366
367 my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
368 my $hash = {
369 u1 => $u1,
370 u2 => $u2,
371 set => ( $set_item & 0xf0 ) >> 4,
372 total => ( $set_item & 0x0f ),
373
374 type => $type,
375 content => $content,
376
377 branch => $br_lib >> 20,
378 library => $br_lib & 0x000fffff,
379
380 custom => $custom,
381 };
382
383 return $hash;
384 }
385
386 sub read_tag {
387 my ( $tag ) = @_;
388
389 confess "no tag?" unless $tag;
390
391 print "read_tag $tag\n";
392
393 my $start_block = 0;
394
395 while ( $start_block < $max_rfid_block ) {
396
397 cmd(
398 sprintf( "D6 00 0D 02 $tag %02x %02x ffff", $start_block, $read_blocks ),
399 "read $tag offset: $start_block blocks: $read_blocks",
400 "D6 00 1F 02 00", sub { # $tag 03 00 00 04 11 00 01 01 00 31 32 33 34 02 00 35 36 37 38 531F\n";
401 $start_block = read_tag_data( $start_block, @_ );
402 warn "# read tag upto $start_block\n";
403 },
404 "D6 00 0F FE 00 00 05 01 $tag 941A", sub {
405 print "FIXME: tag $tag ready? (expected block read instead)\n";
406 },
407 );
408
409 }
410
411 my $security;
412
413 cmd(
414 "D6 00 0B 0A $tag 1234", "check security $tag",
415 "D6 00 0D 0A 00", sub {
416 my $rest = shift;
417 my $from_tag;
418 ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
419 die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
420 $security = as_hex( $security );
421 warn "# SECURITY $tag = $security\n";
422 }
423 );
424
425 print "TAG $tag ", dump(decode_tag( $tag ));
426 }
427
428 sub write_tag {
429 my ($tag) = @_;
430
431 my $path = "$program_path/$tag";
432
433 my $data = read_file( $path );
434 my $hex_data;
435
436 if ( $data =~ s{^hex\s+}{} ) {
437 $hex_data = $data;
438 $hex_data =~ s{\s+}{}g;
439 } else {
440
441 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
442
443 my $max_len = $max_rfid_block * 4;
444
445 if ( length($data) > $max_len ) {
446 $data = substr($data,0,$max_len);
447 warn "strip content to $max_len bytes\n";
448 }
449
450 $hex_data = unpack('H*', $data);
451 }
452
453 my $len = length($hex_data) / 2;
454 # pad to block size
455 $hex_data .= '00' x ( 4 - $len % 4 );
456 my $blocks = sprintf('%02x', length($hex_data) / 4);
457
458 print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
459
460 cmd(
461 "d6 00 ff 04 $tag 00 $blocks 00 $hex_data ffff", "write $tag",
462 "d6 00 0d 04 00 $tag $blocks afb1", sub { assert() },
463 ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
464
465 my $to = $path;
466 $to .= '.' . time();
467
468 rename $path, $to;
469 print ">> $to\n";
470
471 delete $tags_data->{$tag}; # force re-read of tag
472 }
473
474 sub secure_tag {
475 my ($tag) = @_;
476
477 my $path = "$secure_path/$tag";
478 my $data = substr(read_file( $path ),0,2);
479
480 cmd(
481 "d6 00 0c 09 $tag $data 1234", "secure $tag -> $data",
482 "d6 00 0c 09 00 $tag 1234", sub { assert() },
483 );
484
485 my $to = $path;
486 $to .= '.' . time();
487
488 rename $path, $to;
489 print ">> $to\n";
490 }
491
492 exit;
493
494 for ( 1 .. 3 ) {
495
496 # ++-->type 00-0a
497 # D6 00 2A 04 E00401003123AA26 00 07 00 04 11 00 01 31 31 31 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1C D4
498 # D6 00 2A 04 E0 04 01 00 31 23 AA 26 00 07 00 04 11 00 06 32 32 32 32 32 32 32 32 32 32 32 00 00 00 00 00 00 00 00 00 00 00 00 00 32B7
499 # D6 00 2A 04 E0 04 01 00 31 23 AA 26 00 07 00 04 11 00 02 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 00 00 00 00 00 00 00 00 42 1F
500
501 cmd(' D6 00 2A 04 E00401003123AA26 00 07 00 04 11 00 01 30 30 30 30 30 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8843', "write offset 0, block: 7 -- 0000000000 $_" );
502 warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
503
504 }
505 warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
506
507 cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
508
509 cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
510 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
511 cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
512 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
513
514 cmd('D6 00 26 04 E00401003123AA26 00 06 00 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 A98B', 'blank offset: 0 blocks: 6',
515 'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
516
517 undef $port;
518 print "Port closed\n";
519
520 sub writechunk
521 {
522 my $str=shift;
523 my $count = $port->write($str);
524 my $len = length($str);
525 die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
526 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
527 }
528
529 sub as_hex {
530 my @out;
531 foreach my $str ( @_ ) {
532 my $hex = unpack( 'H*', $str );
533 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
534 $hex =~ s/\s+$//;
535 push @out, $hex;
536 }
537 return join(' | ', @out);
538 }
539
540 sub read_bytes {
541 my ( $len, $desc ) = @_;
542 my $data = '';
543 while ( length( $data ) < $len ) {
544 my ( $c, $b ) = $port->read(1);
545 die "no bytes on port: $!" unless defined $b;
546 #warn "## got $c bytes: ", as_hex($b), "\n";
547 $data .= $b;
548 }
549 $desc ||= '?';
550 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
551 return $data;
552 }
553
554 our $assert;
555
556 # my $rest = skip_assert( 3 );
557 sub skip_assert {
558 assert( 0, shift );
559 }
560
561 sub assert {
562 my ( $from, $to ) = @_;
563
564 $from ||= 0;
565 $to = length( $assert->{expect} ) if ! defined $to;
566
567 my $p = substr( $assert->{payload}, $from, $to );
568 my $e = substr( $assert->{expect}, $from, $to );
569 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
570
571 # return the rest
572 return substr( $assert->{payload}, $to );
573 }
574
575 use Digest::CRC;
576
577 sub crcccitt {
578 my $bytes = shift;
579 my $crc = Digest::CRC->new(
580 # midified CCITT to xor with 0xffff instead of 0x0000
581 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
582 ) or die $!;
583 $crc->add( $bytes );
584 pack('n', $crc->digest);
585 }
586
587 # my $checksum = checksum( $bytes );
588 # my $checksum = checksum( $bytes, $original_checksum );
589 sub checksum {
590 my ( $bytes, $checksum ) = @_;
591
592 my $len = ord(substr($bytes,2,1));
593 my $len_real = length($bytes) - 1;
594
595 if ( $len_real != $len ) {
596 print "length wrong: $len_real != $len\n";
597 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
598 }
599
600 my $xor = crcccitt( substr($bytes,1) ); # skip D6
601 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
602
603 if ( defined $checksum && $xor ne $checksum ) {
604 print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
605 return $bytes . $xor;
606 }
607 return $bytes . $checksum;
608 }
609
610 our $dispatch;
611
612 sub readchunk {
613 # sleep 1; # FIXME remove
614
615 # read header of packet
616 my $header = read_bytes( 2, 'header' );
617 my $length = read_bytes( 1, 'length' );
618 my $len = ord($length);
619 my $data = read_bytes( $len, 'data' );
620
621 my $payload = substr( $data, 0, -2 );
622 my $payload_len = length($data);
623 warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
624
625 my $checksum = substr( $data, -2, 2 );
626 checksum( $header . $length . $payload , $checksum );
627
628 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
629
630 $assert->{len} = $len;
631 $assert->{payload} = $payload;
632
633 my $full = $header . $length . $data; # full
634 # find longest match for incomming data
635 my ($to) = grep {
636 my $match = substr($payload,0,length($_));
637 m/^\Q$match\E/
638 } sort { length($a) <=> length($b) } keys %$dispatch;
639 warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
640
641 if ( defined $to ) {
642 my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
643 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
644 $dispatch->{ $to }->( $rest );
645 } else {
646 print "NO DISPATCH for ",dump( $full ),"\n";
647 }
648
649 return $data;
650 }
651
652 sub str2bytes {
653 my $str = shift || confess "no str?";
654 my $b = $str;
655 $b =~ s/\s+//g;
656 $b =~ s/(..)/\\x$1/g;
657 $b = "\"$b\"";
658 my $bytes = eval $b;
659 die $@ if $@;
660 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
661 return $bytes;
662 }
663
664 sub cmd {
665 my $cmd = shift || confess "no cmd?";
666 my $cmd_desc = shift || confess "no description?";
667 my @expect = @_;
668
669 my $bytes = str2bytes( $cmd );
670
671 # fix checksum if needed
672 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
673
674 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
675 $assert->{send} = $cmd;
676 writechunk( $bytes );
677
678 while ( @expect ) {
679 my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
680 my $coderef = shift @expect || confess "no coderef?";
681 confess "not coderef" unless ref $coderef eq 'CODE';
682
683 next if defined $dispatch->{ $pattern };
684
685 $dispatch->{ substr($pattern,3) } = $coderef;
686 warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
687 }
688
689 readchunk;
690 }
691

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26