[Service] Integrate DeviceHome and SignalingServer
[platform/framework/web/wrtjs.git] / device_home / node_modules / ws / lib / sender.js
1 'use strict';
2
3 const crypto = require('crypto');
4
5 const PerMessageDeflate = require('./permessage-deflate');
6 const bufferUtil = require('./buffer-util');
7 const validation = require('./validation');
8 const constants = require('./constants');
9
10 /**
11  * HyBi Sender implementation.
12  */
13 class Sender {
14   /**
15    * Creates a Sender instance.
16    *
17    * @param {net.Socket} socket The connection socket
18    * @param {Object} extensions An object containing the negotiated extensions
19    */
20   constructor (socket, extensions) {
21     this._extensions = extensions || {};
22     this._socket = socket;
23
24     this._firstFragment = true;
25     this._compress = false;
26
27     this._bufferedBytes = 0;
28     this._deflating = false;
29     this._queue = [];
30   }
31
32   /**
33    * Frames a piece of data according to the HyBi WebSocket protocol.
34    *
35    * @param {Buffer} data The data to frame
36    * @param {Object} options Options object
37    * @param {Number} options.opcode The opcode
38    * @param {Boolean} options.readOnly Specifies whether `data` can be modified
39    * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
40    * @param {Boolean} options.mask Specifies whether or not to mask `data`
41    * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
42    * @return {Buffer[]} The framed data as a list of `Buffer` instances
43    * @public
44    */
45   static frame (data, options) {
46     const merge = data.length < 1024 || (options.mask && options.readOnly);
47     var offset = options.mask ? 6 : 2;
48     var payloadLength = data.length;
49
50     if (data.length >= 65536) {
51       offset += 8;
52       payloadLength = 127;
53     } else if (data.length > 125) {
54       offset += 2;
55       payloadLength = 126;
56     }
57
58     const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
59
60     target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
61     if (options.rsv1) target[0] |= 0x40;
62
63     if (payloadLength === 126) {
64       target.writeUInt16BE(data.length, 2);
65     } else if (payloadLength === 127) {
66       target.writeUInt32BE(0, 2);
67       target.writeUInt32BE(data.length, 6);
68     }
69
70     if (!options.mask) {
71       target[1] = payloadLength;
72       if (merge) {
73         data.copy(target, offset);
74         return [target];
75       }
76
77       return [target, data];
78     }
79
80     const mask = crypto.randomBytes(4);
81
82     target[1] = payloadLength | 0x80;
83     target[offset - 4] = mask[0];
84     target[offset - 3] = mask[1];
85     target[offset - 2] = mask[2];
86     target[offset - 1] = mask[3];
87
88     if (merge) {
89       bufferUtil.mask(data, mask, target, offset, data.length);
90       return [target];
91     }
92
93     bufferUtil.mask(data, mask, data, 0, data.length);
94     return [target, data];
95   }
96
97   /**
98    * Sends a close message to the other peer.
99    *
100    * @param {(Number|undefined)} code The status code component of the body
101    * @param {String} data The message component of the body
102    * @param {Boolean} mask Specifies whether or not to mask the message
103    * @param {Function} cb Callback
104    * @public
105    */
106   close (code, data, mask, cb) {
107     var buf;
108
109     if (code === undefined) {
110       buf = constants.EMPTY_BUFFER;
111     } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) {
112       throw new TypeError('First argument must be a valid error code number');
113     } else if (data === undefined || data === '') {
114       buf = Buffer.allocUnsafe(2);
115       buf.writeUInt16BE(code, 0);
116     } else {
117       buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
118       buf.writeUInt16BE(code, 0);
119       buf.write(data, 2);
120     }
121
122     if (this._deflating) {
123       this.enqueue([this.doClose, buf, mask, cb]);
124     } else {
125       this.doClose(buf, mask, cb);
126     }
127   }
128
129   /**
130    * Frames and sends a close message.
131    *
132    * @param {Buffer} data The message to send
133    * @param {Boolean} mask Specifies whether or not to mask `data`
134    * @param {Function} cb Callback
135    * @private
136    */
137   doClose (data, mask, cb) {
138     this.sendFrame(Sender.frame(data, {
139       fin: true,
140       rsv1: false,
141       opcode: 0x08,
142       mask,
143       readOnly: false
144     }), cb);
145   }
146
147   /**
148    * Sends a ping message to the other peer.
149    *
150    * @param {*} data The message to send
151    * @param {Boolean} mask Specifies whether or not to mask `data`
152    * @param {Function} cb Callback
153    * @public
154    */
155   ping (data, mask, cb) {
156     var readOnly = true;
157
158     if (!Buffer.isBuffer(data)) {
159       if (data instanceof ArrayBuffer) {
160         data = Buffer.from(data);
161       } else if (ArrayBuffer.isView(data)) {
162         data = viewToBuffer(data);
163       } else {
164         data = Buffer.from(data);
165         readOnly = false;
166       }
167     }
168
169     if (this._deflating) {
170       this.enqueue([this.doPing, data, mask, readOnly, cb]);
171     } else {
172       this.doPing(data, mask, readOnly, cb);
173     }
174   }
175
176   /**
177    * Frames and sends a ping message.
178    *
179    * @param {*} data The message to send
180    * @param {Boolean} mask Specifies whether or not to mask `data`
181    * @param {Boolean} readOnly Specifies whether `data` can be modified
182    * @param {Function} cb Callback
183    * @private
184    */
185   doPing (data, mask, readOnly, cb) {
186     this.sendFrame(Sender.frame(data, {
187       fin: true,
188       rsv1: false,
189       opcode: 0x09,
190       mask,
191       readOnly
192     }), cb);
193   }
194
195   /**
196    * Sends a pong message to the other peer.
197    *
198    * @param {*} data The message to send
199    * @param {Boolean} mask Specifies whether or not to mask `data`
200    * @param {Function} cb Callback
201    * @public
202    */
203   pong (data, mask, cb) {
204     var readOnly = true;
205
206     if (!Buffer.isBuffer(data)) {
207       if (data instanceof ArrayBuffer) {
208         data = Buffer.from(data);
209       } else if (ArrayBuffer.isView(data)) {
210         data = viewToBuffer(data);
211       } else {
212         data = Buffer.from(data);
213         readOnly = false;
214       }
215     }
216
217     if (this._deflating) {
218       this.enqueue([this.doPong, data, mask, readOnly, cb]);
219     } else {
220       this.doPong(data, mask, readOnly, cb);
221     }
222   }
223
224   /**
225    * Frames and sends a pong message.
226    *
227    * @param {*} data The message to send
228    * @param {Boolean} mask Specifies whether or not to mask `data`
229    * @param {Boolean} readOnly Specifies whether `data` can be modified
230    * @param {Function} cb Callback
231    * @private
232    */
233   doPong (data, mask, readOnly, cb) {
234     this.sendFrame(Sender.frame(data, {
235       fin: true,
236       rsv1: false,
237       opcode: 0x0a,
238       mask,
239       readOnly
240     }), cb);
241   }
242
243   /**
244    * Sends a data message to the other peer.
245    *
246    * @param {*} data The message to send
247    * @param {Object} options Options object
248    * @param {Boolean} options.compress Specifies whether or not to compress `data`
249    * @param {Boolean} options.binary Specifies whether `data` is binary or text
250    * @param {Boolean} options.fin Specifies whether the fragment is the last one
251    * @param {Boolean} options.mask Specifies whether or not to mask `data`
252    * @param {Function} cb Callback
253    * @public
254    */
255   send (data, options, cb) {
256     var opcode = options.binary ? 2 : 1;
257     var rsv1 = options.compress;
258     var readOnly = true;
259
260     if (!Buffer.isBuffer(data)) {
261       if (data instanceof ArrayBuffer) {
262         data = Buffer.from(data);
263       } else if (ArrayBuffer.isView(data)) {
264         data = viewToBuffer(data);
265       } else {
266         data = Buffer.from(data);
267         readOnly = false;
268       }
269     }
270
271     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
272
273     if (this._firstFragment) {
274       this._firstFragment = false;
275       if (rsv1 && perMessageDeflate) {
276         rsv1 = data.length >= perMessageDeflate._threshold;
277       }
278       this._compress = rsv1;
279     } else {
280       rsv1 = false;
281       opcode = 0;
282     }
283
284     if (options.fin) this._firstFragment = true;
285
286     if (perMessageDeflate) {
287       const opts = {
288         fin: options.fin,
289         rsv1,
290         opcode,
291         mask: options.mask,
292         readOnly
293       };
294
295       if (this._deflating) {
296         this.enqueue([this.dispatch, data, this._compress, opts, cb]);
297       } else {
298         this.dispatch(data, this._compress, opts, cb);
299       }
300     } else {
301       this.sendFrame(Sender.frame(data, {
302         fin: options.fin,
303         rsv1: false,
304         opcode,
305         mask: options.mask,
306         readOnly
307       }), cb);
308     }
309   }
310
311   /**
312    * Dispatches a data message.
313    *
314    * @param {Buffer} data The message to send
315    * @param {Boolean} compress Specifies whether or not to compress `data`
316    * @param {Object} options Options object
317    * @param {Number} options.opcode The opcode
318    * @param {Boolean} options.readOnly Specifies whether `data` can be modified
319    * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
320    * @param {Boolean} options.mask Specifies whether or not to mask `data`
321    * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
322    * @param {Function} cb Callback
323    * @private
324    */
325   dispatch (data, compress, options, cb) {
326     if (!compress) {
327       this.sendFrame(Sender.frame(data, options), cb);
328       return;
329     }
330
331     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
332
333     this._deflating = true;
334     perMessageDeflate.compress(data, options.fin, (_, buf) => {
335       options.readOnly = false;
336       this.sendFrame(Sender.frame(buf, options), cb);
337       this._deflating = false;
338       this.dequeue();
339     });
340   }
341
342   /**
343    * Executes queued send operations.
344    *
345    * @private
346    */
347   dequeue () {
348     while (!this._deflating && this._queue.length) {
349       const params = this._queue.shift();
350
351       this._bufferedBytes -= params[1].length;
352       params[0].apply(this, params.slice(1));
353     }
354   }
355
356   /**
357    * Enqueues a send operation.
358    *
359    * @param {Array} params Send operation parameters.
360    * @private
361    */
362   enqueue (params) {
363     this._bufferedBytes += params[1].length;
364     this._queue.push(params);
365   }
366
367   /**
368    * Sends a frame.
369    *
370    * @param {Buffer[]} list The frame to send
371    * @param {Function} cb Callback
372    * @private
373    */
374   sendFrame (list, cb) {
375     if (list.length === 2) {
376       this._socket.write(list[0]);
377       this._socket.write(list[1], cb);
378     } else {
379       this._socket.write(list[0], cb);
380     }
381   }
382 }
383
384 module.exports = Sender;
385
386 /**
387  * Converts an `ArrayBuffer` view into a buffer.
388  *
389  * @param {(DataView|TypedArray)} view The view to convert
390  * @return {Buffer} Converted view
391  * @private
392  */
393 function viewToBuffer (view) {
394   const buf = Buffer.from(view.buffer);
395
396   if (view.byteLength !== view.buffer.byteLength) {
397     return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
398   }
399
400   return buf;
401 }