File Coverage

blib/lib/Net/OnlineCode.pm
Criterion Covered Total %
statement 107 209 51.2
branch 19 56 33.9
condition 0 6 0.0
subroutine 19 31 61.2
pod 1 19 5.2
total 146 321 45.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Net::OnlineCode;
4              
5             # play nicely as a CPAN module
6              
7 4     4   83783 use strict;
  4         10  
  4         146  
8 4     4   22 use warnings;
  4         7  
  4         147  
9 4     4   21 use vars qw(@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION);
  4         7  
  4         1042  
10              
11             require Exporter;
12              
13             @ISA = qw(Exporter);
14              
15             our @export_xor = qw (xor_strings safe_xor_strings fast_xor_strings);
16             our @export_default = qw();
17              
18             %EXPORT_TAGS = ( all => [ @export_default, @export_xor ],
19             xor => [ @export_xor ],
20             );
21             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
22             @EXPORT = ();
23             $VERSION = '0.02';
24              
25             # Use XS for fast xors (TODO: make this optional)
26             require XSLoader;
27             XSLoader::load('Net::OnlineCode', $VERSION);
28              
29             # on to our stuff ...
30              
31 4     4   23 use constant DEBUG => 0;
  4         8  
  4         396  
32              
33              
34             # Codec parameters
35             # q is the number of message blocks that each auxiliary block will
36             # link to
37             # e epsilon, the degree of "suboptimality". Unlike Reed-Solomon or
38             # Rabin's Information Dispersal Algorithm, Online Codes are not
39             # optimal. This means that slightly more data needs to be generated
40             # than either of these two codes. Also, whereas optimal codes
41             # guarantee that a certain fraction of the "check" blocks/digits
42             # suffice to reconstruct the original message, online codes only
43             # guarantee that it can be reconstructed with a certain
44             # probability
45             #
46             # Together with the number of blocks, n, these two variables define
47             # the online code such that (1+qe)n check blocks are sufficient to
48             # reconstruct the original message with a probability of 1 - (e/2) **
49             # (q+1).
50             #
51              
52 4     4   31 use Carp;
  4         7  
  4         365  
53 4     4   4060 use POSIX qw(ceil floor);
  4         35364  
  4         60  
54 4     4   11108 use Digest::SHA qw(sha1 sha1_hex);
  4         20976  
  4         443  
55 4     4   35 use Fcntl;
  4         8  
  4         33940  
56              
57              
58             # Constructor for the base class
59             #
60             # This takes the three parameters that define the Online Code scheme,
61             # corrects the value of epsilon if needed (see below) and then derives
62             # the following:
63             #
64             # * max degree variable (F)
65             # * number of auxiliary blocks (0.55 *qen)
66             # * probability distribution p_1, p2, ... , p_F
67             #
68             # All these are purely deterministic.
69              
70             sub new {
71              
72 2     2 1 15 my $class = shift;
73             # The default parameters used here for q and e (epsilon) are as
74             # suggested in the paper "Rateless Codes and Big Downloads" by Petar
75             # Maymounkov and David Maziere. Note that the value of e may be
76             # overridden with a higher value if the lower value doesn't satisfy
77             # max_degree(epsilon) > ceil(0.55 * q.e.mblocks)
78 2         20 my %args = (
79             e => 0.01,
80             q => 3,
81             mblocks => undef,
82             expand_aux => 1,
83             e_warning => 0,
84              
85             # We don't use or store any RNG parameter that's been
86             # passed into the constructor.
87             @_
88             );
89              
90              
91 2         10 my ($q,$e,$mblocks) = @args{qw(q e mblocks)};
92              
93 2 50       9 unless (defined $args{mblocks}) {
94 0         0 carp __PACKAGE__ . ": mblocks => (# message blocks) must be set\n";
95 0         0 return undef;
96             }
97              
98 2         4 print "Net::OnlineCode mblocks = $mblocks\n" if DEBUG;
99              
100 2         4 my $P = undef;
101 2         4 my $e_changed = 0;
102              
103             # how many auxiliary blocks would this scheme need?
104 2         9 my $ablocks = _count_auxiliary($q,$e,$mblocks);
105              
106             # does epsilon value need updating?
107 2         9 my $f = _max_degree($e);
108              
109             # try an alternative way of calculating F:
110             # $f = $mblocks + $ablocks if $f > $mblocks + $ablocks;
111              
112 2 100       10 if ($f > $mblocks + $ablocks) {
113              
114 1         2 $e_changed = 1;
115              
116 1 50       5 if ($args{e_warning}) {
117 0         0 print "E CHANGED!!\nWas: $e\n";
118 0         0 print "Gave F value of $f\n";
119             }
120              
121             # use a binary search to find a new epsilon such that
122             # get_max_degree($epsilon) <= mblocks + ablocks (ie, n)
123 1         3 my $epsilon = $e;
124              
125             local *eval_f = sub {
126 11     11   15 my $t = shift;
127 11         44 return _max_degree(1/(1 + exp(-$t)));
128 1         7 };
129              
130 1         5 my $l = -log(1/$e - 1);
131 1         3 my $r = $l + 1;
132              
133             # expand right side of search until we get F <= n'
134 1         4 while (eval_f($r) > $mblocks + $ablocks) {
135             # $r = $l + ($r - $l) * 2;
136 1         3 $r = 2 * $r - $l;
137             }
138              
139             # binary search between left and right to find a suitable lower
140             # value of epsilon still satisfying F <= n'
141 1         6 while ($r - $l > 0.01) {
142 8         12 my $m = ($l + $r) / 2;
143 8 100       30 if (eval_f($m) > $mblocks + $ablocks) {
144 3         8 $l = $m;
145             } else {
146 5         51 $r = $m;
147             }
148             }
149              
150             # update e and ablocks
151 1         4 $epsilon = 1/(1 + exp(-$r));
152 1         3 $f = eval_f($r);
153             #$f=_max_degree($epsilon);
154 1 50       5 carp __PACKAGE__ . ": increased epsilon value from $e to $epsilon\n"
155             if $args{e_warning};
156 1         2 $e = $epsilon;
157 1         3 $ablocks = _count_auxiliary($q,$e,$mblocks);
158              
159 1 50       8 if ($args{e_warning}) {
160              
161 0         0 print "Is now: $e\n";
162 0         0 print "New F: $f\n";
163             }
164              
165             }
166              
167             # how many auxiliary blocks would this scheme need?
168              
169             # calculate the probability distribution
170 2         4 print "new: mblocks=$mblocks, ablocks=$ablocks, q=$q\n" if DEBUG;
171 2         9 $P = _probability_distribution($mblocks + $ablocks,$e);
172              
173 2 50       10 die "Wrong number of elements in probability distribution (got "
174             . scalar(@$P) . ", expecting $f)\n"
175             unless @$P == $f;
176              
177 2         24 my $self = { q => $q, e => $e, f => $f, P => $P,
178             mblocks => $mblocks, ablocks => $ablocks,
179             chblocks => 0, expand_aux=> $args{expand_aux},
180             e_changed => $e_changed, unique => {} };
181              
182 2         3 print "expand_aux => $self->{expand_aux}\n" if DEBUG;
183              
184 2         17 bless $self, $class;
185              
186             }
187              
188             # while it probably doesn't matter too much to the encoder whether the
189             # supplied e value needed to be changed, if the receiver plugs the
190             # received value of e into the constructor and it ends up changing,
191             # there will be a problem with receiving the file.
192             sub e_changed {
193 0     0 0 0 return shift ->{e_changed};
194             }
195              
196             # convenience accessor functions
197             sub get_mblocks { # count message blocks; passed into new
198 2     2 0 13 return shift -> {mblocks};
199             }
200              
201             sub get_ablocks { # count auxiliary blocks; set in new
202 2     2 0 10 return shift -> {ablocks};
203             }
204              
205             sub get_coblocks { # count composite blocks
206 2     2 0 4 my $self = shift;
207 2         11 return $self->{mblocks} + $self->{ablocks};
208             }
209              
210             # count checkblocks
211             sub get_chblocks {
212 0     0 0 0 return shift->{chblocks}
213             }
214              
215             sub get_q { # q == reliability factor
216 0     0 0 0 return shift -> {q};
217             }
218              
219             sub get_e { # e == suboptimality factor
220 2     2 0 1219 return shift -> {e};
221             }
222              
223             sub get_epsilon { # epsilon == e, as above
224 0     0 0 0 return shift -> {e};
225             }
226              
227             sub get_f { # f == max (check block) degree
228 2     2 0 28 return shift -> {f};
229             }
230              
231             sub get_P { # P == probability distribution
232 0     0 0 0 return shift -> {P}; # (array ref)
233             }
234              
235              
236             # "Private" routines
237              
238             # calculate how many auxiliary blocks need to be generated for a given
239             # code setup
240             sub _count_auxiliary {
241 3     3   5 my ($q, $e, $n) = @_;
242              
243 3         32 my $count = int(ceil(0.55 * $q * $e * $n));
244 3         5 my $delta = 0.55 * $e;
245              
246 3         4 warn "failure probability " . ($delta ** $q) . "\n" if DEBUG;
247             #$count = int(ceil($q * $delta * $n));
248              
249 3 50       9 if ($count < $q) {
250             #$count = $q; # ???
251             #warn "updated _count_auxiliary output value to $q\n";
252             }
253 3         7 return $count;
254             }
255              
256             # The max degree specifies the maximum number of blocks to be XORed
257             # together. This parameter is named F.
258             sub _max_degree {
259              
260 15     15   18 my $epsilon = shift;
261              
262 15         49 my $quotient = (2 * log ($epsilon / 2)) /
263             (log (1 - $epsilon / 2));
264              
265 15         18 my $delta = 0.55 * $epsilon;
266             #$quotient = (log ($epsilon) + log($delta)) / (log (1 - $epsilon));
267              
268 15         60 return int(ceil($quotient));
269             }
270              
271             # Functions relating to probability distribution
272             #
273             # From the wikipedia page:
274             #
275             # http://en.wikipedia.org/wiki/Online_codes
276             #
277             # During the inner coding step the algorithm selects some number of
278             # composite messages at random and XORs them together to form a check
279             # block. In order for the algorithm to work correctly, both the number
280             # of blocks to be XORed together and their distribution over composite
281             # blocks must follow a particular probability distribution.
282             #
283             # Consult the references for the implementation details.
284             #
285             # The probability distribution is designed to map a random number in
286             # the range [0,1) and return a degree i between 1 and F. The
287             # probability distribution depends on a single input, n, which is the
288             # number of blocks in the original message. The fixed values for q and
289             # epsilon are also used.
290             #
291             # This code includes two changes from that described in the wikipedia
292             # page.
293             #
294             # 1) Rather than returning an array of individual probabilities p_i,
295             # the array includes the cumulative probabilities. For example, if
296             # the p_i probabilities were:
297             # (0.1, 0.2, 0.3, 0.2, 0.1, 0.1)
298             # then the returned array would be:
299             # (0.1, 0.3, 0.6, 0.8, 0.9, 1) (last element always has value 1)
300             # This is done simply to make selecting a value based on the random
301             # number more efficient, but the underlying probability distribution
302             # is the same.
303             # 2) Handling edge cases. These are:
304             # a) the case where n = 1; and
305             # b) the case where F > n
306             # In both cases, the default value for epsilon cannot be used, so a
307             # more suitable value is calculated.
308             #
309             # The return value is an array containing:
310             #
311             # * the max degree F
312             # * a possibly updated value of epsilon
313             # * the F values of the (cumulative) probability distribution
314              
315             sub _probability_distribution {
316              
317 2     2   25 my ($nblocks,$epsilon) = @_; # nblocks = number of *composite* blocks!
318              
319             # after code reorganisation, this shouldn't happen:
320 2 50       9 if ($nblocks == 1) {
321 0         0 croak "BUG: " . __PACKAGE__ ." - number of composite blocks = 1\n";
322 0         0 return (1, 0, 1);
323             }
324              
325 2         3 print "generating probability distribution from nblocks $nblocks, e $epsilon\n"
326             if DEBUG;
327              
328 2         5 my $f = _max_degree($epsilon);
329              
330             # after code reorganisation, this shouldn't happen:
331 2 50       10 if ($f > $nblocks) {
332 0         0 croak "BUG: " .__PACKAGE__ . " - epsilon still too small!\n";
333             }
334              
335             # probability distribution
336              
337             # Calculate the sum of the sequence:
338             #
339             # 1 + 1/F
340             # p_1 = 1 - ---------
341             # 1 + e
342             #
343             #
344             # F . (1 - p_1)
345             # p_i = ---------------------
346             # (F - 1) . (i^2 - i)
347             #
348             # Since the i term is the only thing that changes for each p_i, I
349             # optimise the calculation by keeping a fixed term involving only p
350             # and f with a variable one involving i, then dividing as
351             # appropriate.
352              
353 2         6 my $p1 = 1 - (1 + 1/$f)/(1 + $epsilon);
354 2         7 my $pfterm = (1-$p1) * $f / ($f - 1);
355              
356 2 50       6 die "p1 is negative\n" if $p1 < 0;
357              
358             # hard-code simple cases where f = 1 or 2
359 2 50       26 if ($f == 1) {
    50          
360 0         0 return [1];
361             } elsif ($f == 2) {
362 0         0 return [$p1, 1];
363             }
364              
365             # calculate sum(p_i) for 2 <= i < F.
366             # p_i=F is simply set to 1 to avoid rounding errors in the sum
367 2         4 my $sum = $p1;
368 2         4 my @P = ($sum);
369              
370 2         3 my $i = 2;
371 2         6 while ($i < $f) {
372 2515         2916 my $iterm = $i * ($i - 1);
373 2515         2724 my $p_i = $pfterm / $iterm;
374              
375 2515         2574 $sum += $p_i;
376              
377 2515 50       4059 die "p_$i is negative\n" if $p_i < 0;
378              
379 2515         3107 push @P, $sum;
380 2515         4708 $i++;
381             }
382              
383 2         4 if (DEBUG) {
384             # Make sure of the assumption that the sum of terms approaches 1.
385             # If the "rounding error" below is not a very small number, we
386             # know there is a problem with the assumption!
387             my $p_last = $sum + $pfterm / ($f * $f - $f);
388             my $absdiff = abs (1 - $p_last);
389             warn "Absolute difference of 1,sum to p_F = $absdiff\n";
390             }
391              
392 2         450 return [@P,1];
393             }
394              
395              
396             # Fisher-Yates shuffle algorithm, based on recipe 4.17 from the Perl
397             # Cookbook. Takes an input array it randomises the order (ie,
398             # shuffles) and then truncates the array to "picks" elements.
399             #
400             # This is much more efficient than the usual approach of "keep picking
401             # new elements until we get k distinct ones" particularly as k
402             # approaches the size of the array. That algorithm could make
403             # exponentially many calls to rand, whereas this just makes one call
404             # per item to be picked.
405              
406             sub fisher_yates_shuffle {
407              
408 0     0 0 0 my ($rng, $array, $picks) = @_;
409              
410             #print "picks is $picks\n";
411              
412 0 0       0 die "fisher_yates_shuffle: 1st arg not an RNG object\n"
413             unless ref($rng);
414              
415 0 0       0 die "fisher_yates_shuffle: 2nd arg not a listref\n"
416             unless ref($array) eq "ARRAY";
417              
418             # Change recipe to pick subset of list
419 0 0 0     0 $picks=scalar(@$array) unless
      0        
420             defined($picks) and $picks >=0 and $picks
421              
422             # algorithm fills picks into the end of the array
423 0         0 my $i=scalar(@$array);
424 0         0 while (--$i >= scalar(@$array) - $picks) {
425 0         0 my $j=int($rng->rand($i + 1)); # range [0,$i]
426             #next if $i==$j; # not worth checking, probably
427 0         0 @$array[$i,$j]=@$array[$j,$i]
428             }
429              
430             # delete unpicked elements from the front of the array
431             # (does nothing if picks == length of the array)
432 0         0 splice @$array, 0, (scalar @$array - $picks);
433             }
434              
435             #
436             # Routine to calculate the auxiliary block -> message block* mapping.
437             # The passed rng object must already have been seeded, and both sender
438             # and receiver should use the same seed. Returns [[..],[..],..]
439             # representing which message blocks each of the auxiliary block is
440             # composed of.
441             #
442              
443             sub auxiliary_mapping {
444              
445 0     0 0 0 my $self = shift;
446 0         0 my $rng = shift;
447              
448 0 0       0 croak "auxiliary_mapping: rng is not a reference\n" unless ref($rng);
449              
450             #print "auxiliary_mapping: entering RNG value: " . ($rng->as_hex). "\n";
451              
452             # hash slices: powerful, but syntax is sometimes confusing
453 0         0 my ($mblocks,$ablocks,$q) = @{$self}{"mblocks","ablocks","q"};
  0         0  
454              
455             # make sure hash(ref) slice above actually did something sensible:
456             # die "weird mblocks/ablocks" unless $nblocks + $aux_blocks >= 2;
457              
458             # I made a big mistake when reading the description for creating aux
459             # blocks. What I implemented first (in the commented-out section
460             # below) was to link each of the auxiliary blocks to q message
461             # blocks. What I should have done was to link each *message block*
462             # to q auxiliary blocks. As a result, it was taking much more than
463             # the expected number of check blocks to decode the message.
464              
465             # as a result of the new algorithm, it makes sense to work out
466             # reciprocal links between message blocks and auxiliary blocks
467             # within the base class. Storing them here won't work out very well,
468             # though: the encoder doesn't care about the message block to aux
469             # block mapping, so it would be a waste of memory, but more
470             # importantly, the decoder object stores all mappings in a private
471             # GraphDecoder object (so duplicating the structure here would be a
472             # waste).
473              
474             # I will make one change to the output, though: instead of just
475             # returning the mappings for the 0.55qen auxiliary blocks, I will
476             # return a list of message block *and* auxiliary block mappings. The
477             # encoder and decoder will have to be changed: encoder immediately
478             # splices the array to remove unwanted message block mappings, while
479             # the decoder will be simplified by only having to pass the full
480             # list to the graph decoder (which will have to be modified
481             # appropriately).
482              
483 0         0 my $aux_mapping = [];
484              
485             # list of empty hashes
486 0         0 my @hashes;
487 0         0 for (0 .. $mblocks + $ablocks -1) { $hashes[$_] = {}; }
  0         0  
488              
489 0         0 for my $msg (0 .. $mblocks - 1) {
490             # list of all aux block indices
491 0         0 my $ab = [$mblocks .. $mblocks + $ablocks -1];
492              
493 0         0 fisher_yates_shuffle($rng, $ab, $q);
494              
495             #print "aux_mapping: shuffled list: " . (join " ", @$ab) . "\n";
496              
497 0         0 foreach my $aux (@$ab) {
498 0         0 $hashes[$aux]->{$msg}=undef;
499 0         0 $hashes[$msg]->{$aux}=undef;
500             }
501             }
502              
503             # convert list of hashes into a list of lists
504 0         0 for my $i (0 .. $mblocks + $ablocks -1) {
505 0         0 print "map $i: " . (join " ", keys %{$hashes[$i]}) . "\n" if DEBUG;
506 0         0 push @$aux_mapping, [ keys %{$hashes[$i]} ];
  0         0  
507             }
508              
509             #print "auxiliary_mapping: leaving RNG value: " . ($rng->as_hex). "\n";
510              
511             # save and return aux_mapping
512 0         0 $self->{aux_mapping} = $aux_mapping;
513             }
514              
515             # Until I get the auto expand_aux working, this will have to do
516             sub blklist_to_msglist {
517              
518 0     0 0 0 my ($self,@xor_list) = @_;
519              
520 0         0 my $mblocks = $self->{mblocks};
521              
522 0         0 my %blocks;
523 0         0 while (@xor_list) {
524 0         0 my $entry = shift(@xor_list);
525 0 0       0 if ($entry < $mblocks) { # is it a message block index?
526             # toggle entry in the hash
527 0 0       0 if (exists($blocks{$entry})) {
528 0         0 delete $blocks{$entry};
529             } else {
530 0         0 $blocks{$entry}= undef;
531             }
532             } else {
533             # aux block : push all message blocks it's composed of
534 0         0 push @xor_list, @{$self->{aux_mapping}->[$entry]};
  0         0  
535             }
536             }
537 0         0 return keys %blocks;
538             }
539              
540             sub blklist_to_chklist {
541              
542 0     0 0 0 my $self = shift;
543 0         0 croak "This method only makes sense when called on a Decoder object!\n";
544             }
545              
546              
547             # non-method sub to toggle a key in a hash
548             sub toggle_key {
549 0     0 0 0 my $href = shift;
550 0         0 my $key = shift;
551              
552 0 0       0 if (exists($href->{$key})) {
553 0         0 delete $href->{$key};
554             } else {
555             # apparently, using key => undef is more space-efficient than
556             # using key => 1 (similar changes made throughout this file)
557 0         0 $href->{$key}=undef;
558             }
559             }
560              
561             # Calculate the composition of a single check block based on the
562             # supplied RNG. Returns a reference to a list of composite blocks
563             # indices.
564              
565             sub checkblock_mapping {
566              
567 0     0 0 0 my $self = shift;
568 0         0 my $rng = shift;
569              
570 0 0       0 croak "rng is not an object reference\n" unless ref($rng);
571              
572 0         0 my $coblocks = $self->get_coblocks;
573 0         0 my $P = $self->{P};
574              
575             # die "Probability distribution has wrong number of terms\n"
576             # unless scalar(@$P) <= $coblocks;
577              
578 0         0 my $check_mapping;
579              
580             # It's possible to generate a check block that is empty. If it only
581             # includes message blocks, then there's no problem. However, if the
582             # expansion of all the auxiliary blocks is equal to the list of
583             # message blocks then two two cancel out. Besides being inefficient
584             # to transmit effectively empty check blocks, it can also cause a
585             # bug in the decoder where it assumes that the expanded list of
586             # blocks is not empty. The solution is the same for both encoder and
587             # decoder (loop until expansion is not empty), so I'm implementing
588             # it here in the base class.
589             #
590             # Note that although this involves expanding auxiliary blocks, for
591             # the moment, I'm ignoring "expand_aux" option and will just return
592             # the unexpanded list. This may change in future once I've had a
593             # chance to look at the problem more closely.
594              
595 0         0 my $mblocks = $self->{mblocks}; # quicker than calling is_message
596 0         0 my %expanded=();
597 0         0 my $tries = 0;
598 0         0 my $key; # used for uniqueness-checking
599 0         0 until (keys %expanded) {
600              
601 0         0 ++$tries;
602              
603             # use weighted distribution to find how many blocks to link
604 0         0 my $i = 0;
605 0         0 my $r = $rng->rand;
606 0         0 ++$i while($r > $P->[$i]); # terminates since r < P[last]
607 0         0 ++$i;
608              
609             #die "went past end of probability list\n" if $i > @$P;
610              
611             #warn "picked $i values for checkblock (from $coblocks)\n";
612              
613             # select i composite blocks uniformly
614 0         0 $check_mapping = [ (0 .. $coblocks-1) ];
615 0         0 fisher_yates_shuffle($rng, $check_mapping, $i);
616              
617             # check block for uniqueness before expansion
618 0         0 $key = join " ", sort { $a <=> $b } @$check_mapping;
  0         0  
619 0 0       0 if (exists $self->{unique}->{$key}) {
620 0         0 warn "quashed duplicate check block\n" if DEBUG;
621 0         0 next;
622             }
623              
624             # print "check_mapping: raw composite block list: ",
625             # (join " ", @$check_mapping), "\n";
626              
627             # check expanded list
628 0         0 my @xor_list = @$check_mapping;
629 0         0 while (@xor_list) {
630 0         0 my $entry = shift @xor_list;
631 0 0       0 if ($entry < $mblocks) { # is it a message block index?
632             # toggle entry
633 0         0 toggle_key (\%expanded, $entry);
634             } else {
635             # aux block : push all message blocks it's composed of. Since
636             # we're sharing the aux_mapping structure with the decoder, we
637             # have to filter out any entries it's putting in (ie,
638             # composite blocks) or we can get into an infinite loop
639 0         0 my @expanded = grep { $_ < $mblocks } @{$self->{aux_mapping}->[$entry]};
  0         0  
  0         0  
640             #print "check_mapping: expanding aux block $entry to ",
641             # (join " ", @expanded), "\n";
642 0         0 push @xor_list, @expanded;
643             }
644             }
645             }
646              
647             # prevent generating this block again
648 0         0 $self->{unique}->{$key}=undef;
649              
650             #warn "Created unique, non-empty checkblock on try $tries\n" if $tries>1;
651              
652 0 0       0 die "fisher_yates_shuffle: created empty check block\n!" unless @$check_mapping;
653              
654 0         0 ++($self->{chblocks});
655              
656 0         0 print "CHECKblock mapping: " . (join " ", @$check_mapping) . "\n" if DEBUG;
657              
658 0         0 return $check_mapping;
659              
660             }
661              
662             # non-method sub for xoring a source string (passed by reference) with
663             # one or more target strings. I may reimplement this using XS later to
664             # make it more efficient, but will keep a pure-perl version with this
665             # name.
666             sub safe_xor_strings {
667              
668 4     4 0 20 my $source = shift;
669              
670             # die if user forgot to pass in a reference (to allow updating) or
671             # called $self->safe_xor_strings by mistake
672 4 50       16 croak "xor_strings: arg 1 should be a reference to a SCALAR!\n"
673             unless ref($source) eq "SCALAR";
674              
675 4         6 my $len = length ($$source);
676              
677 4 50       10 croak "xor_strings: source string can't have zero length!\n"
678             unless $len;
679              
680 4         8 foreach my $target (@_) {
681 4 50       13 croak "xor_strings: targets not all same size as source\n"
682             unless length($target) == $len;
683 4         10 map { substr ($$source, $_, 1) ^= substr ($target, $_, 1) }
  28         123  
684             (0 .. $len-1);
685             }
686              
687 4         25 return $$source;
688             }
689              
690             # Later, xor_strings could be replaced with an C version with reduced
691             # error checking, so make a backward-compatible version and an
692             # explicit fast/unsafe version.
693 0     0 0   sub xor_strings { safe_xor_strings(@_) }
694             #sub fast_xor_strings { safe_xor_strings(@_) } # implemented in OnlineCode.xs.
695              
696              
697             1;
698              
699             __END__