Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Random
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
156
0.00% covered (danger)
0.00%
0 / 1
 open
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 close
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getInt
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 shuffle
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\SecurePoll\Crypt;
4
5use MediaWiki\Status\Status;
6use RuntimeException;
7
8class Random {
9    /** @var resource|null */
10    public $urandom;
11
12    /**
13     * Open a /dev/urandom file handle
14     * @return Status
15     */
16    public function open() {
17        if ( $this->urandom ) {
18            return Status::newGood();
19        }
20
21        if ( wfIsWindows() ) {
22            return Status::newFatal( 'securepoll-urandom-not-supported' );
23        }
24        $this->urandom = fopen( '/dev/urandom', 'rb' );
25        if ( !$this->urandom ) {
26            return Status::newFatal( 'securepoll-dump-no-urandom' );
27        }
28
29        return Status::newGood();
30    }
31
32    /**
33     * Close any open file handles
34     */
35    public function close() {
36        if ( $this->urandom ) {
37            fclose( $this->urandom );
38            $this->urandom = null;
39        }
40    }
41
42    /**
43     * Get a random integer between 0 and ($maxp1 - 1).
44     * Should only be called after open() succeeds.
45     * @param int $maxp1
46     * @return int
47     */
48    public function getInt( $maxp1 ) {
49        $numBytes = ceil( strlen( base_convert( (string)$maxp1, 10, 16 ) ) / 2 );
50        if ( $numBytes == 0 ) {
51            return 0;
52        }
53        $data = fread( $this->urandom, $numBytes );
54        if ( strlen( $data ) != $numBytes ) {
55            throw new RuntimeException( __METHOD__ . ': not enough bytes' );
56        }
57        $x = 0;
58        for ( $i = 0; $i < $numBytes; $i++ ) {
59            $x *= 256;
60            $x += ord( substr( $data, $i, 1 ) );
61        }
62
63        return $x % $maxp1;
64    }
65
66    /**
67     * Works like shuffle() except more secure. Returns the new array instead
68     * of modifying it. The algorithm is the Knuth/Durstenfeld kind.
69     * @param array $a
70     * @return array
71     */
72    public function shuffle( $a ) {
73        $a = array_values( $a );
74        for ( $i = count( $a ) - 1; $i >= 0; $i-- ) {
75            $target = $this->getInt( $i + 1 );
76            $tmp = $a[$i];
77            $a[$i] = $a[$target];
78            $a[$target] = $tmp;
79        }
80
81        return $a;
82    }
83}