frame.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. 'use strict'
  2. const { runtimeFeatures } = require('../../util/runtime-features')
  3. const { maxUnsigned16Bit, opcodes } = require('./constants')
  4. const BUFFER_SIZE = 8 * 1024
  5. let buffer = null
  6. let bufIdx = BUFFER_SIZE
  7. const randomFillSync = runtimeFeatures.has('crypto')
  8. ? require('node:crypto').randomFillSync
  9. // not full compatibility, but minimum.
  10. : function randomFillSync (buffer, _offset, _size) {
  11. for (let i = 0; i < buffer.length; ++i) {
  12. buffer[i] = Math.random() * 255 | 0
  13. }
  14. return buffer
  15. }
  16. function generateMask () {
  17. if (bufIdx === BUFFER_SIZE) {
  18. bufIdx = 0
  19. randomFillSync((buffer ??= Buffer.allocUnsafeSlow(BUFFER_SIZE)), 0, BUFFER_SIZE)
  20. }
  21. return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
  22. }
  23. class WebsocketFrameSend {
  24. /**
  25. * @param {Buffer|undefined} data
  26. */
  27. constructor (data) {
  28. this.frameData = data
  29. }
  30. createFrame (opcode) {
  31. const frameData = this.frameData
  32. const maskKey = generateMask()
  33. const bodyLength = frameData?.byteLength ?? 0
  34. /** @type {number} */
  35. let payloadLength = bodyLength // 0-125
  36. let offset = 6
  37. if (bodyLength > maxUnsigned16Bit) {
  38. offset += 8 // payload length is next 8 bytes
  39. payloadLength = 127
  40. } else if (bodyLength > 125) {
  41. offset += 2 // payload length is next 2 bytes
  42. payloadLength = 126
  43. }
  44. const buffer = Buffer.allocUnsafe(bodyLength + offset)
  45. // Clear first 2 bytes, everything else is overwritten
  46. buffer[0] = buffer[1] = 0
  47. buffer[0] |= 0x80 // FIN
  48. buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
  49. /*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
  50. buffer[offset - 4] = maskKey[0]
  51. buffer[offset - 3] = maskKey[1]
  52. buffer[offset - 2] = maskKey[2]
  53. buffer[offset - 1] = maskKey[3]
  54. buffer[1] = payloadLength
  55. if (payloadLength === 126) {
  56. buffer.writeUInt16BE(bodyLength, 2)
  57. } else if (payloadLength === 127) {
  58. // Clear extended payload length
  59. buffer[2] = buffer[3] = 0
  60. buffer.writeUIntBE(bodyLength, 4, 6)
  61. }
  62. buffer[1] |= 0x80 // MASK
  63. // mask body
  64. for (let i = 0; i < bodyLength; ++i) {
  65. buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
  66. }
  67. return buffer
  68. }
  69. /**
  70. * @param {Uint8Array} buffer
  71. */
  72. static createFastTextFrame (buffer) {
  73. const maskKey = generateMask()
  74. const bodyLength = buffer.length
  75. // mask body
  76. for (let i = 0; i < bodyLength; ++i) {
  77. buffer[i] ^= maskKey[i & 3]
  78. }
  79. let payloadLength = bodyLength
  80. let offset = 6
  81. if (bodyLength > maxUnsigned16Bit) {
  82. offset += 8 // payload length is next 8 bytes
  83. payloadLength = 127
  84. } else if (bodyLength > 125) {
  85. offset += 2 // payload length is next 2 bytes
  86. payloadLength = 126
  87. }
  88. const head = Buffer.allocUnsafeSlow(offset)
  89. head[0] = 0x80 /* FIN */ | opcodes.TEXT /* opcode TEXT */
  90. head[1] = payloadLength | 0x80 /* MASK */
  91. head[offset - 4] = maskKey[0]
  92. head[offset - 3] = maskKey[1]
  93. head[offset - 2] = maskKey[2]
  94. head[offset - 1] = maskKey[3]
  95. if (payloadLength === 126) {
  96. head.writeUInt16BE(bodyLength, 2)
  97. } else if (payloadLength === 127) {
  98. head[2] = head[3] = 0
  99. head.writeUIntBE(bodyLength, 4, 6)
  100. }
  101. return [head, buffer]
  102. }
  103. }
  104. module.exports = {
  105. WebsocketFrameSend,
  106. generateMask // for benchmark
  107. }