Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.61% covered (warning)
82.61%
38 / 46
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uuid
82.61% covered (warning)
82.61%
38 / 46
77.78% covered (warning)
77.78%
7 / 9
20.90
0.00% covered (danger)
0.00%
0 / 1
 v4BytesFactory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 v4HexFactory
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 asBytes
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 asHex
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 bytesToHex
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 hexToHex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hexToBytes
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 isBytes
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isHex
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\WikispeechSpeechDataCollector;
4
5/**
6 * @file
7 * @ingroup Extensions
8 * @license GPL-2.0-or-later
9 */
10
11use InvalidArgumentException;
12
13/**
14 * UUID helper methods.
15 *
16 * @since 0.1.0
17 */
18class Uuid {
19
20    /**
21     * V4, pseudo-random UUID.
22     *
23     * @return string
24     * @see Uuid::v4HexFactory()
25     * @since 0.1.0
26     */
27    public static function v4BytesFactory() {
28        return self::hexToBytes( self::v4HexFactory() );
29    }
30
31    /**
32     * Return a UUID (version 4) using random bytes
33     * Note that version 4 follows the format:
34     *     xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
35     * where y is one of: [8, 9, A, B]
36     *
37     * We use (random_bytes(1) & 0x0F) | 0x40 to force
38     * the first character of hex value to always be 4
39     * in the appropriate position.
40     *
41     * For 4: http://3v4l.org/q2JN9
42     * For Y: http://3v4l.org/EsGSU
43     * For the whole shebang: https://3v4l.org/LNgJb
44     *
45     * https://stackoverflow.com/a/31460273/2224584
46     * https://paragonie.com/b/JvICXzh_jhLyt4y3
47     *
48     * @param bool $addDashes If true then '-' is added after byte 4, 6, 8 and 10.
49     * @return string
50     * @since 0.1.0
51     */
52    public static function v4HexFactory( bool $addDashes = false ) {
53        return implode( $addDashes ? '-' : '', [
54            bin2hex( random_bytes( 4 ) ),
55            bin2hex( random_bytes( 2 ) ),
56            bin2hex( chr( ( ord( random_bytes( 1 ) ) & 0x0F ) | 0x40 ) ) . bin2hex( random_bytes( 1 ) ),
57            bin2hex( chr( ( ord( random_bytes( 1 ) ) & 0x3F ) | 0x80 ) ) . bin2hex( random_bytes( 1 ) ),
58            bin2hex( random_bytes( 6 ) )
59        ] );
60    }
61
62    /**
63     * @param string|null $input A valid UUID as bytes or in hex form, or null.
64     * @return string|null
65     * @throws InvalidArgumentException If not null nor a valid UUID.
66     * @since 0.1.0
67     */
68    public static function asBytes( ?string $input ): ?string {
69        if ( $input === null ) {
70            return null;
71        } elseif ( self::isBytes( $input ) ) {
72            return $input;
73        } elseif ( self::isHex( $input ) ) {
74            return self::hexToBytes( $input );
75        }
76        throw new InvalidArgumentException( "Not a valid v4 UUID: $input" );
77    }
78
79    /**
80     * @param string|null $input A valid UUID as bytes or in hex form, or null.
81     * @param bool $addDashes If true then '-' is added after byte 4, 6, 8 and 10.
82     * @return string|null
83     * @throws InvalidArgumentException If not null nor a valid UUID.
84     * @since 0.1.0
85     */
86    public static function asHex( ?string $input, bool $addDashes = false ): ?string {
87        if ( $input === null ) {
88            return null;
89        } elseif ( self::isBytes( $input ) ) {
90            return self::bytesToHex( $input, $addDashes );
91        } elseif ( self::isHex( $input ) ) {
92            return self::hexToHex( $input, $addDashes );
93        }
94        throw new InvalidArgumentException( "Not a valid v4 UUID: $input" );
95    }
96
97    /**
98     * @param string $input
99     * @param bool $addDashes If true then '-' is added after byte 4, 6, 8 and 10.
100     * @return string
101     * @since 0.1.0
102     */
103    private static function bytesToHex( string $input, bool $addDashes = false ): string {
104        $hex = bin2hex( $input );
105        if ( !$addDashes ) {
106            return $hex;
107        }
108        return substr( $hex, 0, 8 ) . '-' .
109            substr( $hex, 8, 4 ) . '-' .
110            substr( $hex, 12, 4 ) . '-' .
111            substr( $hex, 16, 4 ) . '-' .
112            substr( $hex, 20, 12 );
113    }
114
115    /**
116     * @param string $input
117     * @param bool $addDashes If true then '-' is added after byte 4, 6, 8 and 10.
118     * @return string
119     * @since 0.1.0
120     */
121    private static function hexToHex( string $input, bool $addDashes = false ): string {
122        return self::bytesToHex( self::hexToBytes( $input ), $addDashes );
123    }
124
125    /**
126     * @param string $input
127     * @return string
128     * @since 0.1.0
129     */
130    private static function hexToBytes( string $input ): string {
131        $numberOfBytes = strlen( $input );
132        if ( $numberOfBytes === 32 ) {
133            return hex2bin( $input );
134        }
135        // only private internals call to this method, should be safe
136        // elseif ( $numberOfBytes == 36 ) {
137        $hex = substr( $input, 0, 8 ) .
138            substr( $input, 9, 4 ) .
139            substr( $input, 14, 4 ) .
140            substr( $input, 19, 4 ) .
141            substr( $input, 24, 12 );
142        return hex2bin( $hex );
143    }
144
145    /**
146     * @param string $input
147     * @return bool True if input is a valid raw 16 byte UUID V4.
148     * @since 0.1.0
149     */
150    public static function isBytes( string $input ): bool {
151        return strlen( $input ) === 16
152            && self::isHex( self::bytesToHex( $input, true ) );
153    }
154
155    /**
156     * @param string $input
157     * @return bool True if input is a valid hex formatted UUID V4 with or without dashes.
158     * @since 0.1.0
159     */
160    public static function isHex( string $input ): bool {
161        return preg_match(
162        '/^[0-9A-F]{8}(-?)[0-9A-F]{4}(\1)4[0-9A-F]{3}(\1)[89AB][0-9A-F]{3}(\1)[0-9A-F]{12}$/i',
163            $input
164        );
165    }
166
167}