TYPO3 API  SVNRelease
BigMath.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /**
00004  * BigMath: A math library wrapper that abstracts out the underlying
00005  * long integer library.
00006  *
00007  * PHP versions 4 and 5
00008  *
00009  * LICENSE: See the COPYING file included in this distribution.
00010  *
00011  * @access private
00012  * @package OpenID
00013  * @author JanRain, Inc. <openid@janrain.com>
00014  * @copyright 2005-2008 Janrain, Inc.
00015  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
00016  */
00017 
00018 /**
00019  * Needed for random number generation
00020  */
00021 require_once 'Auth/OpenID/CryptUtil.php';
00022 
00023 /**
00024  * Need Auth_OpenID::bytes().
00025  */
00026 require_once 'Auth/OpenID.php';
00027 
00028 /**
00029  * The superclass of all big-integer math implementations
00030  * @access private
00031  * @package OpenID
00032  */
00033 class Auth_OpenID_MathLibrary {
00034     /**
00035      * Given a long integer, returns the number converted to a binary
00036      * string.  This function accepts long integer values of arbitrary
00037      * magnitude and uses the local large-number math library when
00038      * available.
00039      *
00040      * @param integer $long The long number (can be a normal PHP
00041      * integer or a number created by one of the available long number
00042      * libraries)
00043      * @return string $binary The binary version of $long
00044      */
00045     function longToBinary($long)
00046     {
00047         $cmp = $this->cmp($long, 0);
00048         if ($cmp < 0) {
00049             $msg = __FUNCTION__ . " takes only positive integers.";
00050             trigger_error($msg, E_USER_ERROR);
00051             return null;
00052         }
00053 
00054         if ($cmp == 0) {
00055             return "\x00";
00056         }
00057 
00058         $bytes = array();
00059 
00060         while ($this->cmp($long, 0) > 0) {
00061             array_unshift($bytes, $this->mod($long, 256));
00062             $long = $this->div($long, pow(2, 8));
00063         }
00064 
00065         if ($bytes && ($bytes[0] > 127)) {
00066             array_unshift($bytes, 0);
00067         }
00068 
00069         $string = '';
00070         foreach ($bytes as $byte) {
00071             $string .= pack('C', $byte);
00072         }
00073 
00074         return $string;
00075     }
00076 
00077     /**
00078      * Given a binary string, returns the binary string converted to a
00079      * long number.
00080      *
00081      * @param string $binary The binary version of a long number,
00082      * probably as a result of calling longToBinary
00083      * @return integer $long The long number equivalent of the binary
00084      * string $str
00085      */
00086     function binaryToLong($str)
00087     {
00088         if ($str === null) {
00089             return null;
00090         }
00091 
00092         // Use array_merge to return a zero-indexed array instead of a
00093         // one-indexed array.
00094         $bytes = array_merge(unpack('C*', $str));
00095 
00096         $n = $this->init(0);
00097 
00098         if ($bytes && ($bytes[0] > 127)) {
00099             trigger_error("bytesToNum works only for positive integers.",
00100                           E_USER_WARNING);
00101             return null;
00102         }
00103 
00104         foreach ($bytes as $byte) {
00105             $n = $this->mul($n, pow(2, 8));
00106             $n = $this->add($n, $byte);
00107         }
00108 
00109         return $n;
00110     }
00111 
00112     function base64ToLong($str)
00113     {
00114         $b64 = base64_decode($str);
00115 
00116         if ($b64 === false) {
00117             return false;
00118         }
00119 
00120         return $this->binaryToLong($b64);
00121     }
00122 
00123     function longToBase64($str)
00124     {
00125         return base64_encode($this->longToBinary($str));
00126     }
00127 
00128     /**
00129      * Returns a random number in the specified range.  This function
00130      * accepts $start, $stop, and $step values of arbitrary magnitude
00131      * and will utilize the local large-number math library when
00132      * available.
00133      *
00134      * @param integer $start The start of the range, or the minimum
00135      * random number to return
00136      * @param integer $stop The end of the range, or the maximum
00137      * random number to return
00138      * @param integer $step The step size, such that $result - ($step
00139      * * N) = $start for some N
00140      * @return integer $result The resulting randomly-generated number
00141      */
00142     function rand($stop)
00143     {
00144         static $duplicate_cache = array();
00145 
00146         // Used as the key for the duplicate cache
00147         $rbytes = $this->longToBinary($stop);
00148 
00149         if (array_key_exists($rbytes, $duplicate_cache)) {
00150             list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
00151         } else {
00152             if ($rbytes[0] == "\x00") {
00153                 $nbytes = Auth_OpenID::bytes($rbytes) - 1;
00154             } else {
00155                 $nbytes = Auth_OpenID::bytes($rbytes);
00156             }
00157 
00158             $mxrand = $this->pow(256, $nbytes);
00159 
00160             // If we get a number less than this, then it is in the
00161             // duplicated range.
00162             $duplicate = $this->mod($mxrand, $stop);
00163 
00164             if (count($duplicate_cache) > 10) {
00165                 $duplicate_cache = array();
00166             }
00167 
00168             $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
00169         }
00170 
00171         do {
00172             $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
00173             $n = $this->binaryToLong($bytes);
00174             // Keep looping if this value is in the low duplicated range
00175         } while ($this->cmp($n, $duplicate) < 0);
00176 
00177         return $this->mod($n, $stop);
00178     }
00179 }
00180 
00181 /**
00182  * Exposes BCmath math library functionality.
00183  *
00184  * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
00185  * by the BCMath extension.
00186  *
00187  * @access private
00188  * @package OpenID
00189  */
00190 class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
00191     var $type = 'bcmath';
00192 
00193     function add($x, $y)
00194     {
00195         return bcadd($x, $y);
00196     }
00197 
00198     function sub($x, $y)
00199     {
00200         return bcsub($x, $y);
00201     }
00202 
00203     function pow($base, $exponent)
00204     {
00205         return bcpow($base, $exponent);
00206     }
00207 
00208     function cmp($x, $y)
00209     {
00210         return bccomp($x, $y);
00211     }
00212 
00213     function init($number, $base = 10)
00214     {
00215         return $number;
00216     }
00217 
00218     function mod($base, $modulus)
00219     {
00220         return bcmod($base, $modulus);
00221     }
00222 
00223     function mul($x, $y)
00224     {
00225         return bcmul($x, $y);
00226     }
00227 
00228     function div($x, $y)
00229     {
00230         return bcdiv($x, $y);
00231     }
00232 
00233     /**
00234      * Same as bcpowmod when bcpowmod is missing
00235      *
00236      * @access private
00237      */
00238     function _powmod($base, $exponent, $modulus)
00239     {
00240         $square = $this->mod($base, $modulus);
00241         $result = 1;
00242         while($this->cmp($exponent, 0) > 0) {
00243             if ($this->mod($exponent, 2)) {
00244                 $result = $this->mod($this->mul($result, $square), $modulus);
00245             }
00246             $square = $this->mod($this->mul($square, $square), $modulus);
00247             $exponent = $this->div($exponent, 2);
00248         }
00249         return $result;
00250     }
00251 
00252     function powmod($base, $exponent, $modulus)
00253     {
00254         if (function_exists('bcpowmod')) {
00255             return bcpowmod($base, $exponent, $modulus);
00256         } else {
00257             return $this->_powmod($base, $exponent, $modulus);
00258         }
00259     }
00260 
00261     function toString($num)
00262     {
00263         return $num;
00264     }
00265 }
00266 
00267 /**
00268  * Exposes GMP math library functionality.
00269  *
00270  * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
00271  * by the GMP extension.
00272  *
00273  * @access private
00274  * @package OpenID
00275  */
00276 class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
00277     var $type = 'gmp';
00278 
00279     function add($x, $y)
00280     {
00281         return gmp_add($x, $y);
00282     }
00283 
00284     function sub($x, $y)
00285     {
00286         return gmp_sub($x, $y);
00287     }
00288 
00289     function pow($base, $exponent)
00290     {
00291         return gmp_pow($base, $exponent);
00292     }
00293 
00294     function cmp($x, $y)
00295     {
00296         return gmp_cmp($x, $y);
00297     }
00298 
00299     function init($number, $base = 10)
00300     {
00301         return gmp_init($number, $base);
00302     }
00303 
00304     function mod($base, $modulus)
00305     {
00306         return gmp_mod($base, $modulus);
00307     }
00308 
00309     function mul($x, $y)
00310     {
00311         return gmp_mul($x, $y);
00312     }
00313 
00314     function div($x, $y)
00315     {
00316         return gmp_div_q($x, $y);
00317     }
00318 
00319     function powmod($base, $exponent, $modulus)
00320     {
00321         return gmp_powm($base, $exponent, $modulus);
00322     }
00323 
00324     function toString($num)
00325     {
00326         return gmp_strval($num);
00327     }
00328 }
00329 
00330 /**
00331  * Define the supported extensions.  An extension array has keys
00332  * 'modules', 'extension', and 'class'.  'modules' is an array of PHP
00333  * module names which the loading code will attempt to load.  These
00334  * values will be suffixed with a library file extension (e.g. ".so").
00335  * 'extension' is the name of a PHP extension which will be tested
00336  * before 'modules' are loaded.  'class' is the string name of a
00337  * {@link Auth_OpenID_MathWrapper} subclass which should be
00338  * instantiated if a given extension is present.
00339  *
00340  * You can define new math library implementations and add them to
00341  * this array.
00342  */
00343 function Auth_OpenID_math_extensions()
00344 {
00345     $result = array();
00346 
00347     if (!defined('Auth_OpenID_BUGGY_GMP')) {
00348         $result[] =
00349             array('modules' => array('gmp', 'php_gmp'),
00350                   'extension' => 'gmp',
00351                   'class' => 'Auth_OpenID_GmpMathWrapper');
00352     }
00353 
00354     $result[] = array(
00355                       'modules' => array('bcmath', 'php_bcmath'),
00356                       'extension' => 'bcmath',
00357                       'class' => 'Auth_OpenID_BcMathWrapper');
00358 
00359     return $result;
00360 }
00361 
00362 /**
00363  * Detect which (if any) math library is available
00364  */
00365 function Auth_OpenID_detectMathLibrary($exts)
00366 {
00367     $loaded = false;
00368     $hasDl = function_exists('dl');
00369 
00370     foreach ($exts as $extension) {
00371         // See if the extension specified is already loaded.
00372         if ($extension['extension'] &&
00373             extension_loaded($extension['extension'])) {
00374             $loaded = true;
00375         }
00376 
00377         // Try to load dynamic modules.
00378         if (!$loaded && $hasDl) {
00379             foreach ($extension['modules'] as $module) {
00380                 if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
00381                     $loaded = true;
00382                     break;
00383                 }
00384             }
00385         }
00386 
00387         // If the load succeeded, supply an instance of
00388         // Auth_OpenID_MathWrapper which wraps the specified
00389         // module's functionality.
00390         if ($loaded) {
00391             return $extension;
00392         }
00393     }
00394 
00395     return false;
00396 }
00397 
00398 /**
00399  * {@link Auth_OpenID_getMathLib} checks for the presence of long
00400  * number extension modules and returns an instance of
00401  * {@link Auth_OpenID_MathWrapper} which exposes the module's
00402  * functionality.
00403  *
00404  * Checks for the existence of an extension module described by the
00405  * result of {@link Auth_OpenID_math_extensions()} and returns an
00406  * instance of a wrapper for that extension module.  If no extension
00407  * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
00408  * returned, which wraps the native PHP integer implementation.  The
00409  * proper calling convention for this method is $lib =&
00410  * Auth_OpenID_getMathLib().
00411  *
00412  * This function checks for the existence of specific long number
00413  * implementations in the following order: GMP followed by BCmath.
00414  *
00415  * @return Auth_OpenID_MathWrapper $instance An instance of
00416  * {@link Auth_OpenID_MathWrapper} or one of its subclasses
00417  *
00418  * @package OpenID
00419  */
00420 function &Auth_OpenID_getMathLib()
00421 {
00422     // The instance of Auth_OpenID_MathWrapper that we choose to
00423     // supply will be stored here, so that subseqent calls to this
00424     // method will return a reference to the same object.
00425     static $lib = null;
00426 
00427     if (isset($lib)) {
00428         return $lib;
00429     }
00430 
00431     if (Auth_OpenID_noMathSupport()) {
00432         $null = null;
00433         return $null;
00434     }
00435 
00436     // If this method has not been called before, look at
00437     // Auth_OpenID_math_extensions and try to find an extension that
00438     // works.
00439     $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
00440     if ($ext === false) {
00441         $tried = array();
00442         foreach (Auth_OpenID_math_extensions() as $extinfo) {
00443             $tried[] = $extinfo['extension'];
00444         }
00445         $triedstr = implode(", ", $tried);
00446 
00447         Auth_OpenID_setNoMathSupport();
00448 
00449         $result = null;
00450         return $result;
00451     }
00452 
00453     // Instantiate a new wrapper
00454     $class = $ext['class'];
00455     $lib = new $class();
00456 
00457     return $lib;
00458 }
00459 
00460 function Auth_OpenID_setNoMathSupport()
00461 {
00462     if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
00463         define('Auth_OpenID_NO_MATH_SUPPORT', true);
00464     }
00465 }
00466 
00467 function Auth_OpenID_noMathSupport()
00468 {
00469     return defined('Auth_OpenID_NO_MATH_SUPPORT');
00470 }
00471 
00472 ?>