File Coverage

blib/lib/Net/OAuth2/Scheme/Random.pm
Criterion Covered Total %
statement 28 93 30.1
branch 4 34 11.7
condition 0 16 0.0
subroutine 7 17 41.1
pod 2 3 66.6
total 41 163 25.1


line stmt bran cond sub pod time code
1 1     1   4 use strict;
  1         2  
  1         27  
2 1     1   4 use warnings;
  1         1  
  1         38  
3              
4             package Net::OAuth2::Scheme::Random;
5             BEGIN {
6 1     1   14 $Net::OAuth2::Scheme::Random::VERSION = '0.03';
7             }
8             # ABSTRACT: random number generator interface
9 1     1   5 use Carp;
  1         2  
  1         61  
10 1     1   1289 use Thread::IID 'interpreter_id';
  1         705  
  1         1833  
11              
12             # default RNG class; needs to be something that can do
13             # $rng = class->new(@ints) # new rng seeded with @ints
14             # $rng->irand; # random 32/64-bit int
15             #
16             our $RNG_Class;
17              
18             # stash seed so that we can keep re-using it for each new fork and
19             # interpreter clone, varying only the process_id and interpreter_id
20             my @seed;
21              
22             # class setup methods
23             our %seeds = (); # class -> @seeds if we trust their autoseeder
24             our %new = (); # class -> (@seeds -> new or reseeded RNG)
25             our %ish = (); # class -> 3 for 64bit, 2 for 32bit irand
26              
27             sub import {
28 1     1   3 my $class = shift;
29             # set $RNG_Class
30 1 50       4 if (@_) {
31 0         0 $RNG_Class = shift;
32             }
33             else {
34 1         2 my @classes = keys %{ +{ map {$_,1} keys %seeds, keys %new } };
  1         4  
  3         10  
35 1         4 for my $c (@classes) {
36 2         3 my $f = $c;
37 2         8 $f =~ s|::|/|g;
38 2         4 $f .= '.pm';
39 2 50       11 next unless $INC{$f};
40 0         0 $RNG_Class = $c;
41 0         0 last;
42             }
43             }
44 1 50       8 $RNG_Class = 'Math::Random::MT::Auto'
45             unless defined $RNG_Class;
46 1 50       59 eval "require $RNG_Class;" or die $@;
47             # set @seed
48 0   0       @seed = ($seeds{$RNG_Class} || \&_make_seed)->();
49             }
50              
51             my %rng = (); # class -> (singleton) RNG object of that class
52             my %bytes = (); # class -> leftover bytes
53             my %refs = (); # class -> # of Net::OAuth2::Scheme::Random objects
54             my $p_id = -1; # process id ($$)
55             my $i_id; # interpreter id (see Thread::IID for explanation)
56              
57             sub _reseed_for_new_thread {
58 0     0     my $rng_class = shift;
59             my $new = $new{$rng_class} || sub {
60 0     0     my $class=shift;
61 0           return $class->new(@_);
62 0   0       };
63 0           my $rng = $new->($rng_class, $seed[0]+time, $seed[1]+$p_id, $seed[2]+$i_id, @seed[3.. $#seed]);
64 0           $rng{$rng_class} = $rng;
65 0           $bytes{$rng_class} = '';
66             }
67              
68             sub new {
69 0     0 1   my $class = shift;
70 0   0       my $rng_class = shift || $RNG_Class;
71              
72             # check for fork()
73 0 0         $class->CLONE unless $$ == $p_id;
74              
75 0 0         unless ($rng{$rng_class}) {
76 0           eval "use ${rng_class};";
77 0           _reseed_for_new_thread($rng_class);
78             }
79 0           $refs{$rng_class}++;
80 0           return bless \( $rng_class ), $class;
81             }
82              
83             sub _rng {
84 0     0     my $self = shift;
85              
86             # check for fork()
87 0 0         ref($self)->CLONE unless $$ == $p_id;
88              
89 0           return $rng{$$self};
90             }
91              
92             sub DESTROY {
93 0     0     my $self = shift;
94 0           --$refs{$$self};
95             # this routine only exists for the sake of being able to detect
96             # unused RNG classes upon interpreter clone or process fork.
97             #
98             # once a RNG of a given class is created with a given seed,
99             # we need to keep it around forever within any given process/thread
100             # otherwise, we will get repeats
101             }
102              
103             sub CLONE {
104 0     0     my $class = shift;
105 0 0 0       return if $p_id == $$ && $i_id == interpreter_id;
106 0           $p_id = $$;
107 0           $i_id = interpreter_id;
108 0           for my $rng_class (keys %rng) {
109 0 0         if ($refs{$rng_class} <= 0) {
110             # nobody is currently using it
111             # therefore it has not been used yet in this thread
112             # therefore we can safely get rid of it
113 0           delete $rng{$rng_class};
114 0           delete $bytes{$rng_class};
115 0           delete $refs{$rng_class};
116             }
117             else {
118 0           _reseed_for_new_thread($rng_class);
119             }
120             }
121             }
122              
123             sub irand {
124 0     0 0   my $self = shift;
125 0           $self->_rng->irand();
126             }
127              
128             sub bytes {
129 0     0 1   my ($self, $nbytes) = @_;
130 0 0         Carp::croak('non-negative integer expected')
131             if $nbytes < 0;
132              
133 0           my $rng = $self->_rng;
134 0   0       my $ish = $ish{$$self} || 2;
135 0           my $imask = (1<<$ish)-1;
136 0 0         my $L = $ish == 2 ? 'L' : 'Q';
137              
138 0           my @ints = ();
139 0           push @ints, $rng->irand for (1..$nbytes>>$ish);
140              
141 0 0         unless (my $nrem = (${nbytes} & ${imask})) {
142 0           return pack "${L}*", @ints;
143             }
144             else {
145 0           my ($rest);
146 0           my $extras = $bytes{$$self};
147 0 0         if ($nrem == length($extras)) {
148 0           ($rest,$bytes{$$self}) = ($extras,'');
149             }
150             else {
151 0 0         ($rest,$bytes{$$self}) = unpack 'C/aa*',
152             ($nrem > length($extras)
153             ? pack "Ca*${L}", $nrem, $extras, $rng->irand
154             : pack 'Ca*', $nrem, $extras);
155             }
156 0           return pack "a*${L}*", $rest, @ints;
157             }
158             }
159              
160             sub _make_seed {
161             # stolen from Math::Random::Secure
162 0     0     my ($nbytes, $sizeofint) = @_;
163 0   0       $nbytes ||= 64;
164 0   0       $sizeofint ||= 4;
165 0           my $source;
166 0 0         if ($^O =~ /Win32/i) {
167             # On Windows, there is apparently only one choice
168 0           require Crypt::Random::Source::Strong::Win32;
169 0           $source = Crypt::Random::Source::Strong::Win32->new();
170             }
171             else {
172 0           require Crypt::Random::Source::Factory;
173 0           my $factory = Crypt::Random::Source::Factory->new();
174 0           $source = $factory->get;
175              
176             # Never allow rand() to be used as a source, it cannot possibly be
177             # cryptographically strong with 15 or 32 bits for its seed.
178 0 0         $source = $factory->get_strong
179             if ($source->isa('Crypt::Random::Source::Weak::rand'));
180             }
181 0 0         return unpack(($sizeofint == 8 ? 'Q*' : 'L*'), $source->get($nbytes));
182             }
183              
184             ### Math::Random::ISAAC support ###########################
185              
186             $new{'Math::Random::ISAAC'} = sub {
187             my $class=shift;
188             my $rng = $class->new(@_);
189              
190             # skip frontend of Math::Random::ISAAC,
191             # unless Math::Random::ISAAC has changed
192             # so that there is no {backend} anymore.
193             $rng = $rng->{backend} if $rng->{backend};
194             return $rng;
195             };
196              
197             ### Math::Random::MT::Auto support ###########################
198              
199             my $mrma = 'Math::Random::MT::Auto';
200              
201 1     1   11 use Config;
  1         2  
  1         259  
202             $ish{$mrma} = $Config{uvsize} == 8 ? 3 : 2;
203              
204             $seeds{$mrma} = sub {
205             Math::Random::MT::Auto->import unless defined $MRMA::PRNG;
206             my @s = $MRMA::PRNG->get_seed;
207             if (@s < 4) {
208             # class was loaded with :noauto or auto-seeding failed;
209             # try to seed it ourselves
210             @s = _make_seed(2496, $Config{uvsize});
211             }
212             return @s;
213             };
214              
215             $new{$mrma} = sub {
216             my $class = shift;
217              
218             # RNG shared acrosss threads does not need reseeding
219             return $rng{$class}
220             if $Math::Random::MT::Auto::shared;
221              
222             return $class->new('SEED'=> \@_)
223             };
224              
225             sub _SHUT_UP_SHUT_UP_used_once_diagnostics {
226 0     0     [$MRMA::PRNG, $Math::Random::MT::Auto::shared];
227             }
228              
229             1;
230              
231              
232             __END__