File Coverage

blib/lib/Net/OnlineCode/RNG.pm
Criterion Covered Total %
statement 42 65 64.6
branch 4 12 33.3
condition 2 2 100.0
subroutine 11 17 64.7
pod 0 12 0.0
total 59 108 54.6


line stmt bran cond sub pod time code
1             package Net::OnlineCode::RNG;
2              
3 1     1   27264 use strict;
  1         2  
  1         41  
4 1     1   5 use warnings;
  1         1  
  1         28  
5              
6 1     1   4 use Fcntl;
  1         6  
  1         359  
7 1     1   993 use Digest::SHA qw(sha1);
  1         6433  
  1         118  
8              
9 1     1   9 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS $VERSION);
  1         2  
  1         860  
10              
11             require Exporter;
12              
13             @ISA = qw(Exporter);
14             @EXPORT_OK = qw(random_uuid_160);
15             $VERSION = '0.01';
16              
17             #
18             # Random number generator
19             #
20              
21             # A portable random number generator is needed if this program is to
22             # be used to transfer data from one machine to another. This is
23             # because the sending program does not transmit any metadata along
24             # with the check blocks to indicate which and how many composite
25             # blocks each contains. Likewise, the information relating which and
26             # how many message blocks are contained in an auxiliary block is not
27             # sent.
28             #
29             # While it would be possible to send this metadata in a particular
30             # implementation, the solution used here is based on having identical
31             # pseudo-random number generators set up on both sender and receiver
32             # machines. When the sender machine begins a transfer, it will send a
33             # random 160-bit number, along with any other metadata such as
34             # filename, file size, block size and q and e parameters. Both sender
35             # and receiver then seed a deterministic PRNG with the 160-bit value.
36             #
37             # The sender then uses this seeded PRNG to randomly create 0.55qen
38             # auxiliary blocks. The receiver carries out the same operation and
39             # since it is using both the same seed value and the same RNG
40             # algorithm, both sides should agree on which message blocks are xored
41             # into which auxiliary blocks.
42             #
43             # In a similar manner, when sending a check block, the receiver first
44             # selects a random seed value. This seed value is then used with the
45             # RNG algorithm to select which composite blocks are to be included in
46             # the check block. When the receiver receives the packet it unpacks
47             # the seed value and uses it to seed the RNG. It goes through the same
48             # algorithm that the sender used to decide which composite blocks were
49             # included in the check block and saves that information along with
50             # the data for the check block itself.
51             #
52             # The RNG used in this implementation is based on repeated application
53             # of the SHA-1 message digest algorithm. This was chosen because:
54             #
55             # * SHA-1 is a published standard, so we can be fairly certain that
56             # the implementations on different machines will produce the same
57             # results.
58             #
59             # * there are implementations available in a variety of languages, so
60             # it is possible to make interoperable implementations in those
61             # languages
62             #
63             # * it produces a 160-bit value, which is large enough to use as a
64             # unique block (or filename) ID, even if used in a long-running
65             # program
66             #
67             # * also, we can feed the output of one call to the digest function
68             # back in as input to get a new pseudo-random number. It should be
69             # highly unlikely that cycles will appear over the lifetime of its
70             # use, so it is unlikely to skew the probability distributions that
71             # we want to use.
72             #
73             # * message digest functions are designed with some desirable features
74             # in mind, such as having output bits that are uncorrelated with the
75             # input, having long limit cycles and not having obvious biases in
76             # the output. These features are very useful when using a message
77             # digest algorithm as a PRNG.
78             #
79             # The one potential disadvantage of using a message digest algorithm
80             # over some other custom PRNG is that it may not perform as well.
81             # However, I believe that the benefits of using a readily-available
82             # implementation (along with the other advantages listed) outweigh
83             # this minor disadvantage. For those reasons, I will use SHA-1 here.
84             #
85              
86             sub new {
87              
88 2     2 0 18 my ($class, $seed) = @_;
89 2         7 my $self = {
90             seed => $seed,
91             current => undef,
92             };
93              
94 2         4 bless $self, $class;
95 2         6 $self->seed($seed);
96 2         5 return $self;
97             }
98              
99             sub new_random {
100              
101 0     0 0 0 my $class = shift;
102 0         0 my $self = {
103             seed => undef,
104             current => undef,
105             };
106              
107 0         0 bless $self, $class;
108 0         0 $self->seed_random();
109 0         0 return $self;
110             }
111              
112              
113             # Note that seed/srand with no args is usually implemented to
114             # pick a random value. For this application, it's better to set
115             # up some deterministic value
116              
117             sub seed {
118 3     3 0 5 my $self = shift;
119 3         5 my $seed = shift;
120              
121 3 50       9 die "seed: self object not a reference\n" unless ref($self);
122              
123 3 100       7 $seed = "\0" x 20 unless defined($seed);
124 3         11 $self->{seed} = $seed;
125             # $self->{current} = sha1($seed);
126             # we can save a call to sha1:
127 3         4 $self->{current} = $seed;
128 3         8 return $seed;
129             }
130              
131             # Also provide seed_random to set a random seed
132             sub seed_random {
133 0     0 0 0 my $self = shift;
134              
135 0         0 return $self->seed(random_uuid_160());
136             }
137              
138             sub get_seed {
139 3     3 0 855 return shift->{seed};
140             }
141              
142             # As per Perl's rand, return a float value, 0 <= value < x
143             sub rand {
144 51     51 0 2629 my ($self,$max) = @_;
145 51   100     110 $max = $max || 1;
146 51         45 $max = abs($max); # don't allow negative values
147              
148 51         54 $max += 0.0; # ensure max is a float
149              
150 51         64 while(1) {
151 51         311 $self->{current} = sha1($self->{current}); # advance to next rand
152              
153             # unpack 5 32-bit words from the 160-bit SHA sum
154 51         152 my @uints = unpack "N5", $self->{current};
155              
156             # We calculate the rand by max * uint/(max 32-bit int).
157 51         710 while (@uints>=1) {
158 51         59 my $r = shift @uints;
159             # $r ^= shift @uints;
160             # $r ^= shift @uints;
161             # $r ^= shift @uints;
162             # $r ^= shift @uints;
163 51         53 my $maxint = 2**32 - 1;
164 51         63 my $ratio = $r / $maxint;
165 51 50       203 return ($max * $ratio) if ($r < $maxint);
166             }
167             }
168             }
169              
170             # The remaining subs are debugging purposes. They report back the
171             # last random number in a variety of formats, but do not advance
172             # to the next rand
173              
174              
175             sub current {
176 3     3 0 14 return shift->{current};
177             }
178              
179             sub as_string { # alias for "current" method
180 1     1 0 7 return shift->{current};
181             }
182              
183             # Unpacking as bytes or 32-bit unsigned ints. Use "network" order
184             # for portability.
185             sub as_byte_array {
186 0     0 0   return unpack "C20", shift->{current};
187             }
188              
189             sub as_uint32_array {
190 0     0 0   return unpack "N5", shift->{current};
191             }
192              
193              
194             sub as_hex {
195 0     0 0   return unpack "H40", shift->{current};
196             }
197              
198             # *nix-specific helper function to get a random 160-bit value from the
199             # output of /dev/urandom. This does not affect the current value of
200             # the RNG, but the returned value can be used to seed it.
201              
202             sub random_uuid_160 {
203 0     0 0   my $self = shift; # we don't use this
204              
205             # sysopen/sysread avoids any potential problem with opening file in
206             # non-binary mode
207 0 0         sysopen (RAND, "/dev/urandom", O_RDONLY)
208             or die "couldn't open urandom!\n";
209              
210 0           my $bits = '';
211 0           my $chunk = '';
212 0           my $rc = 0;
213              
214             # use a loop in case we read fewer than the required number of bytes
215 0           do {
216 0           $rc = (sysread RAND,$chunk,20-length($bits));
217              
218 0 0         if (defined ($rc)) {
219 0 0         if ($rc) {
220 0           $bits .= $chunk;
221             } else {
222 0           die "Random source dried up (unexpected EOF)!\n";
223             }
224             } else {
225 0           die "Failed to sysread from urandom: $!\n";
226             }
227             } while (length $bits < 20);
228              
229 0           return $bits;
230             }
231              
232             1;
233