Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
18 / 22
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
HistoricalUIDGenerator
81.82% covered (warning)
81.82%
18 / 22
33.33% covered (danger)
33.33%
1 / 3
6.22
0.00% covered (danger)
0.00%
0 / 1
 historicalTimestampedUID88
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 millisecondsSinceEpochBinary
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
2.31
 newNodeId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Flow\Import;
4
5use MWCryptRand;
6use RuntimeException;
7
8/**
9 * Modified version of UIDGenerator generates historical timestamped
10 * uid's for use when importing older data.
11 *
12 * DO NOT USE for normal UID generation, this is likely to run into
13 * id collisions.
14 *
15 * The import process needs to identify collision failures reported by
16 * the database and re-try importing that item with another generated
17 * uid.
18 */
19class HistoricalUIDGenerator {
20    private const COUNTER_MAX = 1023; // 2^10 - 1
21
22    public static function historicalTimestampedUID88( $timestamp, $base = 10 ) {
23        static $counter = false;
24        if ( $counter === false ) {
25            $counter = mt_rand( 0, self::COUNTER_MAX );
26        }
27
28        // (seconds, milliseconds)
29        $time = [
30            wfTimestamp( TS_UNIX, $timestamp ),
31            mt_rand( 0, 999 )
32        ];
33        ++$counter;
34
35        // Take the 46 LSBs of "milliseconds since epoch"
36        $id_bin = self::millisecondsSinceEpochBinary( $time );
37        // Add a 10 bit counter resulting in 56 bits total
38        $id_bin .= str_pad( decbin( $counter % ( self::COUNTER_MAX + 1 ) ), 10, '0', STR_PAD_LEFT );
39        // Add the 32 bit node ID resulting in 88 bits total
40        $id_bin .= self::newNodeId();
41        if ( strlen( $id_bin ) !== 88 ) {
42            throw new RuntimeException( "Detected overflow for millisecond timestamp." );
43        }
44
45        return \Wikimedia\base_convert( $id_bin, 2, $base );
46    }
47
48    /**
49     * @param array $time Array of second and millisecond integers
50     * @return string 46 LSBs of "milliseconds since epoch" in binary (rolls over in 4201)
51     * @throws RuntimeException
52     */
53    protected static function millisecondsSinceEpochBinary( array $time ) {
54        [ $sec, $msec ] = $time;
55        $ts = 1000 * $sec + $msec;
56        if ( $ts > 2 ** 52 ) {
57            throw new RuntimeException(
58                __METHOD__ . ': sorry, this function doesn\'t work after the year 144680'
59            );
60        }
61
62        return substr( \Wikimedia\base_convert( (string)$ts, 10, 2, 46 ), -46 );
63    }
64
65    /**
66     * Rotate the nodeId to a random one. The stable node is best for
67     * generating "now" uid's on a cluster of servers, but repeated
68     * creation of historical uid's with one or a smaller number of
69     * machines requires use of a random node id.
70     *
71     * @return string String of 32 binary digits
72     */
73    protected static function newNodeId() {
74        // 4 bytes = 32 bits
75
76        return \Wikimedia\base_convert( MWCryptRand::generateHex( 8 ), 16, 2, 32 );
77    }
78}