xref: /dokuwiki/inc/Ip32.php (revision ba6d945f137b76d4feccb1ad84ccd6304a56e3b3)
191da8d44SWillForan<?php
291da8d44SWillForan
391da8d44SWillForan/**
491da8d44SWillForan * DokuWiki 32bit shims for IP address functions.
591da8d44SWillForan *
691da8d44SWillForan * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
791da8d44SWillForan */
891da8d44SWillForan
991da8d44SWillForannamespace dokuwiki;
1091da8d44SWillForan
1191da8d44SWillForanclass Ip32
1291da8d44SWillForan{
132f70db90SWillForan    public static $b32 = '4294967296';
1491da8d44SWillForan    /**
1591da8d44SWillForan     * slow and ugly bitwise_and for 32bit arch
1691da8d44SWillForan     * @param $u64 unsigned 64bit integer as string
17a060f5a0SWillForan     *            likely from ipv6UpperLowerOn32
1891da8d44SWillForan     * @param $pow 0-64 power of 2 for bitmask
1991da8d44SWillForan     */
20*ba6d945fSWillForan    public static function bitmask64On32(string $u64, int $pow): string
21*ba6d945fSWillForan    {
222f70db90SWillForan        $upper = bcdiv($u64, Ip32::$b32, 0);
232f70db90SWillForan        $lower = bcmod($u64, Ip32::$b32);
242f70db90SWillForan        // str of len 32 all 0s and 1s
252f70db90SWillForan        $upper_bin = Ip32::decimalToBinary32($upper);
262f70db90SWillForan        $lower_bin = Ip32::decimalToBinary32($lower);
272f70db90SWillForan        $bin = $upper_bin . $lower_bin;
2891da8d44SWillForan
29a060f5a0SWillForan        $mask = Ip32::makeBitmaskOn32(64 - $pow);
3091da8d44SWillForan
3191da8d44SWillForan        // most right is lowest bit
3291da8d44SWillForan        $res = '0';
3391da8d44SWillForan        for ($i = 0; $i < 64; $i++) {
3491da8d44SWillForan            if (bcmul($bin[$i], $mask[$i]) == 1) {
3591da8d44SWillForan                $res = bcadd($res, bcpow(2, 63 - $i));
3691da8d44SWillForan            }
3791da8d44SWillForan        }
3891da8d44SWillForan        return $res;
3991da8d44SWillForan    }
4091da8d44SWillForan
4191da8d44SWillForan
4291da8d44SWillForan    /**
4391da8d44SWillForan     * modeling bitshift like  ~0 << $pow for 32-bit arch
4491da8d44SWillForan     * @param pow power of 2 for mask
4591da8d44SWillForan     * @return 64-char string of 1 and 0s
4691da8d44SWillForan     * pow=1
4791da8d44SWillForan     * 1111111111111111111111111111111111111111111111111111111111111110
4891da8d44SWillForan     * pow=63
4991da8d44SWillForan     * 1000000000000000000000000000000000000000000000000000000000000000
5091da8d44SWillForan     * pow=64
5191da8d44SWillForan     * 0000000000000000000000000000000000000000000000000000000000000000
5291da8d44SWillForan     */
53*ba6d945fSWillForan    private static function makeBitmaskOn32(int $pow): string
54*ba6d945fSWillForan    {
5591da8d44SWillForan        $pow = $pow < 0 ? 64 - $pow : $pow;
5691da8d44SWillForan        $mask = sprintf("%064d", 0);
5791da8d44SWillForan        for ($i = 0; $i < 64; $i++) {
5891da8d44SWillForan            if ($i >= $pow) {
5991da8d44SWillForan                $mask[63 - $i] = '1';
6091da8d44SWillForan            }
6191da8d44SWillForan        }
6291da8d44SWillForan        return $mask;
6391da8d44SWillForan    }
6491da8d44SWillForan
6591da8d44SWillForan    /**
6691da8d44SWillForan     * conversion of inet_pton ipv6 into 64-bit upper and lower
6791da8d44SWillForan     * bcmath version for 32-bit architecture
6891da8d44SWillForan     * w/o no unpack('J') - unsigned long long (always 64 bit, big endian byte order)
6991da8d44SWillForan     *
7091da8d44SWillForan     * results match unpack('Jupper/Jlower', $binary)
7191da8d44SWillForan     *
7291da8d44SWillForan     * @param string $binary inet_pton's ipv6 16 element binary
7391da8d44SWillForan     *
742f70db90SWillForan     * @return string[] upper 64 and lower 64 for ipToNumber
7591da8d44SWillForan     */
76*ba6d945fSWillForan    public static function ipv6UpperLowerOn32(string $binary)
77*ba6d945fSWillForan    {
7891da8d44SWillForan       // unpack into four 32-bit unsigned ints to recombine as 2 64-bit
7991da8d44SWillForan        $parts = unpack('N4', $binary);
802f70db90SWillForan        $upper = Ip32::partsTo64($parts[1], $parts[2]);
812f70db90SWillForan        $lower = Ip32::partsTo64($parts[3], $parts[4]);
8291da8d44SWillForan        return ['upper' => $upper, 'lower' => $lower];
8391da8d44SWillForan    }
842f70db90SWillForan
85*ba6d945fSWillForan    private static function partsTo64(string $high, string $low): string
86*ba6d945fSWillForan    {
872f70db90SWillForan        // signed to unsigned
882f70db90SWillForan        $high = $high < 0 ? bcadd($high, Ip32::$b32) : $high;
892f70db90SWillForan        $low  = $low < 0 ? bcadd($low, Ip32::$b32) : $low;
902f70db90SWillForan
912f70db90SWillForan        return bcadd(bcmul($high, Ip32::$b32), $low);
922f70db90SWillForan    }
932f70db90SWillForan
942f70db90SWillForan    /**
952f70db90SWillForan     * Convert a decimal number to 32-bit binary string using bcmath
962f70db90SWillForan     * Handles large numbers that exceed PHP_INT_MAX on 32-bit systems
972f70db90SWillForan     *
982f70db90SWillForan     * @param string $decimal The decimal number as string
992f70db90SWillForan     * @return string 32-bit binary representation
1002f70db90SWillForan     */
101*ba6d945fSWillForan    private static function decimalToBinary32(string $decimal): string
102*ba6d945fSWillForan    {
1032f70db90SWillForan        if (bccomp($decimal, '0') == 0) {
1042f70db90SWillForan            return str_repeat('0', 32);
1052f70db90SWillForan        }
1062f70db90SWillForan        $binary = '';
1072f70db90SWillForan        $num = $decimal;
1082f70db90SWillForan        for ($i = 31; $i >= 0; $i--) {
1092f70db90SWillForan            $power = bcpow('2', (string)$i);
1102f70db90SWillForan            if (bccomp($num, $power) >= 0) {
1112f70db90SWillForan                $binary .= '1';
1122f70db90SWillForan                $num = bcsub($num, $power);
1132f70db90SWillForan            } else {
1142f70db90SWillForan                $binary .= '0';
1152f70db90SWillForan            }
1162f70db90SWillForan        }
1172f70db90SWillForan        return $binary;
1182f70db90SWillForan    }
11991da8d44SWillForan}
120