hex.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { E_STRING } from './_utils.js'
  2. import { nativeDecoder, nativeEncoder, decode2string } from './platform.js'
  3. import { encodeAscii, decodeAscii } from './latin1.js'
  4. let hexArray // array of 256 bytes converted to two-char hex strings
  5. let hexCodes // hexArray converted to u16 code pairs
  6. let dehexArray
  7. const _00 = 0x30_30 // '00' string in hex, the only allowed char pair to generate 0 byte
  8. const _ff = 0x66_66 // 'ff' string in hex, max allowed char pair (larger than 'FF' string)
  9. const allowed = '0123456789ABCDEFabcdef'
  10. export const E_HEX = 'Input is not a hex string'
  11. // Expects a checked Uint8Array
  12. export function toHex(arr) {
  13. if (!hexArray) hexArray = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
  14. const length = arr.length // this helps Hermes
  15. // Only old browsers use this, barebone engines don't have TextDecoder
  16. // But Hermes can use this when it (hopefully) implements TextDecoder
  17. if (nativeDecoder) {
  18. if (!hexCodes) {
  19. hexCodes = new Uint16Array(256)
  20. const u8 = new Uint8Array(hexCodes.buffer, hexCodes.byteOffset, hexCodes.byteLength)
  21. for (let i = 0; i < 256; i++) {
  22. const pair = hexArray[i]
  23. u8[2 * i] = pair.charCodeAt(0)
  24. u8[2 * i + 1] = pair.charCodeAt(1)
  25. }
  26. }
  27. const oa = new Uint16Array(length)
  28. let i = 0
  29. for (const last3 = arr.length - 3; ; i += 4) {
  30. if (i >= last3) break // loop is fast enough for moving this here to be useful on JSC
  31. const x0 = arr[i]
  32. const x1 = arr[i + 1]
  33. const x2 = arr[i + 2]
  34. const x3 = arr[i + 3]
  35. oa[i] = hexCodes[x0]
  36. oa[i + 1] = hexCodes[x1]
  37. oa[i + 2] = hexCodes[x2]
  38. oa[i + 3] = hexCodes[x3]
  39. }
  40. for (; i < length; i++) oa[i] = hexCodes[arr[i]]
  41. return decodeAscii(oa)
  42. }
  43. return decode2string(arr, 0, length, hexArray)
  44. }
  45. export function fromHex(str) {
  46. if (typeof str !== 'string') throw new TypeError(E_STRING)
  47. if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
  48. const length = str.length / 2 // this helps Hermes in loops
  49. const arr = new Uint8Array(length)
  50. // Native encoder path is beneficial even for small arrays in Hermes
  51. if (nativeEncoder) {
  52. if (!dehexArray) {
  53. dehexArray = new Uint8Array(_ff + 1) // 26 KiB cache, >2x perf improvement on Hermes
  54. const u8 = new Uint8Array(2)
  55. const u16 = new Uint16Array(u8.buffer, u8.byteOffset, 1) // for endianess-agnostic transform
  56. const map = [...allowed].map((c) => [c.charCodeAt(0), parseInt(c, 16)])
  57. for (const [ch, vh] of map) {
  58. u8[0] = ch // first we read high hex char
  59. for (const [cl, vl] of map) {
  60. u8[1] = cl // then we read low hex char
  61. dehexArray[u16[0]] = (vh << 4) | vl
  62. }
  63. }
  64. }
  65. const codes = encodeAscii(str, E_HEX)
  66. const codes16 = new Uint16Array(codes.buffer, codes.byteOffset, codes.byteLength / 2)
  67. let i = 0
  68. for (const last3 = length - 3; i < last3; i += 4) {
  69. const ai = codes16[i]
  70. const bi = codes16[i + 1]
  71. const ci = codes16[i + 2]
  72. const di = codes16[i + 3]
  73. const a = dehexArray[ai]
  74. const b = dehexArray[bi]
  75. const c = dehexArray[ci]
  76. const d = dehexArray[di]
  77. if ((!a && ai !== _00) || (!b && bi !== _00) || (!c && ci !== _00) || (!d && di !== _00)) {
  78. throw new SyntaxError(E_HEX)
  79. }
  80. arr[i] = a
  81. arr[i + 1] = b
  82. arr[i + 2] = c
  83. arr[i + 3] = d
  84. }
  85. while (i < length) {
  86. const ai = codes16[i]
  87. const a = dehexArray[ai]
  88. if (!a && ai !== _00) throw new SyntaxError(E_HEX)
  89. arr[i++] = a
  90. }
  91. } else {
  92. if (!dehexArray) {
  93. // no regex input validation here, so we map all other bytes to -1 and recheck sign
  94. // non-ASCII chars throw already though, so we should process only 0-127
  95. dehexArray = new Int8Array(128).fill(-1)
  96. for (let i = 0; i < 16; i++) {
  97. const s = i.toString(16)
  98. dehexArray[s.charCodeAt(0)] = dehexArray[s.toUpperCase().charCodeAt(0)] = i
  99. }
  100. }
  101. let j = 0
  102. for (let i = 0; i < length; i++) {
  103. const a = str.charCodeAt(j++)
  104. const b = str.charCodeAt(j++)
  105. const res = (dehexArray[a] << 4) | dehexArray[b]
  106. if (res < 0 || (0x7f | a | b) !== 0x7f) throw new SyntaxError(E_HEX) // 0-127
  107. arr[i] = res
  108. }
  109. }
  110. return arr
  111. }