utils/base32.js

  1. import charMapping from './charMapping.js';
  2. const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
  3. const DECODED_BLOCK_SIZE = 5;
  4. const ENCODED_BLOCK_SIZE = 8;
  5. // region encode
  6. const encodeBlock = (input, inputOffset, output, outputOffset) => {
  7. output[outputOffset + 0] = ALPHABET[input[inputOffset + 0] >> 3];
  8. output[outputOffset + 1] = ALPHABET[((input[inputOffset + 0] & 0x07) << 2) | (input[inputOffset + 1] >> 6)];
  9. output[outputOffset + 2] = ALPHABET[(input[inputOffset + 1] & 0x3E) >> 1];
  10. output[outputOffset + 3] = ALPHABET[((input[inputOffset + 1] & 0x01) << 4) | (input[inputOffset + 2] >> 4)];
  11. output[outputOffset + 4] = ALPHABET[((input[inputOffset + 2] & 0x0F) << 1) | (input[inputOffset + 3] >> 7)];
  12. output[outputOffset + 5] = ALPHABET[(input[inputOffset + 3] & 0x7F) >> 2];
  13. output[outputOffset + 6] = ALPHABET[((input[inputOffset + 3] & 0x03) << 3) | (input[inputOffset + 4] >> 5)];
  14. output[outputOffset + 7] = ALPHABET[input[inputOffset + 4] & 0x1F];
  15. };
  16. // endregion
  17. // region decode
  18. const Char_To_Decoded_Char_Map = (() => {
  19. const builder = charMapping.createBuilder();
  20. builder.addRange('A', 'Z', 0);
  21. builder.addRange('2', '7', 26);
  22. return builder.map;
  23. })();
  24. const decodeChar = c => {
  25. const decodedChar = Char_To_Decoded_Char_Map[c];
  26. if (undefined !== decodedChar)
  27. return decodedChar;
  28. throw Error(`illegal base32 character ${c}`);
  29. };
  30. const decodeBlock = (input, inputOffset, output, outputOffset) => {
  31. const bytes = new Uint8Array(ENCODED_BLOCK_SIZE);
  32. for (let i = 0; i < ENCODED_BLOCK_SIZE; ++i)
  33. bytes[i] = decodeChar(input[inputOffset + i]);
  34. output[outputOffset + 0] = (bytes[0] << 3) | (bytes[1] >> 2);
  35. output[outputOffset + 1] = ((bytes[1] & 0x03) << 6) | (bytes[2] << 1) | (bytes[3] >> 4);
  36. output[outputOffset + 2] = ((bytes[3] & 0x0F) << 4) | (bytes[4] >> 1);
  37. output[outputOffset + 3] = ((bytes[4] & 0x01) << 7) | (bytes[5] << 2) | (bytes[6] >> 3);
  38. output[outputOffset + 4] = ((bytes[6] & 0x07) << 5) | bytes[7];
  39. };
  40. // endregion
  41. /**
  42. * Base32 encodes a binary buffer.
  43. * @param {Uint8Array} data Binary data to encode.
  44. * @returns {string} Base32 encoded string corresponding to the input data.
  45. */
  46. const encode = data => {
  47. if (0 !== data.length % DECODED_BLOCK_SIZE)
  48. throw Error(`decoded size must be multiple of ${DECODED_BLOCK_SIZE}`);
  49. const output = new Array(data.length / DECODED_BLOCK_SIZE * ENCODED_BLOCK_SIZE);
  50. for (let i = 0; i < data.length / DECODED_BLOCK_SIZE; ++i)
  51. encodeBlock(data, i * DECODED_BLOCK_SIZE, output, i * ENCODED_BLOCK_SIZE);
  52. return output.join('');
  53. };
  54. /**
  55. * Base32 decodes a base32 encoded string.
  56. * @param {string} encoded Base32 encoded string to decode.
  57. * @returns {Uint8Array} Binary data corresponding to the input string.
  58. */
  59. const decode = encoded => {
  60. if (0 !== encoded.length % ENCODED_BLOCK_SIZE)
  61. throw Error(`encoded size must be multiple of ${ENCODED_BLOCK_SIZE}`);
  62. const output = new Uint8Array(encoded.length / ENCODED_BLOCK_SIZE * DECODED_BLOCK_SIZE);
  63. for (let i = 0; i < encoded.length / ENCODED_BLOCK_SIZE; ++i)
  64. decodeBlock(encoded, i * ENCODED_BLOCK_SIZE, output, i * DECODED_BLOCK_SIZE);
  65. return output;
  66. };
  67. export default { encode, decode };