File Coverage

blib/lib/Net/OnlineCode/RNG.pm
Criterion Covered Total %
statement 42 70 60.0
branch 4 12 33.3
condition 2 2 100.0
subroutine 11 17 64.7
pod 0 12 0.0
total 59 113 52.2


line stmt bran cond sub pod time code
1             package Net::OnlineCode::RNG;
2              
3 1     1   25546 use strict;
  1         2  
  1         49  
4 1     1   6 use warnings;
  1         2  
  1         43  
5              
6 1     1   6 use Fcntl;
  1         6  
  1         434  
7 1     1   41563 use Digest::SHA qw(sha1);
  1         5474  
  1         136  
8              
9 1     1   10 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS $VERSION);
  1         2  
  1         1059  
10              
11             require Exporter;
12              
13             @ISA = qw(Exporter);
14             @EXPORT_OK = qw(random_uuid_160);
15             $VERSION = '0.03';
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 15 my ($class, $seed) = @_;
89 2         6 my $self = {
90             seed => $seed,
91             current => undef,
92             };
93              
94 2         4 bless $self, $class;
95 2         6 $self->seed($seed);
96 2         3 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 4 my $self = shift;
119 3         5 my $seed = shift;
120              
121 3 50       8 die "seed: self object not a reference\n" unless ref($self);
122              
123 3 100       8 $seed = "\0" x 20 unless defined($seed);
124 3         11 $self->{seed} = $seed;
125 3         3 $self->{current} = $seed;
126 3         8 return $seed;
127             }
128              
129             # Also provide seed_random to set a random seed
130             sub seed_random {
131 0     0 0 0 my $self = shift;
132              
133 0         0 return $self->seed(random_uuid_160());
134             }
135              
136             sub get_seed {
137 3     3 0 337 return shift->{seed};
138             }
139              
140             # As per Perl's rand, return a float value, 0 <= value < x
141             sub rand {
142 51     51 0 931 my ($self,$max) = @_;
143 51   100     98 $max = $max || 1;
144 51         48 $max = abs($max); # don't allow negative values
145              
146 51         51 $max += 0.0; # ensure max is a float
147              
148 51         48 while(1) {
149 51         468 $self->{current} = sha1($self->{current}); # advance to next rand
150              
151             # unpack 5 32-bit words from the 160-bit SHA sum
152 51         142 my @uints = unpack "N5", $self->{current};
153              
154             # We calculate the rand by max * uint/(max 32-bit int).
155 51         624 while (@uints>=1) {
156 51         58 my $r = shift @uints;
157             # $r ^= shift @uints;
158             # $r ^= shift @uints;
159             # $r ^= shift @uints;
160             # $r ^= shift @uints;
161 51         45 my $maxint = 2**32 - 1;
162 51         59 my $ratio = $r / $maxint;
163 51 50       187 return ($max * $ratio) if ($r < $maxint);
164             }
165             }
166             }
167              
168             # The remaining subs are debugging purposes. They report back the
169             # last random number in a variety of formats, but do not advance
170             # to the next rand
171              
172              
173             sub current {
174 3     3 0 14 return shift->{current};
175             }
176              
177             sub as_string { # alias for "current" method
178 1     1 0 4 return shift->{current};
179             }
180              
181             # Unpacking as bytes or 32-bit unsigned ints. Use "network" order
182             # for portability.
183             sub as_byte_array {
184 0     0 0   return unpack "C20", shift->{current};
185             }
186              
187             sub as_uint32_array {
188 0     0 0   return unpack "N5", shift->{current};
189             }
190              
191              
192             sub as_hex {
193 0     0 0   return unpack "H40", shift->{current};
194             }
195              
196             # *nix-specific helper function to get a random 160-bit value from the
197             # output of /dev/urandom. This does not affect the current value of
198             # the RNG, but the returned value can be used to seed it.
199              
200             sub random_uuid_160 {
201 0     0 0   my $self = shift; # we don't need an object ref.
202              
203             # sysopen/sysread avoids any potential problem with opening file in
204             # non-binary mode
205 0 0         if(!sysopen (RAND, "/dev/urandom", O_RDONLY)) {
206              
207             # This probably isn't a Linux machine, so fall back to using
208             # Perl's internal (non-secure) RNG. This isn't meant to be a
209             # proper solution---it's only so that smoker tests don't die on
210             # Windows platforms or other *nix distros that have a /dev/random
211             # but not a /dev/urandom
212              
213             # always warn since this is a potential security problem and not
214             # really meant to be used
215 0           warn "This machine doesn't have /dev/urandom; using rand() instead\n";
216              
217 0           my $uuid="";
218 0           for (1..20) { $uuid.= chr CORE::rand 256 }; # rand() is ambiguous
  0            
219 0           return $uuid;
220              
221             }
222              
223 0           my $bits = '';
224 0           my $chunk = '';
225 0           my $rc = 0;
226              
227             # use a loop in case we read fewer than the required number of bytes
228 0           do {
229 0           $rc = (sysread RAND,$chunk,20-length($bits));
230              
231 0 0         if (defined ($rc)) {
232 0 0         if ($rc) {
233 0           $bits .= $chunk;
234             } else {
235 0           die "Random source dried up (unexpected EOF)!\n";
236             }
237             } else {
238 0           die "Failed to sysread from urandom: $!\n";
239             }
240             } while (length $bits < 20);
241              
242 0           return $bits;
243             }
244              
245             1;
246