1<?php
2/*
3 * Copyright 2008-2010 GuardTime AS
4 *
5 * This file is part of the GuardTime PHP SDK.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20/**
21 * @package util
22 */
23
24/**
25 * Collection of miscellaneous commonly used utility functions.
26 *
27 * @package util
28 */
29class GTUtil {
30
31    /**
32     * Computes the greatest common divisor (GCD) of two integers.
33     *
34     * Greatest common divisor is the largest integer that divides both numbers
35     * without remainder.
36     *
37     * <code>
38     * echo GTUtil::gcd(4, 8);
39     *
40     * // output:
41     * // 4
42     * </code>
43     *
44     * @static
45     * @param  int $a the first integer
46     * @param  int $b the second integer
47     * @return int the greatest common divisor of a and b or 0 if both are 0
48     */
49    public static function gcd($a, $b) {
50
51        $a = abs((int) $a);
52        $b = abs((int) $b);
53
54        while ($a > 0) {
55            $c = $b % $a;
56            $b = $a;
57            $a = $c;
58        }
59
60        return $b;
61
62    }
63
64    /**
65     * Computes the least common multiple (LCM) of two integers.
66     *
67     * Least common multiple is the smallest positive integer that can be divided
68     * by both numbers without a remainder.
69     *
70     * <code>
71     * echo GTUtil::lcm(4, 6);
72     *
73     * // output:
74     * // 12
75     * </code>
76     *
77     * @static
78     * @throws GTException when the result is too big to fit into {@code int}
79     * @param  int $a the first integer
80     * @param  int $b the second integer
81     * @return int the least common multiple of a and b, or 0 if either a or b is 0
82     */
83    public static function lcm($a, $b) {
84
85        $a = (int) $a;
86        $b = (int) $b;
87
88        if ($a == 0 || $b == 0) {
89            return 0;
90        }
91
92        $a = abs($a) / GTUtil::gcd($a, $b);
93        $b = abs($b);
94
95        if ($a > PHP_INT_MAX / $b) {
96            throw new GTException("Integer overflow");
97        }
98
99        return $a * $b;
100    }
101
102    /**
103     * Writes data to file.
104     *
105     * Example:
106     *
107     * <code>
108     * GTUtil::write('file.txt', array(1, 2, 3));
109     * </code>
110     *
111     * @static
112     * @throws GTException
113     * @param  string $file file name to write to
114     * @param  array $bytes byte array that contains the bytes to write
115     * @return void
116     *
117     * @see read
118     */
119    public static function write($file, array $bytes) {
120
121        if (empty($file)) {
122            throw new GTException("parameter file is required");
123        }
124
125        if ($bytes == null) {
126            throw new GTException("parameter bytes is required");
127        }
128
129        if (!is_array($bytes)) {
130            throw new GTException("parameter bytes must be an array");
131        }
132
133        $fp = fopen($file, 'wb+');
134
135        if (!$fp) {
136            throw new GTException("Unable to open file {$file} for writing");
137        }
138
139        if (!fwrite($fp, GTUtil::fromByteArray($bytes))) {
140            throw new GTException("Unable to write to bytes to file {$file}");
141        }
142
143        if (!fclose($fp)) {
144            throw new GTException("Unable to close file after writing {$file}");
145        }
146
147    }
148
149    /**
150     * Touches specified file.
151     *
152     * @static
153     * @throws GTException
154     * @param  $file the file to touch
155     * @return void
156     */
157    public static function touch($file) {
158
159        if (empty($file)) {
160            throw new GTException("parameter file is required");
161        }
162
163        touch($file);
164    }
165
166    /**
167     * Reads data from file.
168     *
169     * @static
170     * @throws GTException
171     * @param  string $file file name to read from
172     * @return array byte array read from $file
173     *
174     * @see write
175     */
176    public static function read($file) {
177
178        if (empty($file)) {
179            throw new GTException("parameter file is required");
180        }
181
182        if (!is_file($file)) {
183            throw new GTException("file {$file} does not exist");
184        }
185
186        if (!is_readable($file)) {
187            throw new GTException("file {$file} is not readable");
188        }
189
190        $fp = fopen($file, 'rb');
191
192        if (!$fp) {
193            throw new GTException("Unable to open file {$file} for reading");
194        }
195
196        $length = filesize($file);
197
198        if ($length > 0) {
199            $data = fread($fp, $length);
200
201        } else {
202            $data = "";
203
204        }
205
206        if ($data === false) {
207            throw new GTException("Unable to read from file {$file}");
208        }
209
210        fclose($fp);
211
212        return GTUtil::toByteArray($data);
213    }
214
215    /**
216     * Wrapper method for OpenSSL asn1parse.
217     *
218     * @static
219     * @param  string $file file containing a valid ASN.1 DER object
220     * @return void
221     */
222    public static function printAsn1($file) {
223        passthru("openssl asn1parse -i -inform DER -in {$file}");
224    }
225
226    /**
227     * Decodes an ASN.1 formatted time to unix timestamp.
228     *
229     * @static
230     * @throws GTException
231     * @param  string $time ASN.1 formatted time
232     * @param  string $timezone timezone to use
233     * @return int php unix timestamp
234     */
235    public static function decodeTime($time, $timezone = null) {
236
237        if ($timezone != null) {
238
239            $old_timezone = date_default_timezone_get();
240            $new_timezone = date_default_timezone_set($timezone);
241
242            if ($new_timezone === false) {
243                throw new GTException("Unable to set timezone to {$timezone}");
244            }
245
246        }
247
248        if ($time == null) {
249            throw new GTException("parameter time must not be empty");
250        }
251
252        if (strlen($time) != 15) {
253            throw new GTException("parameter time has invalid length");
254        }
255
256        $tokens = array(
257            'year' => substr($time, 0, 4),
258            'month' => substr($time, 4, 2),
259            'day' => substr($time, 6, 2),
260            'hour' => substr($time, 8, 2),
261            'minute' => substr($time, 10, 2),
262            'second' => substr($time, 12, 2)
263        );
264
265        foreach ($tokens as $name => $value) {
266            if (!ctype_digit($value)) {
267                throw new GTException("Invalid time encoding, {$name} = {$value}");
268            }
269        }
270
271        $time = mktime(
272            (int) $tokens['hour'],
273            (int) $tokens['minute'],
274            (int) $tokens['second'],
275            (int) $tokens['month'],
276            (int) $tokens['day'],
277            (int) $tokens['year']
278        );
279
280        if ($timezone != null) {
281            date_default_timezone_set($old_timezone);
282        }
283
284        return $time;
285    }
286
287    /**
288     * Formats a PHP unix timestamp in a more human readable format (%Y-%m-%d %H:%M:%S UTC)
289     *
290     * @static
291     * @throws GTException
292     * @param  $time php unix timestamp
293     * @param  $timezone the timezone to use
294     * @return string formatted datetime
295     */
296    public static function formatTime($time, $timezone = null) {
297
298        if ($timezone != null) {
299
300            $old_timezone = date_default_timezone_get();
301            $new_timezone = date_default_timezone_set($timezone);
302
303            if ($new_timezone === false) {
304                throw new GTException("Unable to set timezone to {$timezone}");
305            }
306
307        }
308
309        $formatted = strftime('%Y-%m-%d %H:%M:%S UTC', $time);
310
311        if ($timezone != null) {
312            date_default_timezone_set($old_timezone);
313        }
314
315        return $formatted;
316    }
317
318    /**
319     * Pads $array by prepending $value until the size of $array equals $length.
320     *
321     * @static
322     * @param  $array the array to pad
323     * @param  $length the length to pad to
324     * @param  $value padding value
325     * @return void
326     */
327    public static function lpad(array &$array, $length, $value) {
328        while (count($array) < $length) {
329            array_unshift($array, $value);
330        }
331    }
332
333    /**
334     * Pads $array by appending $value until the size of $array equals $length.
335     *
336     * @static
337     * @param  $array the array to pad
338     * @param  $length the length to pad to
339     * @param  $value padding value
340     * @return void
341     */
342    public static function rpad(array &$array, $length, $value) {
343        while (count($array) < $length) {
344            array_push($array, $value);
345        }
346    }
347
348    /**
349     * Converts a string into an array of characters.
350     *
351     * <code>
352     * $result = GTUtil::toArray('foo');
353     *
354     * print_r($result);
355     *
356     * // output:
357     * // Array
358     * // (
359     * //   [0] => f
360     * //   [1] => o
361     * //   [2] => o
362     * // )
363     * </code>
364     *
365     * @static
366     * @param  string $string the input string
367     * @return array an array built from characters in string
368     */
369    public static function toArray($string) {
370
371        $result = array();
372
373        if (empty($string)) {
374            return $result;
375        }
376
377        for ($i = 0; $i < strlen($string); $i++) {
378            array_push($result, $string{$i});
379        }
380
381        return $result;
382
383    }
384
385    /**
386     * Converts an array of characters into a string
387     *
388     * <code>
389     * echo GTUtil::fromArray(array('f', 'o', 'o'));
390     *
391     * // output:
392     * // foo
393     * </code>
394     *
395     * @static
396     * @param  $array the input array
397     * @return string a string built from characters in array
398     */
399    public static function fromArray(array $array = null) {
400
401        $result = "";
402
403        if (empty($array)) {
404            return $result;
405        }
406
407        for ($i = 0; $i < count($array); $i++) {
408            $result .= $array[$i];
409        }
410
411        return $result;
412
413    }
414
415    /**
416     * Converts a string into an array of bytes.
417     *
418     * Each byte in the resulting array will represent the ASCII value of each
419     * character in the input string
420     *
421     * <code>
422     * $result = GTUtil::toByteArray('foo');
423     *
424     * print_r($result);
425     *
426     * // output:
427     * // Array
428     * // (
429     * //   [0] => 102
430     * //   [1] => 111
431     * //   [2] => 111
432     * // )
433     * </code>
434     *
435     * @static
436     * @param  $string the input string
437     * @return array an array of bytes representing the characters in string
438     */
439    public static function toByteArray($string) {
440
441        $result = array();
442
443        if (empty($string)) {
444            return $result;
445        }
446
447        for ($i = 0; $i < strlen($string); $i++) {
448            array_push($result, ord($string{$i}));
449        }
450
451        return $result;
452
453    }
454
455    /**
456     * Converts a byte array into a string.
457     *
458     * Each character in the string will represent the ASCII code of each
459     * byte in the input array
460     *
461     * <code>
462     * $bytes = array(
463     *   ord('f'),
464     *   ord('o'),
465     *   ord('o')
466     * );
467     *
468     * echo GTUtil::fromByteArray($bytes);
469     *
470     * // output:
471     * // foo
472     * </code>
473     *
474     * @static
475     * @param  $array the input array
476     * @return string a string built from bytes in the array
477     */
478    public static function fromByteArray(array $array = null) {
479
480        $result = "";
481
482        if (empty($array)) {
483            return $result;
484        }
485
486        for ($i = 0; $i < count($array); $i++) {
487            $result .= chr($array[$i]);
488        }
489
490        return $result;
491
492    }
493
494    /**
495     * Reads a short (2 bytes) integer from the given byte array.
496     *
497     * @static
498     * @throws GTException
499     * @param  array $array the byte array to read from
500     * @param  int $position the position in the array to read from
501     * @return int short integer from the given byte array
502     */
503    public static function readShort(array $array, $position) {
504
505        if ($position + 2 > count($array) - 1) {
506            throw new GTException("Array index out of bounds");
507        }
508
509        $integer = new GTBigInteger(
510            array_slice($array, $position, 2)
511        );
512
513        return (int) $integer->getValue();
514    }
515
516    /**
517     * Reads an integer (4 bytes) from the given byte array.
518     *
519     * @static
520     * @throws GTException
521     * @param  array $array the byte array to read from
522     * @param  int $position the position in the array to read from
523     * @return int 4 byte integer from the given byte array
524     */
525    public static function readInt(array $array, $position) {
526
527        if ($position + 4 > count($array) - 1) {
528            throw new GTException("Array index out of bounds");
529        }
530
531        $integer = new GTBigInteger(
532            array_slice($array, $position, 4)
533        );
534
535        return (int) $integer->getValue();
536    }
537
538    /**
539     * Reads a GTBigInteger (8 bytes) from the given byte array.
540     *
541     * @static
542     * @throws GTException
543     * @param  array $array the byte array to read from
544     * @param  int $position the position in the array to read from
545     * @return GTBigInteger 8 byte GTBigInteger from the given byte array
546     */
547    public static function readLong(array $array, $position) {
548
549        if ($position + 8 > count($array) - 1) {
550            throw new GTException("Array index out of bounds");
551        }
552
553        return new GTBigInteger(
554            array_slice($array, $position, 8)
555        );
556    }
557
558    /**
559     * Computes and adds CRC32 to the given byte array.
560     *
561     * @static
562     * @param  $bytes input byte array
563     * @return array bytes with added CRC32 checksum
564     */
565    public static function addCrc32(array $bytes) {
566
567        $checksum = crc32(GTUtil::fromByteArray($bytes));
568        $checksum = sprintf("%u", $checksum); // make unsigned, needed for 32 bit PHP
569        $checksum = new GTBigInteger($checksum);
570        $checksum = $checksum->toBytes();
571
572        // remove leading 0x0-s
573        while (count($checksum) > 4) {
574            array_shift($checksum);
575        }
576
577        foreach ($checksum as $byte) {
578            array_push($bytes, $byte);
579        }
580
581        return $bytes;
582    }
583
584    /**
585     * Gets OpenSSL version as string.
586     *
587     * @static
588     * @return string OpenSSL version as string, example: 0.9.8
589     */
590    public static function getOpensslVersion() {
591
592        $version = new GTBigInteger(OPENSSL_VERSION_NUMBER);
593
594        $mask = new GTBigInteger(0xF);
595
596        $major = $version->shiftRight(28)->bitAnd($mask)->getValue();
597        $minor = $version->shiftRight(20)->bitAnd($mask)->getValue();
598        $fix = $version->shiftRight(12)->bitAnd($mask)->getValue();
599
600        return "{$major}.{$minor}.{$fix}";
601    }
602
603}
604
605?>
606