File Coverage

blib/lib/Algorithm/Hamming/Perl.pm
Criterion Covered Total %
statement 63 95 66.3
branch 12 18 66.6
condition n/a
subroutine 6 7 85.7
pod 4 5 80.0
total 85 125 68.0


line stmt bran cond sub pod time code
1             #!/bin/perl
2             #
3             # Perl.pm - Algorithm::Hamming::Perl library. Implements 8,4 bit Hamming ECC.
4             #
5             # This code will be unusual to read - instead of finding the Hamming
6             # algorithm you will see hash after hash after hash. These are used to
7             # improve the speed of the library, and act as a cache of preprocessed
8             # results. An optional subrourine may be run:
9             # Algorithm::Hamming::Perl::hamming_faster()
10             # which uses a bigger cache for faster encoding/decoding (but more memory
11             # and slower startups).
12             #
13             # 18-Oct-2003 Brendan Gregg Created this.
14              
15              
16             package Algorithm::Hamming::Perl;
17              
18 1     1   7461 use 5.006;
  1         4  
  1         48  
19 1     1   5 use strict;
  1         2  
  1         1942  
20              
21             require Exporter;
22             our @ISA = qw(Exporter);
23             our @EXPORT_OK = qw(hamming unhamming unhamming_err);
24              
25             our $VERSION = '0.05';
26              
27              
28             my %Hamming8raw; # This hash is used during initialisation only. It
29             # contains binary text keys and binary text values
30             # as [data] -> [Hamming code] lookups,
31             # eg "00001010" => "000001010010"
32              
33             my %Hamming8semi; # This hash is semi-processed, and is used in "slow"
34             # encoding mode. It contains byte keys and binary
35             # text values as [data] -> [Hamming code] lookups,
36             # eg "A" => "010010000100"
37              
38             my %Hamming8by2; # This hash is fully-processed and provides speed at
39             # the cost of memory. It contains 2 byte keys and
40             # 3 byte values as [data] -> [Hamming code] lookups,
41             # eg "AA" => "HD " # (whatever the code is!)
42             # By using this hash, the program can read an
43             # input stream 2 bytes at a time, writing an output
44             # stream 3 bytes at a time - no messing aroung
45             # with half bytes or byte boundaries.
46              
47             my %Hamming8rev; # This hash is semi-processed, and is used for
48             # decoding Hamming code to data. It contains
49             # binary text values for keys and bytes for values
50             # as [Hamming code] -> [data] lookups,
51             # eg "010010000100" => "A"
52              
53             my %Hamming8by2rev; # This hash is fully-processed and provides speed at
54             # the cost of memory. It contains 3 byte keys and
55             # 2 byte values as [Hamming code] -> [data] lookups,
56             # eg "HD " => "AA" # (whatever the code is!)
57             # By using this hash, the program can read an
58             # input stream 3 bytes at a time, writing an output
59             # stream 2 bytes at a time - no messing aroung
60             # with half bytes or byte boundaries.
61              
62             my ($x,$y,$key,$char,$char1,$char2,$chars,$char_out,$ham_text,$number);
63              
64             #
65             # Hamming8raw is NOT the lookup table used! :)
66             # (that would be dreadfully inefficient).
67             # This hash is processed into a bytes -> bytes lookup.
68             #
69             %Hamming8raw = ("00000000" => "000000000000",
70             "00000001" => "000000000111",
71             "00000010" => "000000011001",
72             "00000011" => "000000011110",
73             "00000100" => "000000101010",
74             "00000101" => "000000101101",
75             "00000110" => "000000110011",
76             "00000111" => "000000110100",
77             "00001000" => "000001001011",
78             "00001001" => "000001001100",
79             "00001010" => "000001010010",
80             "00001011" => "000001010101",
81             "00001100" => "000001100001",
82             "00001101" => "000001100110",
83             "00001110" => "000001111000",
84             "00001111" => "000001111111",
85             "00010000" => "000110000001",
86             "00010001" => "000110000110",
87             "00010010" => "000110011000",
88             "00010011" => "000110011111",
89             "00010100" => "000110101011",
90             "00010101" => "000110101100",
91             "00010110" => "000110110010",
92             "00010111" => "000110110101",
93             "00011000" => "000111001010",
94             "00011001" => "000111001101",
95             "00011010" => "000111010011",
96             "00011011" => "000111010100",
97             "00011100" => "000111100000",
98             "00011101" => "000111100111",
99             "00011110" => "000111111001",
100             "00011111" => "000111111110",
101             "00100000" => "001010000010",
102             "00100001" => "001010000101",
103             "00100010" => "001010011011",
104             "00100011" => "001010011100",
105             "00100100" => "001010101000",
106             "00100101" => "001010101111",
107             "00100110" => "001010110001",
108             "00100111" => "001010110110",
109             "00101000" => "001011001001",
110             "00101001" => "001011001110",
111             "00101010" => "001011010000",
112             "00101011" => "001011010111",
113             "00101100" => "001011100011",
114             "00101101" => "001011100100",
115             "00101110" => "001011111010",
116             "00101111" => "001011111101",
117             "00110000" => "001100000011",
118             "00110001" => "001100000100",
119             "00110010" => "001100011010",
120             "00110011" => "001100011101",
121             "00110100" => "001100101001",
122             "00110101" => "001100101110",
123             "00110110" => "001100110000",
124             "00110111" => "001100110111",
125             "00111000" => "001101001000",
126             "00111001" => "001101001111",
127             "00111010" => "001101010001",
128             "00111011" => "001101010110",
129             "00111100" => "001101100010",
130             "00111101" => "001101100101",
131             "00111110" => "001101111011",
132             "00111111" => "001101111100",
133             "01000000" => "010010000011",
134             "01000001" => "010010000100",
135             "01000010" => "010010011010",
136             "01000011" => "010010011101",
137             "01000100" => "010010101001",
138             "01000101" => "010010101110",
139             "01000110" => "010010110000",
140             "01000111" => "010010110111",
141             "01001000" => "010011001000",
142             "01001001" => "010011001111",
143             "01001010" => "010011010001",
144             "01001011" => "010011010110",
145             "01001100" => "010011100010",
146             "01001101" => "010011100101",
147             "01001110" => "010011111011",
148             "01001111" => "010011111100",
149             "01010000" => "010100000010",
150             "01010001" => "010100000101",
151             "01010010" => "010100011011",
152             "01010011" => "010100011100",
153             "01010100" => "010100101000",
154             "01010101" => "010100101111",
155             "01010110" => "010100110001",
156             "01010111" => "010100110110",
157             "01011000" => "010101001001",
158             "01011001" => "010101001110",
159             "01011010" => "010101010000",
160             "01011011" => "010101010111",
161             "01011100" => "010101100011",
162             "01011101" => "010101100100",
163             "01011110" => "010101111010",
164             "01011111" => "010101111101",
165             "01100000" => "011000000001",
166             "01100001" => "011000000110",
167             "01100010" => "011000011000",
168             "01100011" => "011000011111",
169             "01100100" => "011000101011",
170             "01100101" => "011000101100",
171             "01100110" => "011000110010",
172             "01100111" => "011000110101",
173             "01101000" => "011001001010",
174             "01101001" => "011001001101",
175             "01101010" => "011001010011",
176             "01101011" => "011001010100",
177             "01101100" => "011001100000",
178             "01101101" => "011001100111",
179             "01101110" => "011001111001",
180             "01101111" => "011001111110",
181             "01110000" => "011110000000",
182             "01110001" => "011110000111",
183             "01110010" => "011110011001",
184             "01110011" => "011110011110",
185             "01110100" => "011110101010",
186             "01110101" => "011110101101",
187             "01110110" => "011110110011",
188             "01110111" => "011110110100",
189             "01111000" => "011111001011",
190             "01111001" => "011111001100",
191             "01111010" => "011111010010",
192             "01111011" => "011111010101",
193             "01111100" => "011111100001",
194             "01111101" => "011111100110",
195             "01111110" => "011111111000",
196             "01111111" => "011111111111",
197             "10000000" => "100010001000",
198             "10000001" => "100010001111",
199             "10000010" => "100010010001",
200             "10000011" => "100010010110",
201             "10000100" => "100010100010",
202             "10000101" => "100010100101",
203             "10000110" => "100010111011",
204             "10000111" => "100010111100",
205             "10001000" => "100011000011",
206             "10001001" => "100011000100",
207             "10001010" => "100011011010",
208             "10001011" => "100011011101",
209             "10001100" => "100011101001",
210             "10001101" => "100011101110",
211             "10001110" => "100011110000",
212             "10001111" => "100011110111",
213             "10010000" => "100100001001",
214             "10010001" => "100100001110",
215             "10010010" => "100100010000",
216             "10010011" => "100100010111",
217             "10010100" => "100100100011",
218             "10010101" => "100100100100",
219             "10010110" => "100100111010",
220             "10010111" => "100100111101",
221             "10011000" => "100101000010",
222             "10011001" => "100101000101",
223             "10011010" => "100101011011",
224             "10011011" => "100101011100",
225             "10011100" => "100101101000",
226             "10011101" => "100101101111",
227             "10011110" => "100101110001",
228             "10011111" => "100101110110",
229             "10100000" => "101000001010",
230             "10100001" => "101000001101",
231             "10100010" => "101000010011",
232             "10100011" => "101000010100",
233             "10100100" => "101000100000",
234             "10100101" => "101000100111",
235             "10100110" => "101000111001",
236             "10100111" => "101000111110",
237             "10101000" => "101001000001",
238             "10101001" => "101001000110",
239             "10101010" => "101001011000",
240             "10101011" => "101001011111",
241             "10101100" => "101001101011",
242             "10101101" => "101001101100",
243             "10101110" => "101001110010",
244             "10101111" => "101001110101",
245             "10110000" => "101110001011",
246             "10110001" => "101110001100",
247             "10110010" => "101110010010",
248             "10110011" => "101110010101",
249             "10110100" => "101110100001",
250             "10110101" => "101110100110",
251             "10110110" => "101110111000",
252             "10110111" => "101110111111",
253             "10111000" => "101111000000",
254             "10111001" => "101111000111",
255             "10111010" => "101111011001",
256             "10111011" => "101111011110",
257             "10111100" => "101111101010",
258             "10111101" => "101111101101",
259             "10111110" => "101111110011",
260             "10111111" => "101111110100",
261             "11000000" => "110000001011",
262             "11000001" => "110000001100",
263             "11000010" => "110000010010",
264             "11000011" => "110000010101",
265             "11000100" => "110000100001",
266             "11000101" => "110000100110",
267             "11000110" => "110000111000",
268             "11000111" => "110000111111",
269             "11001000" => "110001000000",
270             "11001001" => "110001000111",
271             "11001010" => "110001011001",
272             "11001011" => "110001011110",
273             "11001100" => "110001101010",
274             "11001101" => "110001101101",
275             "11001110" => "110001110011",
276             "11001111" => "110001110100",
277             "11010000" => "110110001010",
278             "11010001" => "110110001101",
279             "11010010" => "110110010011",
280             "11010011" => "110110010100",
281             "11010100" => "110110100000",
282             "11010101" => "110110100111",
283             "11010110" => "110110111001",
284             "11010111" => "110110111110",
285             "11011000" => "110111000001",
286             "11011001" => "110111000110",
287             "11011010" => "110111011000",
288             "11011011" => "110111011111",
289             "11011100" => "110111101011",
290             "11011101" => "110111101100",
291             "11011110" => "110111110010",
292             "11011111" => "110111110101",
293             "11100000" => "111010001001",
294             "11100001" => "111010001110",
295             "11100010" => "111010010000",
296             "11100011" => "111010010111",
297             "11100100" => "111010100011",
298             "11100101" => "111010100100",
299             "11100110" => "111010111010",
300             "11100111" => "111010111101",
301             "11101000" => "111011000010",
302             "11101001" => "111011000101",
303             "11101010" => "111011011011",
304             "11101011" => "111011011100",
305             "11101100" => "111011101000",
306             "11101101" => "111011101111",
307             "11101110" => "111011110001",
308             "11101111" => "111011110110",
309             "11110000" => "111100001000",
310             "11110001" => "111100001111",
311             "11110010" => "111100010001",
312             "11110011" => "111100010110",
313             "11110100" => "111100100010",
314             "11110101" => "111100100101",
315             "11110110" => "111100111011",
316             "11110111" => "111100111100",
317             "11111000" => "111101000011",
318             "11111001" => "111101000100",
319             "11111010" => "111101011010",
320             "11111011" => "111101011101",
321             "11111100" => "111101101001",
322             "11111101" => "111101101110",
323             "11111110" => "111101110000",
324             "11111111" => "111101110111");
325              
326              
327             #
328             # Build Hamming lookup tables
329             #
330             foreach $key (sort { $a <=> $b } keys %Hamming8raw) {
331             $char = pack("B*",$key);
332             $Hamming8semi{$char} = $Hamming8raw{$key};
333             }
334             %Hamming8rev = reverse(%Hamming8semi);
335              
336              
337             # hamming_faster - this subroutine builds two large hashes of,
338             # %Hamming8by2 2 byte data -> 3 byte Hamming code
339             # %Hamming8by2rev 3 byte Hamming code -> 2 byte data
340             # for faster encodings and decodings. Running this subroutine is
341             # optional. If it is used then conversions are faster, however more
342             # memory is used to store the hashes, and a couple of seconds is added
343             # to the startup time. If it is not used, conversions are slower -
344             # taking up to 5 times the usual time. A good measure is the data you
345             # with to encode - more than 1 Mb would benifit from this subroutine.
346             #
347             sub hamming_faster {
348              
349             #
350             # Step through 0,0 to 255,255 to build a hash that can convert
351             # any 2 byte combinations.
352             #
353 0     0 1 0 for ($x=0; $x<256; $x++) {
354 0         0 for ($y=0; $y<256; $y++) {
355              
356             ### Convert numbers into 2 bytes
357 0         0 $char1 = chr($x);
358 0         0 $char2 = chr($y);
359 0         0 $chars = $char1 . $char2;
360              
361             ### Generating 24 bit Hamming code
362 0         0 $ham_text = $Hamming8semi{$char1} .
363             $Hamming8semi{$char2};
364            
365             ### Make 3 byte Hamming code
366 0         0 $char_out = pack("B*",$ham_text);
367              
368             ### Add to hash
369 0         0 $Hamming8by2{$chars} = $char_out;
370             }
371             }
372 0         0 %Hamming8by2rev = reverse(%Hamming8by2);
373             }
374            
375              
376             # hamming - this turns data into hamming code. This has been written
377             # with memory and CPU efficiency in mind (without resorting to C).
378             #
379             sub hamming {
380 1     1 1 50 my $data = shift; # input data
381 1         3 my $pos; # counter to step through data string
382             my $char_in1; # first input byte
383 0         0 my $char_in2; # second input byte
384 0         0 my $chars_in; # both input bytes
385 0         0 my $ham_text; # hamming code in binary text "0101.."
386 0         0 my $char_out; # hamming code as bytes
387 1         3 my $output = ""; # full output hamming code as bytes
388              
389 1         2 my $length = length($data);
390            
391             #
392             # Step through the $data 2 bytes at a time, generating a
393             # Hamming encoded $output.
394             #
395 1         6 for ($pos = 0; $pos < ($length-1); $pos+=2) {
396              
397 1         3 $chars_in = substr($data,$pos,2);
398 1 50       5 if (defined $Hamming8by2{$chars_in}) {
399             #
400             # Fast method
401             #
402 0         0 $output .= $Hamming8by2{$chars_in};
403             } else {
404             #
405             # Slow method
406             #
407              
408             ### Get both chars
409 1         3 $char_in1 = substr($data,$pos,1);
410 1         2 $char_in2 = substr($data,$pos+1,1);
411              
412             ### Make a 24 bit hamming binary number
413 1         5 $ham_text = $Hamming8semi{$char_in1} .
414             $Hamming8semi{$char_in2};
415              
416             ### Turn this number into 3 bytes
417 1         6 $char_out = pack("B*",$ham_text);
418              
419             ### Add to output
420 1         5 $output .= $char_out;
421             }
422             }
423              
424             #
425             # A leftover byte (if present) is padded with 0's.
426             #
427 1 50       4 if ($length == ($pos + 1)) {
428              
429             ### Get the last character
430 0         0 $char_in1 = substr($data,$pos,1);
431              
432             ### Generate padded hamming text
433 0         0 $ham_text = $Hamming8semi{$char_in1} . "0000";
434            
435             ### Turn into 2 bytes
436 0         0 $char_out = pack("B*",$ham_text);
437              
438             ### Add to output
439 0         0 $output .= $char_out;
440             }
441            
442 1         4 return $output;
443             }
444              
445              
446             # unhamming_err - this turns hamming code into data. This has been written
447             # with memory and CPU efficiencu in mind (without resorting to C).
448             #
449             sub unhamming_err {
450 3     3 1 142 my $data = shift; # input data
451 3         5 my $pos; # counter to step through data string
452             my $err; # corrected bit error
453 0         0 my $chars_in; # input bytes
454 0         0 my $ham_text; # hamming code in binary text "0101..", 2 bytes
455 0         0 my $ham_text1; # hamming code for first byte
456 0         0 my $ham_text2; # hamming code for second byte
457 0         0 my $char_out1; # output data byte 1
458 0         0 my $char_out2; # output data byte 2
459 3         5 my $output = ""; # full output data as bytes
460 3         4 my $err_all = 0; # count of corrected bit errors
461              
462 3         4 my $length = length($data);
463            
464             #
465             # Step through the $data 3 bytes at a time, decoding it back into
466             # the $output data.
467             #
468 3         11 for ($pos = 0; $pos < ($length-2); $pos+=3) {
469              
470             ### Fetch 3 bytes
471 3         7 $chars_in = substr($data,$pos,3);
472              
473 3 50       8 if (defined $Hamming8by2rev{$chars_in}) {
474             #
475             # Fast method
476             #
477 0         0 $output .= $Hamming8by2rev{$chars_in};
478             } else {
479             #
480             # Slow method
481             #
482              
483             ### Fetch the 2 Hamming codes
484 3         9 $ham_text = unpack("B*",$chars_in);
485 3         7 $ham_text1 = substr($ham_text,0,12);
486 3         4 $ham_text2 = substr($ham_text,12);
487              
488             ### Convert each code into the original byte
489 3         7 ($char_out1,$err) = unhamchar($ham_text1);
490 3         7 $err_all += $err;
491 3         7 ($char_out2,$err) = unhamchar($ham_text2);
492 3         6 $err_all += $err;
493              
494             ### Add bytes to output
495 3         10 $output .= $char_out1 . $char_out2;
496             }
497             }
498              
499             #
500             # Decode leftover bytes (if present).
501             #
502 3 50       8 if ($length == ($pos + 2)) {
503             ### Fetch the 2 leftover bytes
504 0         0 $chars_in = substr($data,$pos,2);
505              
506             ### Fetch the Hamming code
507 0         0 $ham_text = unpack("B*",$chars_in);
508 0         0 $ham_text1 = substr($ham_text,0,12);
509            
510             ### Convert the code to the original byte
511 0         0 ($char_out1,$err) = unhamchar($ham_text1);
512 0         0 $err_all += $err;
513              
514             ### Add byte to output
515 0         0 $output .= $char_out1;
516             }
517            
518 3         11 return ($output,$err_all);
519             }
520              
521              
522             # unhamming - this is a wrapper around unhamming_err that just returns
523             # the data.
524             #
525             sub unhamming {
526 1     1 1 104 my $data = shift;
527 1         2 my ($output,$err);
528              
529 1         5 ($output,$err) = unhamming_err($data);
530 1         3 return $output;
531             }
532              
533              
534             # unhamchar - this takes a hamming code as binary text "0101..." and returns
535             # both the char and number (0 or 1) to represent if correction
536             # occured.
537             #
538             sub unhamchar {
539 6     6 0 8 my $text = shift;
540 6         20 my $pos = 0; # counter
541 6         8 my $err = 0; # error bit position
542 6         7 my ($bit);
543              
544             ### If okay, return now
545 6 100       18 if (defined $Hamming8rev{$text}) {
546 4         13 return ($Hamming8rev{$text},0);
547             }
548              
549             ### Find error bit
550 2         4 my $copy = $text;
551 2         6 while ($copy ne "") {
552 24         25 $pos++;
553 24         26 $bit = chop($copy);
554 24 100       49 if ($bit eq "1") {
555 10         17 $err = $err ^ $pos;
556             }
557             }
558              
559             ### Correct error bit
560 2         4 $copy = $text;
561 2 50       6 if ($err <= 12) {
562 2         6 $bit = substr($copy,-$err,1);
563 2 100       6 if ($bit eq "0") { $bit = "1"; }
  1         2  
564 1         22 else { $bit = "0"; }
565 2         5 substr($copy,-$err,1) = $bit;
566             }
567              
568             ### If okay now, return
569 2 50       7 if (defined $Hamming8rev{$copy}) {
570 2         7 return ($Hamming8rev{$copy},1);
571             }
572              
573             ### We shouldn't get here
574 0           return ("\0",1);
575             }
576              
577              
578              
579             1;
580             __END__