PageRenderTime 28ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/FixedByteNotation.php

http://github.com/chregu/GoogleAuthenticator.php
PHP | 276 lines | 158 code | 45 blank | 73 comment | 35 complexity | 3dbeb03040be44d09ac2e9b1c89a0c77 MD5 | raw file
  1. <?php
  2. /**
  3. * FixedBitNotation
  4. *
  5. * @author Andre DeMarre
  6. * @package FixedBitNotation
  7. */
  8. /**
  9. * The FixedBitNotation class is for binary to text conversion. It
  10. * can handle many encoding schemes, formally defined or not, that
  11. * use a fixed number of bits to encode each character.
  12. *
  13. * @package FixedBitNotation
  14. */
  15. class FixedBitNotation
  16. {
  17. protected $_chars;
  18. protected $_bitsPerCharacter;
  19. protected $_radix;
  20. protected $_rightPadFinalBits;
  21. protected $_padFinalGroup;
  22. protected $_padCharacter;
  23. protected $_charmap;
  24. /**
  25. * Constructor
  26. *
  27. * @param integer $bitsPerCharacter Bits to use for each encoded
  28. * character
  29. * @param string $chars Base character alphabet
  30. * @param boolean $rightPadFinalBits How to encode last character
  31. * @param boolean $padFinalGroup Add padding to end of encoded
  32. * output
  33. * @param string $padCharacter Character to use for padding
  34. */
  35. public function __construct(
  36. $bitsPerCharacter, $chars = NULL, $rightPadFinalBits = FALSE,
  37. $padFinalGroup = FALSE, $padCharacter = '=')
  38. {
  39. // Ensure validity of $chars
  40. if (!is_string($chars) || ($charLength = strlen($chars)) < 2) {
  41. $chars =
  42. '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
  43. $charLength = 64;
  44. }
  45. // Ensure validity of $bitsPerCharacter
  46. if ($bitsPerCharacter < 1) {
  47. // $bitsPerCharacter must be at least 1
  48. $bitsPerCharacter = 1;
  49. $radix = 2;
  50. } elseif ($charLength < 1 << $bitsPerCharacter) {
  51. // Character length of $chars is too small for $bitsPerCharacter
  52. // Set $bitsPerCharacter to greatest acceptable value
  53. $bitsPerCharacter = 1;
  54. $radix = 2;
  55. while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) {
  56. $bitsPerCharacter++;
  57. }
  58. $radix >>= 1;
  59. } elseif ($bitsPerCharacter > 8) {
  60. // $bitsPerCharacter must not be greater than 8
  61. $bitsPerCharacter = 8;
  62. $radix = 256;
  63. } else {
  64. $radix = 1 << $bitsPerCharacter;
  65. }
  66. $this->_chars = $chars;
  67. $this->_bitsPerCharacter = $bitsPerCharacter;
  68. $this->_radix = $radix;
  69. $this->_rightPadFinalBits = $rightPadFinalBits;
  70. $this->_padFinalGroup = $padFinalGroup;
  71. $this->_padCharacter = $padCharacter[0];
  72. }
  73. /**
  74. * Encode a string
  75. *
  76. * @param string $rawString Binary data to encode
  77. * @return string
  78. */
  79. public function encode($rawString)
  80. {
  81. // Unpack string into an array of bytes
  82. $bytes = unpack('C*', $rawString);
  83. $byteCount = count($bytes);
  84. $encodedString = '';
  85. $byte = array_shift($bytes);
  86. $bitsRead = 0;
  87. $chars = $this->_chars;
  88. $bitsPerCharacter = $this->_bitsPerCharacter;
  89. $rightPadFinalBits = $this->_rightPadFinalBits;
  90. $padFinalGroup = $this->_padFinalGroup;
  91. $padCharacter = $this->_padCharacter;
  92. // Generate encoded output;
  93. // each loop produces one encoded character
  94. for ($c = 0; $c < $byteCount * 8 / $bitsPerCharacter; $c++) {
  95. // Get the bits needed for this encoded character
  96. if ($bitsRead + $bitsPerCharacter > 8) {
  97. // Not enough bits remain in this byte for the current
  98. // character
  99. // Save the remaining bits before getting the next byte
  100. $oldBitCount = 8 - $bitsRead;
  101. $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
  102. $newBitCount = $bitsPerCharacter - $oldBitCount;
  103. if (!$bytes) {
  104. // Last bits; match final character and exit loop
  105. if ($rightPadFinalBits) $oldBits <<= $newBitCount;
  106. $encodedString .= $chars[$oldBits];
  107. if ($padFinalGroup) {
  108. // Array of the lowest common multiples of
  109. // $bitsPerCharacter and 8, divided by 8
  110. $lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1,
  111. 5 => 5, 6 => 3, 7 => 7, 8 => 1);
  112. $bytesPerGroup = $lcmMap[$bitsPerCharacter];
  113. $pads = $bytesPerGroup * 8 / $bitsPerCharacter
  114. - ceil((strlen($rawString) % $bytesPerGroup)
  115. * 8 / $bitsPerCharacter);
  116. $encodedString .= str_repeat($padCharacter[0], $pads);
  117. }
  118. break;
  119. }
  120. // Get next byte
  121. $byte = array_shift($bytes);
  122. $bitsRead = 0;
  123. } else {
  124. $oldBitCount = 0;
  125. $newBitCount = $bitsPerCharacter;
  126. }
  127. // Read only the needed bits from this byte
  128. $bits = $byte >> 8 - ($bitsRead + ($newBitCount));
  129. $bits ^= $bits >> $newBitCount << $newBitCount;
  130. $bitsRead += $newBitCount;
  131. if ($oldBitCount) {
  132. // Bits come from seperate bytes, add $oldBits to $bits
  133. $bits = ($oldBits << $newBitCount) | $bits;
  134. }
  135. $encodedString .= $chars[$bits];
  136. }
  137. return $encodedString;
  138. }
  139. /**
  140. * Decode a string
  141. *
  142. * @param string $encodedString Data to decode
  143. * @param boolean $caseSensitive
  144. * @param boolean $strict Returns NULL if $encodedString contains
  145. * an undecodable character
  146. * @return string|NULL
  147. */
  148. public function decode($encodedString, $caseSensitive = TRUE,
  149. $strict = FALSE)
  150. {
  151. if (!$encodedString || !is_string($encodedString)) {
  152. // Empty string, nothing to decode
  153. return '';
  154. }
  155. $chars = $this->_chars;
  156. $bitsPerCharacter = $this->_bitsPerCharacter;
  157. $radix = $this->_radix;
  158. $rightPadFinalBits = $this->_rightPadFinalBits;
  159. $padFinalGroup = $this->_padFinalGroup;
  160. $padCharacter = $this->_padCharacter;
  161. // Get index of encoded characters
  162. if ($this->_charmap) {
  163. $charmap = $this->_charmap;
  164. } else {
  165. $charmap = array();
  166. for ($i = 0; $i < $radix; $i++) {
  167. $charmap[$chars[$i]] = $i;
  168. }
  169. $this->_charmap = $charmap;
  170. }
  171. // The last encoded character is $encodedString[$lastNotatedIndex]
  172. $lastNotatedIndex = strlen($encodedString) - 1;
  173. // Remove trailing padding characters
  174. while ($encodedString[$lastNotatedIndex] == $padCharacter[0]) {
  175. $encodedString = substr($encodedString, 0, $lastNotatedIndex);
  176. $lastNotatedIndex--;
  177. }
  178. $rawString = '';
  179. $byte = 0;
  180. $bitsWritten = 0;
  181. // Convert each encoded character to a series of unencoded bits
  182. for ($c = 0; $c <= $lastNotatedIndex; $c++) {
  183. if (!isset($charmap[$encodedString[$c]]) && !$caseSensitive) {
  184. // Encoded character was not found; try other case
  185. if (isset($charmap[$cUpper
  186. = strtoupper($encodedString[$c])])) {
  187. $charmap[$encodedString[$c]] = $charmap[$cUpper];
  188. } elseif (isset($charmap[$cLower
  189. = strtolower($encodedString[$c])])) {
  190. $charmap[$encodedString[$c]] = $charmap[$cLower];
  191. }
  192. }
  193. if (isset($charmap[$encodedString[$c]])) {
  194. $bitsNeeded = 8 - $bitsWritten;
  195. $unusedBitCount = $bitsPerCharacter - $bitsNeeded;
  196. // Get the new bits ready
  197. if ($bitsNeeded > $bitsPerCharacter) {
  198. // New bits aren't enough to complete a byte; shift them
  199. // left into position
  200. $newBits = $charmap[$encodedString[$c]] << $bitsNeeded
  201. - $bitsPerCharacter;
  202. $bitsWritten += $bitsPerCharacter;
  203. } elseif ($c != $lastNotatedIndex || $rightPadFinalBits) {
  204. // Zero or more too many bits to complete a byte;
  205. // shift right
  206. $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
  207. $bitsWritten = 8; //$bitsWritten += $bitsNeeded;
  208. } else {
  209. // Final bits don't need to be shifted
  210. $newBits = $charmap[$encodedString[$c]];
  211. $bitsWritten = 8;
  212. }
  213. $byte |= $newBits;
  214. if ($bitsWritten == 8 || $c == $lastNotatedIndex) {
  215. // Byte is ready to be written
  216. $rawString .= pack('C', $byte);
  217. if ($c != $lastNotatedIndex) {
  218. // Start the next byte
  219. $bitsWritten = $unusedBitCount;
  220. $byte = ($charmap[$encodedString[$c]]
  221. ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten;
  222. }
  223. }
  224. } elseif ($strict) {
  225. // Unable to decode character; abort
  226. return NULL;
  227. }
  228. }
  229. return $rawString;
  230. }
  231. }