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 * A generic implementation for RFC 4648 base-X encoders/decoders. 26 * 27 * @link http://tools.ietf.org/html/rfc4648 RFC 4648 28 * @see GTBase16, GTBase32, GTBase64 29 * 30 * @package util 31 */ 32class GTBaseX { 33 34 private $values; 35 private $min; 36 private $max; 37 private $bits; 38 private $block; 39 private $pad; 40 41 /** 42 * Constructs an encoder/decoder using the given characters. 43 * 44 * Example for Base16: 45 * 46 * <code> 47 * $base = new BaseX('0123456789ABCDEF', false, ' '); 48 * </code> 49 * 50 * @throws GTException 51 * @param string $alphabet the encoding alphabet 52 * @param boolean $caseSensitive if true both the encoder/decoder will be case sensitive 53 * @param string $pad the padding characters used to even out the encoded output 54 * 55 * @see GTBase16, GTBase32, GTBase64 56 */ 57 public function __construct($alphabet, $caseSensitive, $pad) { 58 59 $this->bits = 1; 60 61 while ((1 << $this->bits) < strlen($alphabet)) { 62 $this->bits++; 63 } 64 65 if ((1 << $this->bits) != strlen($alphabet)) { 66 throw new GTException("The size of the encoding alphabet is not a power of 2"); 67 } 68 69 $this->block = 8 / GTUtil::gcd(8, $this->bits); 70 $this->chars = GTUtil::toArray($alphabet); 71 72 $this->min = -1; 73 $this->max = -1; 74 75 if ($caseSensitive) { 76 77 $this->addMinMax($alphabet); 78 79 $this->values = array(); 80 81 for ($i = 0; $i < ($this->max - $this->min) + 1; $i++) { 82 array_push($this->values, -1); 83 } 84 85 $this->addChars($alphabet); 86 87 } else { 88 89 $this->addMinMax(strtoupper($alphabet)); 90 $this->addMinMax(strtolower($alphabet)); 91 92 $this->values = array(); 93 94 for ($i = 0; $i < ($this->max - $this->min) + 1; $i++) { 95 array_push($this->values, -1); 96 } 97 98 $this->addChars(strtolower($alphabet)); 99 $this->addChars(strtoupper($alphabet)); 100 101 } 102 103 if ($pad >= $this->min && $pad <= $this->max && $this->values[$pad - $this->min] != -1) { 104 throw new GTException("The padding character appears in the encoding alphabet"); 105 } 106 107 $this->pad = $pad; 108 109 } 110 111 /** 112 * Encodes the given bytes into a base-X string. 113 * 114 * This method also optionally inserts a separator into the result with given frequency. 115 * 116 * @throws GTException 117 * @param array $bytes the bytes to encode 118 * @param int $offset the start offset in bytes array 119 * @param int $length the number of bytes to encode 120 * @param string $separator if separator is not null it's inserted into the encoded string at frequency 121 * @param int $frequency if separator is not null it's inserted into the encoded string at frequency 122 * @return string string containing the encoded data 123 */ 124 public function encode($bytes, $offset = null, $length = null, $separator = null, $frequency = null) { 125 126 if (empty($bytes)) { 127 throw new GTException("parameter bytes is required"); 128 } 129 130 if (!is_array($bytes)) { 131 throw new GTException("parameter bytes must be an array of bytes"); 132 } 133 134 if ($offset == null) { 135 $offset = 0; 136 } 137 138 if ($length == null) { 139 $length = count($bytes); 140 } 141 142 $offset = (int) $offset; 143 $length = (int) $length; 144 145 if ($offset < 0 || $offset > count($bytes)) { 146 throw new GTException("parameter offset out of bounds"); 147 } 148 149 if ($length < 0 || $offset + $length > count($bytes)) { 150 throw new GTException("parameter length out of bounds"); 151 } 152 153 if ($separator == null) { 154 $frequency = 0; 155 156 } else { 157 158 for ($i = 0; $i < strlen($separator); $i++) { 159 $c = $separator{$i}; 160 161 if ($c >= $this->min && $c <= $this->max && $this->values[$c - $this->min] != -1) { 162 throw new GTException("parameter separator contains characters from the encoding alphabet"); 163 } 164 } 165 } 166 167 $result = ""; 168 169 $i = 0; // number of output characters produced 170 $j = 0; // number of input bytes consumed 171 172 $buff = 0; // buffer of input bits not yet sent to output 173 $size = 0; // number of bits in the buffer 174 $mask = (1 << $this->bits) - 1; 175 176 while ($this->bits * $i < 8 * $length) { 177 178 if ($frequency > 0 && $i > 0 && $i % $frequency == 0) { 179 $result .= $separator; 180 } 181 182 while ($size < $this->bits) { 183 184 $next = ($j < $length ? $bytes[$offset + $j] : 0); 185 $j++; 186 187 $buff = ($buff << 8) | ($next & 0xff); 188 $size += 8; 189 190 } 191 192 $result .= $this->chars[($buff >> ($size - $this->bits)) & $mask]; 193 $size -= $this->bits; 194 195 $i++; 196 197 } 198 199 // pad 200 201 while ($i % $this->block != 0) { 202 if ($frequency > 0 && $i > 0 && $i % $frequency == 0) { 203 $result .= $separator; 204 } 205 206 $result .= $this->pad; 207 $i++; 208 } 209 210 return $result; 211 212 } 213 214 /** 215 * Decodes the given base-X string into bytes. 216 * 217 * This method silently ignores any non-base-X characters. 218 * 219 * @throws GTException 220 * @param string $string the string to decode 221 * @return array decoded bytes 222 */ 223 public function decode($string) { 224 225 if (empty($string)) { 226 throw new GTException("parameter string is required"); 227 } 228 229 $result = array(); 230 231 $i = 0; // number of output bytes produced 232 $j = 0; // number of input bytes consumed 233 234 $buff = 0; // buffer of input bits not yet sent to output 235 $size = 0; // number of bits in the buffer 236 237 while ($j < strlen($string)) { 238 239 $next = ord($string{$j}); 240 $j++; 241 242 if ($next < $this->min || $next > $this->max) { 243 continue; 244 } 245 246 $next = $this->values[$next - $this->min]; 247 248 if ($next == -1) { 249 continue; 250 } 251 252 $buff = ($buff << $this->bits) | $next; 253 $size += $this->bits; 254 255 while ($size >= 8) { 256 257 $result[$i] = (($buff >> ($size - 8)) & 0xff); 258 259 $size -= 8; 260 $i++; 261 262 } 263 } 264 265 return $result; 266 267 } 268 269 /** 270 * Updates $this->min and $this->max so that the range [$this->min .. $this->max] includes all values from $string 271 * 272 * @param string $string characters to process 273 * @return void 274 */ 275 private function addMinMax($string) { 276 277 for ($i = 0; $i < strlen($string); $i++) { 278 279 $c = ord($string{$i}); 280 281 if ($this->min == -1 || $this->min > $c) { 282 $this->min = $c; 283 } 284 285 if ($this->max == -1 || $this->max < $c) { 286 $this->max = $c; 287 } 288 } 289 } 290 291 /** 292 * Adds the characters from the given string to $this->values lookup table. 293 * 294 * @throws GTException 295 * @param string $string characters to process 296 * @return void 297 */ 298 private function addChars($string) { 299 300 for ($i = 0; $i < strlen($string); $i++) { 301 302 $c = ord($string{$i}) - $this->min; 303 304 if ($this->values[$c] != -1 && $this->values[$c] != $i) { 305 throw new GTException("Duplicate character in encoding alphabet"); 306 } 307 308 $this->values[$c] = $i; 309 310 } 311 } 312 313} 314 315?> 316