├── .eslintrc ├── .gitignore ├── README.md ├── lib ├── connection.js ├── deserializer.js ├── flow_controller.js ├── framer.js ├── hpack.js ├── http.js ├── index.js ├── logger.js ├── protocol.js ├── serializer.js └── stream.js ├── package.json └── test ├── connection_test.js ├── flow_controller_test.js ├── framer_test.js ├── hpack_test.js └── stream_test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "space-infix-ops": 0, 7 | "no-underscore-dangle": 0, 8 | "quotes": [1, "single"], 9 | "strict": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sasazka 2 | 3 | Yet another HTTP/2 implementation for Node.js. 4 | This module supports [draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14). 5 | 6 | ## Usage 7 | 8 | ```javascript 9 | var fs = require('fs'); 10 | var http2 = require('sasazka'); 11 | 12 | var options = { 13 | key: fs.readFileSync('ssl/key.pem'), 14 | cert: fs.readFileSync('ssl/cert.pem'), 15 | maxConcurrentStreams: 100 16 | }; 17 | 18 | var server = http2.createServer(options, function (req, res) { 19 | res.push('/pushed.txt', function (pushReq, pushRes) { 20 | pushRes.end('Server pushed!'); 21 | }); 22 | 23 | res.writeHead(200); 24 | res.end('Hello HTTP/2!'); 25 | }); 26 | 27 | server.listen(443); 28 | ``` 29 | 30 | ## Notes 31 | 32 | * ALPN is not yet supported in Node.js. Sasazka uses NPN for negotiation. 33 | * This is an experimental module. so this module is not registered on the npm registry yet. 34 | * To enable debug logger, you must set environment valiable `DEBUG=serializer,deserializer`. 35 | 36 | ## TODO 37 | 38 | * API documents 39 | * Client APIs 40 | * Better HTTP layer APIs 41 | * Support HTTP Upgrade 42 | * Support stream priority 43 | * Better flow control 44 | * Upload to the npm registry 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2014 Moto Ishizawa 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining 53 | a copy of this software and associated documentation files (the 54 | "Software"), to deal in the Software without restriction, including 55 | without limitation the rights to use, copy, modify, merge, publish, 56 | distribute, sublicense, and/or sell copies of the Software, and to 57 | permit persons to whom the Software is furnished to do so, subject to 58 | the following conditions: 59 | 60 | The above copyright notice and this permission notice shall be 61 | included in all copies or substantial portions of the Software. 62 | 63 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 64 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 65 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 66 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 67 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 68 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 69 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 70 | 71 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var FlowController = require('./flow_controller'), 4 | Stream = require('./stream'), 5 | Serializer = require('./serializer'), 6 | Deserializer = require('./deserializer'), 7 | hpack = require('./hpack'), 8 | framer = require('./framer'), 9 | protocol = require('./protocol'); 10 | 11 | var SETTINGS_ACK_TIMEOUT = 30 * 1000; // 30s 12 | 13 | 14 | function ConnectionSettings() { 15 | this.headerTableSize = protocol.DEFAULT_HEADER_TABLE_SIZE; 16 | this.enablePush = protocol.DEFAULT_ENABLE_PUSH; 17 | this.maxConcurrentStreams = protocol.DEFAULT_MAX_CONCURRENT_STREAMS; 18 | this.initialWindowSize = protocol.DEFAULT_INITIAL_WINDOW_SIZE; 19 | } 20 | 21 | 22 | function Connection(socket, settings, server) { 23 | FlowController.call(this); 24 | 25 | this.closed = false; 26 | this._server = (server === true); 27 | 28 | this._streams = []; 29 | this._lastStreamId = 0; 30 | this._nextCleanupId = 1; 31 | 32 | this._localSettings = new ConnectionSettings(); 33 | this._remoteSettings = new ConnectionSettings(); 34 | this._pendingSettings = []; 35 | this._settingsTimers = []; 36 | 37 | this._localStreams = 0; 38 | this._remoteStreams = 0; 39 | 40 | this.socket = socket; 41 | this.socket.once('end', this._socketEndHandler()); 42 | this.socket.once('close', this._socketCloseHandler()); 43 | this.socket.once('error', this._socketErrorHandler()); 44 | this.socket.once('timeout', this._socketTimeoutHandler()); 45 | 46 | this._compressor = hpack.createContext(); 47 | this._decompressor = hpack.createContext(); 48 | 49 | this._serializer = new Serializer(); 50 | this._deserializer = new Deserializer(); 51 | 52 | if (this._server) { 53 | this._setupServerConnection(settings); 54 | } else { 55 | this._setupClientConnection(settings); 56 | } 57 | } 58 | 59 | util.inherits(Connection, FlowController); 60 | 61 | Connection.prototype._setupClientConnection = function(settings) { 62 | var self = this; 63 | 64 | this._streamId = 1; 65 | this.socket.on('connect', function() { 66 | self._setupPipeline(); 67 | 68 | self.socket.write(protocol.CONNECTION_PREFACE); 69 | self.sendSettingsFrame(settings); 70 | 71 | self.emit('connect'); 72 | }); 73 | }; 74 | 75 | Connection.prototype._setupServerConnection = function(settings) { 76 | this._streamId = 2; 77 | this.socket.once('readable', this._connectionPrefaceHandler(settings)); 78 | }; 79 | 80 | Connection.prototype._setupPipeline = function() { 81 | this._serializer.pipe(this.socket).pipe(this._deserializer); 82 | this._deserializer.on('data', this._frameHandler()); 83 | }; 84 | 85 | Connection.prototype.createStream = function(id, promised) { 86 | var self = this; 87 | 88 | if (typeof id === 'boolean') { 89 | promised = id; 90 | id = undefined; 91 | } 92 | promised = (promised === true) ? true : false; 93 | 94 | if (promised && !this._remoteSettings.enablePush) { 95 | return null; 96 | } 97 | 98 | if (!id) { 99 | id = this._streamId; 100 | this._streamId += 2; 101 | } 102 | 103 | if (this._streams[id]) { 104 | var errorCode = protocol.CODE_PROTOCOL_ERROR; 105 | this.sendGoawayFrame(errorCode); 106 | this._error(errorCode); 107 | return null; 108 | } 109 | 110 | var isServerStream = (id % 2 === 0); 111 | var stream = new Stream({ 112 | id: id, 113 | promised: promised, 114 | initialWindowSize: this._remoteSettings.initialWindowSize, 115 | compressor: this._compressor, 116 | decompressor: this._decompressor 117 | }); 118 | 119 | stream.on('send', function (frame) { 120 | self.push(frame); 121 | }); 122 | 123 | stream.on('active', function () { 124 | if (self._server === isServerStream) { 125 | self._localStreams++; 126 | } else { 127 | self._remoteStreams++; 128 | } 129 | }); 130 | 131 | stream.on('close', function () { 132 | if (self._server === isServerStream) { 133 | self._localStreams--; 134 | } else { 135 | self._remoteStreams--; 136 | } 137 | }); 138 | 139 | stream.on('error', function (err, connectionError) { 140 | if (connectionError) { 141 | self.sendGoawayFrame(err); 142 | self._error(err); 143 | } 144 | }); 145 | 146 | this._streams[stream.id] = stream; 147 | setImmediate(function(){ 148 | self.emit('stream', stream); 149 | }); 150 | 151 | var refuse = false; 152 | if (isServerStream) { 153 | if (self._localStreams > self._remoteSettings.maxConcurrentStreams) { 154 | refuse = true; 155 | } 156 | } else { 157 | if (self._remoteStreams > self._localSettings.maxConcurrentStreams) { 158 | refuse = true; 159 | } 160 | } 161 | 162 | if (refuse) { 163 | stream.sendRstStreamFrame(protocol.REFUSED_STREAM); 164 | } 165 | 166 | this._cleanupStream(); 167 | 168 | return stream; 169 | }; 170 | 171 | Connection.prototype._cleanupStream = function () { 172 | var maxStreams = 50; 173 | 174 | if (this._streams.length > maxStreams && (this._streams.length % 20 === 0)) { 175 | var idx = this._nextCleanupId; 176 | while (this._streams[idx].closed) { 177 | this._streams[idx] = false; 178 | idx++; 179 | console.log('Cleanup:', idx); 180 | } 181 | 182 | this._nextCleanupId = idx; 183 | } 184 | }; 185 | 186 | Connection.prototype.setTimeout = function(timeout, callback) { 187 | this.socket.setTimeout(timeout); 188 | 189 | if (callback) { 190 | this.once('timeout', callback); 191 | } 192 | }; 193 | 194 | Connection.prototype.setHeaderTableSize = function(headerTableSize) { 195 | this.sendSettingsFrame({ headerTableSize: headerTableSize }); 196 | }; 197 | 198 | Connection.prototype.setEnablePush = function(enablePush) { 199 | this.sendSettingsFrame({ enablePush: enablePush }); 200 | }; 201 | 202 | Connection.prototype.setMaxConcurrentStreams = function(maxConcurrentStreams) { 203 | this.sendSettingsFrame({ maxConcurrentStreams: maxConcurrentStreams }); 204 | }; 205 | 206 | Connection.prototype.setInitialWindowSize = function(initialWindowSize) { 207 | this.sendSettingsFrame({ initialWindowSize: initialWindowSize }); 208 | }; 209 | 210 | Connection.prototype.ping = function() { 211 | this.sendPingFrame(); 212 | }; 213 | 214 | Connection.prototype.destroy = function() { 215 | this.sendGoawayFrame(); 216 | this.socket.destroy(); 217 | this._close(); 218 | }; 219 | 220 | Connection.prototype.sendSettingsFrame = function(settings, ack) { 221 | var self = this; 222 | 223 | if (typeof settings === 'boolean') { 224 | ack = settings; 225 | settings = {}; 226 | } else if(settings === undefined) { 227 | settings = {}; 228 | } 229 | 230 | var frame = framer.createSettingsFrame(); 231 | 232 | if (ack === true) { 233 | frame.ack = true; 234 | } else { 235 | for (var param in settings) { 236 | var method = 'set' + param[0].toUpperCase() + param.slice(1); 237 | if (typeof frame[method] === 'function') { 238 | frame[method](settings[param]); 239 | } 240 | } 241 | this._pendingSettings.push(settings); 242 | 243 | var timer = setTimeout(function(){ 244 | var errorCode = protocol.CODE_SETTINGS_TIMEOUT; 245 | self.sendGoawayFrame(errorCode); 246 | self._error(errorCode); 247 | }, SETTINGS_ACK_TIMEOUT); 248 | this._settingsTimers.push(timer); 249 | } 250 | 251 | this.push(frame); 252 | }; 253 | 254 | Connection.prototype.sendGoawayFrame = function(errorCode) { 255 | if (!errorCode) { 256 | errorCode = protocol.CODE_NO_ERROR; 257 | } 258 | 259 | var frame = framer.createGoawayFrame(); 260 | frame.setLastStreamId(this._lastStreamId); 261 | frame.setErrorCode(errorCode); 262 | 263 | this.push(frame); 264 | }; 265 | 266 | Connection.prototype.sendPingFrame = function(opaqueData) { 267 | var frame = framer.createPingFrame(); 268 | if (opaqueData) { 269 | frame.setOpaqueData(opaqueData); 270 | } 271 | this.push(frame); 272 | }; 273 | 274 | Connection.prototype._send = function(frame) { 275 | this._serializer.write(frame); 276 | }; 277 | 278 | Connection.prototype._process = function(frame) { 279 | var code = protocol.CODE_NO_ERROR; 280 | 281 | switch (frame.type) { 282 | case protocol.FRAME_TYPE_DATA: 283 | case protocol.FRAME_TYPE_HEADERS: 284 | case protocol.FRAME_TYPE_PRIORITY: 285 | case protocol.FRAME_TYPE_RST_STREAM: 286 | case protocol.FRAME_TYPE_PUSH_PROMISE: 287 | case protocol.FRAME_TYPE_CONTINUATION: 288 | code = protocol.CODE_PROTOCOL_ERROR; 289 | break; 290 | case protocol.FRAME_TYPE_GOAWAY: 291 | code = this._processGoawayFrame(frame); 292 | break; 293 | case protocol.FRAME_TYPE_SETTINGS: 294 | code = this._processSettingsFrame(frame); 295 | break; 296 | case protocol.FRAME_TYPE_PING: 297 | code = this._processPingFrame(frame); 298 | break; 299 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 300 | code = this._processWindowUpdateFrame(frame); 301 | break; 302 | } 303 | 304 | if (code !== protocol.CODE_NO_ERROR) { 305 | this._error(code); 306 | } 307 | }; 308 | 309 | Connection.prototype._processGoawayFrame = function(frame) { 310 | if (frame.errorCode === protocol.CODE_NO_ERROR) { 311 | this.socket.destroy(); 312 | this._close(); 313 | } 314 | 315 | return frame.errorCode; 316 | }; 317 | 318 | Connection.prototype._processSettingsFrame = function(frame) { 319 | var newSettings, settings; 320 | 321 | if (frame.ack) { 322 | clearTimeout(this._settingsTimers.shift()); 323 | newSettings = this._pendingSettings.shift(); 324 | settings = this._localSettings; 325 | } else { 326 | this.sendSettingsFrame(true); 327 | newSettings = frame.getChangedParameters(); 328 | settings = this._remoteSettings; 329 | } 330 | 331 | for (var param in newSettings) { 332 | var value = newSettings[param]; 333 | 334 | switch (param) { 335 | case 'initialWindowSize': 336 | if (frame.ack) { 337 | this.updateInitialWindowSize(value); 338 | } 339 | break; 340 | case 'headerTableSize': 341 | if (frame.ack) { 342 | this._compressor.setHeaderTableSize(value); 343 | } else { 344 | this._decompressor.setHeaderTableSize(value); 345 | } 346 | break; 347 | } 348 | 349 | settings[param] = value; 350 | } 351 | 352 | return protocol.CODE_NO_ERROR; 353 | }; 354 | 355 | Connection.prototype._processPingFrame = function(frame) { 356 | if (frame.ack) { 357 | var self = this; 358 | //setImmediate(function(){ 359 | self.emit('ping'); 360 | //}); 361 | } else { 362 | frame.ack = true; 363 | this.push(frame); 364 | } 365 | 366 | return protocol.CODE_NO_ERROR; 367 | }; 368 | 369 | Connection.prototype._processWindowUpdateFrame = function(frame) { 370 | this.increaseWindowSize(frame.windowSizeIncrement); 371 | return protocol.CODE_NO_ERROR; 372 | }; 373 | 374 | Connection.prototype._close = function(hadError) { 375 | if (this.closed) { 376 | return; 377 | } 378 | 379 | this.closed = true; 380 | this._send = function() {}; 381 | 382 | this.emit('close', hadError); 383 | }; 384 | 385 | Connection.prototype._error = function(errorCode) { 386 | var self = this; 387 | 388 | var err = new Error(); 389 | err.code = errorCode; 390 | 391 | this.emit('error', err); 392 | 393 | process.nextTick(function () { 394 | self._close(true); 395 | }); 396 | }; 397 | 398 | Connection.prototype._connectionPrefaceHandler = function(settings) { 399 | var self = this; 400 | 401 | return function () { 402 | var preface = self.socket.read(protocol.CONNECTION_PREFACE_LEN); 403 | 404 | if (!preface) { 405 | self.socket.once('readable', self._connectionPrefaceHandler()); 406 | return; 407 | } 408 | 409 | if (preface.toString() === protocol.CONNECTION_PREFACE) { 410 | self._setupPipeline(); 411 | self.sendSettingsFrame(settings); 412 | self.emit('connect'); 413 | } else { 414 | self.socket.destroy(); 415 | } 416 | }; 417 | }; 418 | 419 | Connection.prototype._frameHandler = function() { 420 | var self = this; 421 | 422 | return function (frame) { 423 | process.nextTick(function(){ 424 | self.emit('frame', frame); 425 | }); 426 | 427 | if (frame.streamId === 0) { 428 | self._process(frame); 429 | return; 430 | } 431 | 432 | var stream = self._streams[frame.streamId]; 433 | if (!stream) { 434 | stream = self.createStream(frame.streamId); 435 | if (self._remoteStreams > self._localSettings.maxConcurrentStreams) { 436 | stream.sendRstStreamFrame(protocol.CODE_STREAM_ERROR); 437 | return; 438 | } 439 | } 440 | 441 | stream.process(frame); 442 | self._lastStreamId = stream.id; 443 | }; 444 | }; 445 | 446 | Connection.prototype._socketEndHandler = function() { 447 | var self = this; 448 | 449 | return function () { 450 | self.emit('end'); 451 | }; 452 | }; 453 | 454 | Connection.prototype._socketCloseHandler = function() { 455 | var self = this; 456 | 457 | return function (hadError) { 458 | self._close(hadError); 459 | }; 460 | }; 461 | 462 | Connection.prototype._socketErrorHandler = function() { 463 | var self = this; 464 | 465 | return function (err) { 466 | if (self.closed) { 467 | return; 468 | } 469 | self.emit('error', err); 470 | }; 471 | }; 472 | 473 | Connection.prototype._socketTimeoutHandler = function() { 474 | var self = this; 475 | 476 | return function () { 477 | self.emit('timeout'); 478 | }; 479 | }; 480 | 481 | 482 | module.exports = Connection; 483 | -------------------------------------------------------------------------------- /lib/deserializer.js: -------------------------------------------------------------------------------- 1 | var stream = require('stream'), 2 | util = require('util'); 3 | 4 | var framer = require('./framer'), 5 | protocol = require('./protocol'); 6 | 7 | var logger = require('./logger')('deserializer'); 8 | 9 | 10 | function Deserializer (options) { 11 | options = options || {}; 12 | options.objectMode = true; 13 | stream.Transform.call(this, options); 14 | 15 | this._frameLength = null; 16 | } 17 | 18 | util.inherits(Deserializer, stream.Transform); 19 | 20 | Deserializer.prototype._transform = function (chunk, encoding, callback) { 21 | var length = chunk.length; 22 | var offset = 0; 23 | 24 | while (offset < length) { 25 | if (this._frameLength === null) { 26 | this._frameLength = chunk.readUInt32BE(offset) >> 8; 27 | this._frameLength += protocol.FRAME_HEADER_LEN; 28 | } 29 | 30 | var end = offset + this._frameLength; 31 | if (end <= length) { 32 | var buffer = chunk.slice(offset, end); 33 | var frame = framer.decodeFrame(buffer); 34 | 35 | logger.logFrame(frame); 36 | this.push(frame); 37 | 38 | offset = end; 39 | this._frameLength = null; 40 | } else { 41 | break; 42 | } 43 | } 44 | 45 | if (offset < length) { 46 | this.unshift(chunk.slice(offset)); 47 | } 48 | 49 | callback(); 50 | }; 51 | 52 | module.exports = Deserializer; 53 | -------------------------------------------------------------------------------- /lib/flow_controller.js: -------------------------------------------------------------------------------- 1 | var events = require('events'), 2 | util = require('util'); 3 | 4 | var framer = require('./framer'), 5 | protocol = require('./protocol'); 6 | 7 | function FlowController(windowSize) { 8 | if (!windowSize) { 9 | windowSize = protocol.INITIAL_WINDOW_SIZE; 10 | } 11 | 12 | this.initialWindowSize = windowSize; 13 | this.currentWindowSize = windowSize; 14 | 15 | this._queue = []; 16 | } 17 | 18 | util.inherits(FlowController, events.EventEmitter); 19 | 20 | FlowController.prototype.updateInitialWindowSize = function(windowSize) { 21 | var delta = windowSize - this.initialWindowSize; 22 | 23 | if (delta === 0) { 24 | return; 25 | } 26 | 27 | this.initialWindowSize = windowSize; 28 | this.currentWindowSize += delta; 29 | 30 | this._flush(); 31 | }; 32 | 33 | FlowController.prototype.increaseWindowSize = function(increment) { 34 | this.currentWindowSize += increment; 35 | this._flush(); 36 | }; 37 | 38 | FlowController.prototype.push = function(frame) { 39 | if (frame.type === protocol.FRAME_TYPE_DATA) { 40 | this._queue.push(frame); 41 | this._flush(); 42 | } else { 43 | this._send(frame); 44 | } 45 | }; 46 | 47 | FlowController.prototype._flush = function() { 48 | if (this.currentWindowSize === 0) { 49 | return; 50 | } 51 | 52 | while (this.currentWindowSize > 0) { 53 | var frame = this._queue.shift(); 54 | if (frame === undefined) { 55 | return; 56 | } 57 | 58 | var chunkLength = protocol.FRAME_LEN_MAX; 59 | if (chunkLength > this.currentWindowSize) { 60 | chunkLength = this.currentWindowSize; 61 | } 62 | 63 | if (frame.length <= chunkLength) { 64 | this.currentWindowSize -= frame.length; 65 | this._send(frame); 66 | continue; 67 | } 68 | 69 | var chunkedFrame = framer.createDataFrame(); 70 | chunkedFrame.streamId = frame.streamId; 71 | if (frame.padding !== null) { 72 | chunkedFrame.setPadding(frame.padding); 73 | } 74 | 75 | if (chunkedFrame.length >= this.currentWindowSize) { 76 | this._queue.unshift(frame); 77 | return; 78 | } 79 | 80 | var chunkedDataLength = chunkLength - chunkedFrame.length; 81 | var chunkedData = new Buffer(chunkedDataLength); 82 | frame.data.copy(chunkedData, 0, 0, chunkedDataLength); 83 | chunkedFrame.setData(chunkedData); 84 | frame.setData(frame.data.slice(chunkedDataLength)); 85 | 86 | this.currentWindowSize -= chunkedFrame.length; 87 | this._send(chunkedFrame); 88 | 89 | this._queue.unshift(frame); 90 | } 91 | }; 92 | 93 | FlowController.prototype._send = function() { 94 | throw new Error('Not implemented'); 95 | }; 96 | 97 | module.exports = FlowController; 98 | -------------------------------------------------------------------------------- /lib/framer.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var protocol = require('./protocol'); 4 | 5 | // ============================================== 6 | // Generic Frame 7 | // ============================================== 8 | function Frame () { 9 | this.length = 0; 10 | this.streamId = 0; 11 | } 12 | 13 | Frame._decodeHeader = function (buffer) { 14 | return { 15 | length: buffer.readUInt32BE(0) >> 8, 16 | type: buffer.readUInt8(3), 17 | flags: buffer.readUInt8(4), 18 | streamId: buffer.readUInt32BE(5) 19 | }; 20 | }; 21 | 22 | Frame.prototype.encode = function () { 23 | var header = this._encodeHeader(); 24 | var payload = this._encodePayload(); 25 | 26 | return Buffer.concat([header, payload]); 27 | }; 28 | 29 | Frame.prototype._encodeHeader = function () { 30 | var buffer = new Buffer(protocol.FRAME_HEADER_LEN); 31 | 32 | buffer.writeUInt32BE(this.length << 8, 0); 33 | buffer.writeUInt8(this.type, 3); 34 | buffer.writeUInt8(this._encodeFlags(), 4); 35 | buffer.writeUInt32BE(this.streamId, 5); 36 | 37 | return buffer; 38 | }; 39 | 40 | 41 | // ============================================== 42 | // Generic Padding Frame 43 | // ============================================== 44 | function PaddingFrame () { 45 | Frame.call(this); 46 | 47 | this._padded = false; 48 | this.padding = null; 49 | } 50 | 51 | util.inherits(PaddingFrame, Frame); 52 | 53 | PaddingFrame._decodePadding = function (frame, flags, buffer) { 54 | var offset = 0; 55 | 56 | if (flags & protocol.FLAG_PADDED) { 57 | frame._padded = true; 58 | } 59 | 60 | if (frame._padded) { 61 | frame.padding = buffer.readUInt8(0); 62 | offset += 1; 63 | } 64 | 65 | return offset; 66 | }; 67 | 68 | PaddingFrame.prototype.encode = function () { 69 | var header = this._encodeHeader(); 70 | var payload = this._encodePayload(); 71 | payload = this._encodePaddingPayload(payload); 72 | 73 | return Buffer.concat([header, payload]); 74 | }; 75 | 76 | PaddingFrame.prototype._encodeFlags = function () { 77 | var flags = protocol.FLAG_NONE; 78 | 79 | if (this._padded) { 80 | flags |= protocol.FLAG_PADDED; 81 | } 82 | 83 | return flags; 84 | }; 85 | 86 | PaddingFrame.prototype._encodePaddingPayload = function (payload) { 87 | var length = new Buffer(0); 88 | var padding = new Buffer(0); 89 | 90 | if (this._padded) { 91 | length = new Buffer(1); 92 | length.writeUInt8(this.padding, 0); 93 | } 94 | 95 | if (this.padding !== null) { 96 | padding = new Buffer(this.padding); 97 | padding.fill(0); 98 | } 99 | 100 | return Buffer.concat([length, payload, padding]); 101 | }; 102 | 103 | PaddingFrame.prototype.setPadding = function (padding) { 104 | if (padding > 255) { 105 | throw new Error('Padding is too large'); 106 | } 107 | 108 | if (this._padded) { 109 | this._padded = false; 110 | this.length -= 1; 111 | } 112 | if (this.padding !== null && padding !== null) { 113 | this.length -= this.padding; 114 | } 115 | 116 | if (padding !== null) { 117 | this._padded = true; 118 | this.length += (1 + padding); 119 | } 120 | 121 | this.padding = padding; 122 | }; 123 | 124 | 125 | // ============================================== 126 | // DATA Frame 127 | // ============================================== 128 | function DataFrame () { 129 | PaddingFrame.call(this); 130 | 131 | this.type = protocol.FRAME_TYPE_DATA; 132 | 133 | this.endStream = false; 134 | this.endSegment = false; 135 | 136 | this.data = null; 137 | } 138 | 139 | util.inherits(DataFrame, PaddingFrame); 140 | 141 | DataFrame.decode = function (header, buffer) { 142 | var frame = new DataFrame(); 143 | var offset = 0; 144 | 145 | frame.length = header.length; 146 | frame.streamId = header.streamId; 147 | 148 | if (header.flags & protocol.FLAG_END_STREAM) { 149 | frame.endStream = true; 150 | } 151 | if (header.flags & protocol.FLAG_END_SEGMENT) { 152 | frame.endSegment = true; 153 | } 154 | 155 | offset += PaddingFrame._decodePadding(frame, header.flags, buffer); 156 | 157 | var padding = frame.padding || 0; 158 | frame.data = buffer.slice(offset, frame.length - padding); 159 | 160 | return frame; 161 | }; 162 | 163 | DataFrame.prototype._encodeFlags = function () { 164 | var flags = PaddingFrame.prototype._encodeFlags.call(this); 165 | 166 | if (this.endStream) { 167 | flags |= protocol.FLAG_END_STREAM; 168 | } 169 | if (this.endSegment) { 170 | flags |= protocol.FLAG_END_SEGMENT; 171 | } 172 | 173 | return flags; 174 | }; 175 | 176 | DataFrame.prototype._encodePayload = function () { 177 | return (this.data) ? this.data : new Buffer(0); 178 | }; 179 | 180 | DataFrame.prototype.setData = function (data) { 181 | if (!data) { 182 | return; 183 | } 184 | 185 | if (typeof data === 'string') { 186 | data = new Buffer(data); 187 | } 188 | 189 | if (this.data !== null) { 190 | this.length -= this.data.length; 191 | } 192 | 193 | this.data = data; 194 | this.length += this.data.length; 195 | }; 196 | 197 | 198 | // ============================================== 199 | // HEADERS Frame 200 | // ============================================== 201 | function HeadersFrame () { 202 | PaddingFrame.call(this); 203 | 204 | this.type = protocol.FRAME_TYPE_HEADERS; 205 | 206 | this.endStream = false; 207 | this.endHeaders = false; 208 | this.priority = false; 209 | 210 | this.exclusive = null; 211 | this.streamDependency = null; 212 | this.weight = null; 213 | 214 | this.headerBlockFragment = null; 215 | this.headers = null; 216 | } 217 | 218 | util.inherits(HeadersFrame, PaddingFrame); 219 | 220 | HeadersFrame.decode = function (header, buffer) { 221 | var frame = new HeadersFrame(); 222 | var offset = 0; 223 | 224 | frame.length = header.length; 225 | frame.streamId = header.streamId; 226 | 227 | if (header.flags & protocol.FLAG_END_STREAM) { 228 | frame.endStream = true; 229 | } 230 | if (header.flags & protocol.FLAG_END_HEADERS) { 231 | frame.endHeaders = true; 232 | } 233 | if (header.flags & protocol.FLAG_PRIORITY) { 234 | frame.priority = true; 235 | } 236 | 237 | offset += PaddingFrame._decodePadding(frame, header.flags, buffer); 238 | 239 | if (frame.priority) { 240 | var streamDependency = buffer.readUInt32BE(offset); 241 | offset += 4; 242 | 243 | frame.exclusive = (streamDependency & 0x80000000) ? true : false; 244 | frame.streamDependency = (streamDependency & 0x7FFFFFFF); 245 | 246 | frame.weight = buffer.readUInt8(offset); 247 | offset += 1; 248 | } 249 | 250 | var padding = frame.padding || 0; 251 | frame.headerBlockFragment = buffer.slice(offset, frame.length - padding); 252 | 253 | return frame; 254 | }; 255 | 256 | HeadersFrame.prototype._encodeFlags = function () { 257 | var flags = PaddingFrame.prototype._encodeFlags.call(this); 258 | 259 | if (this.endStream) { 260 | flags |= protocol.FLAG_END_STREAM; 261 | } 262 | if (this.endHeaders) { 263 | flags |= protocol.FLAG_END_HEADERS; 264 | } 265 | if (this.priority) { 266 | flags |= protocol.FLAG_PRIORITY; 267 | } 268 | 269 | return flags; 270 | }; 271 | 272 | HeadersFrame.prototype._encodePayload = function () { 273 | var buffers = []; 274 | 275 | if (this.priority) { 276 | var buffer = new Buffer(5); 277 | 278 | var mask = 0x0; 279 | if (this.exclusive) { 280 | mask = 0x80000000; 281 | } 282 | 283 | buffer.writeUInt32BE(this.streamDependency + mask, 0); 284 | buffer.writeUInt8(this.weight, 4); 285 | buffers.push(buffer); 286 | } 287 | 288 | buffers.push(this.headerBlockFragment); 289 | 290 | return Buffer.concat(buffers); 291 | }; 292 | 293 | HeadersFrame.prototype.setPriority = function (dependency, weight, exclusive) { 294 | if (arguments[0] === null) { 295 | this.priority = false; 296 | this.length -= 5; 297 | 298 | this.exclusive = null; 299 | this.streamDependency = null; 300 | this.weight = null; 301 | } 302 | 303 | if (!this.priority) { 304 | this.priority = true; 305 | this.length += 5; 306 | } 307 | 308 | this.exclusive = (exclusive === true) ? true : false; 309 | this.streamDependency = dependency; 310 | this.weight = weight; 311 | }; 312 | 313 | HeadersFrame.prototype.setHeaderBlockFragment = function (headerBlock) { 314 | if (this.headerBlockFragment !== null) { 315 | this.length -= this.headerBlockFragment.length; 316 | } 317 | 318 | this.headerBlockFragment = headerBlock; 319 | this.length += headerBlock.length; 320 | }; 321 | 322 | HeadersFrame.prototype.setHeaders = function (headers) { 323 | this.headers = headers; 324 | }; 325 | 326 | 327 | // ============================================== 328 | // PRIORITY Frame 329 | // ============================================== 330 | function PriorityFrame () { 331 | Frame.call(this); 332 | this.type = protocol.FRAME_TYPE_PRIORITY; 333 | this.length = 5; 334 | 335 | this.exclusive = null; 336 | this.streamDependency = null; 337 | this.weight = null; 338 | } 339 | 340 | util.inherits(PriorityFrame, Frame); 341 | 342 | PriorityFrame.decode = function (header, buffer) { 343 | var frame = new PriorityFrame(); 344 | 345 | frame.length = header.length; 346 | frame.streamId = header.streamId; 347 | 348 | var streamDependency = buffer.readUInt32BE(0); 349 | 350 | frame.exclusive = (streamDependency & 0x80000000) ? true : false; 351 | frame.streamDependency = (streamDependency & 0x7FFFFFFF); 352 | 353 | frame.weight = buffer.readUInt8(4); 354 | 355 | return frame; 356 | }; 357 | 358 | PriorityFrame.prototype._encodeFlags = function () { 359 | return protocol.FLAG_NONE; 360 | }; 361 | 362 | PriorityFrame.prototype._encodePayload = function () { 363 | var buffer = new Buffer(5); 364 | 365 | var mask = 0x0; 366 | if (this.exclusive) { 367 | mask = 0x80000000; 368 | } 369 | 370 | buffer.writeUInt32BE(this.streamDependency + mask, 0); 371 | buffer.writeUInt8(this.weight, 4); 372 | 373 | return buffer; 374 | }; 375 | 376 | PriorityFrame.prototype.setPriority = function (dependency, weight, exclusive) { 377 | this.exclusive = (exclusive === true) ? true : false; 378 | this.streamDependency = dependency; 379 | this.weight = weight; 380 | }; 381 | 382 | 383 | // ============================================== 384 | // RST_STREAM Frame 385 | // ============================================== 386 | function RstStreamFrame () { 387 | Frame.call(this); 388 | this.type = protocol.FRAME_TYPE_RST_STREAM; 389 | this.length = 4; 390 | this.errorCode = protocol.CODE_NO_ERROR; 391 | } 392 | 393 | util.inherits(RstStreamFrame, Frame); 394 | 395 | RstStreamFrame.decode = function (header, buffer) { 396 | var frame = new RstStreamFrame(); 397 | var offset = 0; 398 | 399 | frame.length = header.length; 400 | frame.streamId = header.streamId; 401 | 402 | frame.errorCode = buffer.readUInt32BE(offset); 403 | 404 | return frame; 405 | }; 406 | 407 | RstStreamFrame.prototype._encodeFlags = function () { 408 | return protocol.FLAG_NONE; 409 | }; 410 | 411 | RstStreamFrame.prototype._encodePayload = function () { 412 | var buffer = new Buffer(4); 413 | buffer.writeUInt32BE(this.errorCode, 0); 414 | 415 | return buffer; 416 | }; 417 | 418 | RstStreamFrame.prototype.setErrorCode = function (errorCode) { 419 | this.errorCode = errorCode; 420 | }; 421 | 422 | 423 | // ============================================== 424 | // SETTINGS Frame 425 | // ============================================== 426 | function SettingsFrame () { 427 | Frame.call(this); 428 | 429 | this.type = protocol.FRAME_TYPE_SETTINGS; 430 | 431 | this.ack = false; 432 | 433 | this.headerTableSize = protocol.DEFAULT_HEADER_TABLE_SIZE; 434 | this.enablePush = protocol.DEFAULT_ENABLE_PUSH; 435 | this.maxConcurrentStreams = protocol.DEFAULT_MAX_CONCURRENT_STREAMS; 436 | this.initialWindowSize = protocol.DEFAULT_INITIAL_WINDOW_SIZE; 437 | 438 | this._changed = {}; 439 | } 440 | 441 | util.inherits(SettingsFrame, Frame); 442 | 443 | SettingsFrame.decode = function (header, buffer) { 444 | var frame = new SettingsFrame(); 445 | var offset = 0; 446 | 447 | frame.length = header.length; 448 | frame.streamId = header.streamId; 449 | 450 | if (header.flags & protocol.FLAG_ACK) { 451 | frame.ack = true; 452 | } 453 | 454 | while (buffer.length > offset) { 455 | var identifier = buffer.readUInt16BE(offset); 456 | offset += 2; 457 | 458 | var value = buffer.readUInt32BE(offset); 459 | offset += 4; 460 | 461 | switch (identifier) { 462 | case protocol.SETTINGS_HEADER_TABLE_SIZE: 463 | frame.headerTableSize = value; 464 | frame._changed.headerTableSize = true; 465 | break; 466 | case protocol.SETTINGS_ENABLE_PUSH: 467 | frame.enablePush = (value === 1) ? true : false; 468 | frame._changed.enablePush = true; 469 | break; 470 | case protocol.SETTINGS_MAX_CONCURRENT_STREAMS: 471 | frame.maxConcurrentStreams = value; 472 | frame._changed.maxConcurrentStreams = true; 473 | break; 474 | case protocol.SETTINGS_INITIAL_WINDOW_SIZE: 475 | frame.initialWindowSize = value; 476 | frame._changed.initialWindowSize = true; 477 | break; 478 | } 479 | } 480 | 481 | return frame; 482 | }; 483 | 484 | SettingsFrame.prototype._encodeFlags = function () { 485 | var flags = protocol.FLAG_NONE; 486 | 487 | if (this.ack) { 488 | flags |= protocol.FLAG_ACK; 489 | } 490 | 491 | return flags; 492 | }; 493 | 494 | SettingsFrame.prototype._encodePayload = function () { 495 | var buffers = []; 496 | 497 | for (var param in this._changed) { 498 | if (!this._changed[param]) { 499 | continue; 500 | } 501 | 502 | var buffer = new Buffer(6); 503 | var identifier, value; 504 | 505 | switch (param) { 506 | case 'headerTableSize': 507 | identifier = protocol.SETTINGS_HEADER_TABLE_SIZE; 508 | value = this[param]; 509 | break; 510 | case 'enablePush': 511 | identifier = protocol.SETTINGS_ENABLE_PUSH; 512 | value = this[param] ? 1 : 0; 513 | break; 514 | case 'maxConcurrentStreams': 515 | identifier = protocol.SETTINGS_MAX_CONCURRENT_STREAMS; 516 | value = this[param]; 517 | break; 518 | case 'initialWindowSize': 519 | identifier = protocol.SETTINGS_INITIAL_WINDOW_SIZE; 520 | value = this[param]; 521 | break; 522 | } 523 | 524 | buffer.writeUInt16BE(identifier, 0); 525 | buffer.writeUInt32BE(value, 2); 526 | buffers.push(buffer); 527 | } 528 | 529 | return Buffer.concat(buffers); 530 | }; 531 | 532 | SettingsFrame.prototype._setParameter = function (param, value, defaultValue) { 533 | var changed = (this[param] !== value); 534 | var alreadyChanged = (this[param] !== defaultValue); 535 | 536 | if (changed) { 537 | this[param] = value; 538 | this._changed[param] = true; 539 | 540 | if (!alreadyChanged) { 541 | this.length += 6; 542 | } 543 | } 544 | }; 545 | 546 | SettingsFrame.prototype.setHeaderTableSize = function (value) { 547 | var param = 'headerTableSize'; 548 | var defaultValue = protocol.DEFAULT_HEADER_TABLE_SIZE; 549 | this._setParameter(param, value, defaultValue); 550 | }; 551 | 552 | SettingsFrame.prototype.setEnablePush = function (value) { 553 | var param = 'enablePush'; 554 | var defaultValue = protocol.DEFAULT_ENABLE_PUSH; 555 | this._setParameter(param, value, defaultValue); 556 | }; 557 | 558 | SettingsFrame.prototype.setMaxConcurrentStreams = function (value) { 559 | var param = 'maxConcurrentStreams'; 560 | var defaultValue = protocol.DEFAULT_MAX_CONCURRENT_STREAMS; 561 | this._setParameter(param, value, defaultValue); 562 | }; 563 | 564 | SettingsFrame.prototype.setInitialWindowSize = function (value) { 565 | var param = 'initialWindowSize'; 566 | var defaultValue = protocol.DEFAULT_INITIAL_WINDOW_SIZE; 567 | this._setParameter(param, value, defaultValue); 568 | }; 569 | 570 | SettingsFrame.prototype.getChangedParameters = function () { 571 | var changedParams = {}; 572 | 573 | for (var param in this._changed) { 574 | changedParams[param] = this[param]; 575 | } 576 | 577 | return changedParams; 578 | }; 579 | 580 | 581 | // ============================================== 582 | // PUSH_PROMISE Frame 583 | // ============================================== 584 | function PushPromiseFrame () { 585 | PaddingFrame.call(this); 586 | 587 | this.type = protocol.FRAME_TYPE_PUSH_PROMISE; 588 | this.length = 4; 589 | 590 | this.endHeaders = false; 591 | 592 | this.promisedStreamId = 0; 593 | this.headerBlockFragment = null; 594 | this.headers = null; 595 | } 596 | 597 | util.inherits(PushPromiseFrame, PaddingFrame); 598 | 599 | PushPromiseFrame.decode = function (header, buffer) { 600 | var frame = new PushPromiseFrame(); 601 | var offset = 0; 602 | 603 | frame.length = header.length; 604 | frame.streamId = header.streamId; 605 | 606 | if (header.flags & protocol.FLAG_END_HEADERS) { 607 | frame.endHeaders = true; 608 | } 609 | 610 | offset += PaddingFrame._decodePadding(frame, header.flags, buffer); 611 | 612 | frame.promisedStreamId = buffer.readUInt32BE(offset) & 0x7FFFFFFF; 613 | offset += 4; 614 | 615 | var padding = frame.padding || 0; 616 | frame.headerBlockFragment = buffer.slice(offset, frame.length - padding); 617 | 618 | return frame; 619 | }; 620 | 621 | PushPromiseFrame.prototype._encodeFlags = function () { 622 | var flags = PaddingFrame.prototype._encodeFlags.call(this); 623 | 624 | if (this.endHeaders) { 625 | flags |= protocol.FLAG_END_HEADERS; 626 | } 627 | 628 | return flags; 629 | }; 630 | 631 | PushPromiseFrame.prototype._encodePayload = function () { 632 | var buffer = new Buffer(4); 633 | buffer.writeUInt32BE(this.promisedStreamId & 0x7FFFFFFF, 0); 634 | 635 | return Buffer.concat([buffer, this.headerBlockFragment]); 636 | }; 637 | 638 | PushPromiseFrame.prototype.setPromisedStreamId = function (promisedStreamId) { 639 | this.promisedStreamId = promisedStreamId; 640 | }; 641 | 642 | PushPromiseFrame.prototype.setHeaderBlockFragment = function (headerBlock) { 643 | if (this.headerBlockFragment !== null) { 644 | this.length -= this.headerBlockFragment.length; 645 | } 646 | 647 | this.headerBlockFragment = headerBlock; 648 | this.length += headerBlock.length; 649 | }; 650 | 651 | PushPromiseFrame.prototype.setHeaders = function (headers) { 652 | this.headers = headers; 653 | }; 654 | 655 | 656 | // ============================================== 657 | // PING Frame 658 | // ============================================== 659 | function PingFrame () { 660 | Frame.call(this); 661 | 662 | this.type = protocol.FRAME_TYPE_PING; 663 | this.length = 8; 664 | 665 | this.ack = false; 666 | 667 | this.opaqueData = new Buffer(8); 668 | this.opaqueData.fill(0); 669 | } 670 | 671 | util.inherits(PingFrame, Frame); 672 | 673 | PingFrame.decode = function (header, buffer) { 674 | var frame = new PingFrame(); 675 | 676 | frame.length = header.length; 677 | frame.streamId = header.streamId; 678 | 679 | if (header.flags & protocol.FLAG_ACK) { 680 | frame.ack = true; 681 | } 682 | 683 | frame.opaqueData = buffer.slice(0, 8); 684 | 685 | return frame; 686 | }; 687 | 688 | PingFrame.prototype._encodeFlags = function () { 689 | var flags = protocol.FLAG_NONE; 690 | 691 | if (this.ack) { 692 | flags |= protocol.FLAG_ACK; 693 | } 694 | 695 | return flags; 696 | }; 697 | 698 | PingFrame.prototype._encodePayload = function () { 699 | return this.opaqueData.slice(0, 8); 700 | }; 701 | 702 | PingFrame.prototype.setOpaqueData = function (opaqueData) { 703 | if (!opaqueData) { 704 | return; 705 | } 706 | 707 | if (typeof opaqueData === 'string') { 708 | this.opaqueData = new Buffer(8); 709 | this.opaqueData.fill(0); 710 | this.opaqueData.write(opaqueData); 711 | } else { 712 | this.opaqueData = opaqueData; 713 | } 714 | }; 715 | 716 | 717 | // ============================================== 718 | // GOAWAY Frame 719 | // ============================================== 720 | function GoawayFrame () { 721 | Frame.call(this); 722 | 723 | this.type = protocol.FRAME_TYPE_GOAWAY; 724 | this.length = 8; 725 | 726 | this.lastStreamId = 0; 727 | this.errorCode = protocol.CODE_NO_ERROR; 728 | this.debugData = null; 729 | } 730 | 731 | util.inherits(GoawayFrame, Frame); 732 | 733 | GoawayFrame.decode = function (header, buffer) { 734 | var frame = new GoawayFrame(); 735 | var offset = 0; 736 | 737 | frame.length = header.length; 738 | frame.streamId = header.streamId; 739 | 740 | frame.lastStreamId = buffer.readUInt32BE(offset); 741 | offset += 4; 742 | frame.errorCode = buffer.readUInt32BE(offset); 743 | offset += 4; 744 | 745 | if (offset !== buffer.length) { 746 | frame.debugData = buffer.slice(offset); 747 | } 748 | 749 | return frame; 750 | }; 751 | 752 | GoawayFrame.prototype._encodeFlags = function () { 753 | return protocol.FLAG_NONE; 754 | }; 755 | 756 | GoawayFrame.prototype._encodePayload = function () { 757 | var buffers = []; 758 | 759 | var payload = new Buffer(8); 760 | payload.writeUInt32BE(this.lastStreamId, 0); 761 | payload.writeUInt32BE(this.errorCode, 4); 762 | buffers.push(payload); 763 | 764 | if (this.debugData) { 765 | buffers.push(this.debugData); 766 | } 767 | 768 | return Buffer.concat(buffers); 769 | }; 770 | 771 | GoawayFrame.prototype.setLastStreamId = function (lastStreamId) { 772 | this.lastStreamId = lastStreamId; 773 | }; 774 | 775 | GoawayFrame.prototype.setErrorCode = function (errorCode) { 776 | this.errorCode = errorCode; 777 | }; 778 | 779 | GoawayFrame.prototype.setDebugData = function (debugData) { 780 | if (!debugData) { 781 | return; 782 | } 783 | 784 | if (typeof debugData === 'string') { 785 | debugData = new Buffer(debugData); 786 | } 787 | 788 | this.debugData = debugData; 789 | this.length += debugData.length; 790 | }; 791 | 792 | 793 | // ============================================== 794 | // WINDOW_UPDATE Frame 795 | // ============================================== 796 | function WindowUpdateFrame () { 797 | Frame.call(this); 798 | 799 | this.type = protocol.FRAME_TYPE_WINDOW_UPDATE; 800 | this.length = 4; 801 | 802 | this.windowSizeIncrement = 0; 803 | } 804 | 805 | util.inherits(WindowUpdateFrame, Frame); 806 | 807 | WindowUpdateFrame.decode = function (header, buffer) { 808 | var frame = new WindowUpdateFrame(); 809 | 810 | frame.length = header.length; 811 | frame.streamId = header.streamId; 812 | frame.windowSizeIncrement = buffer.readUInt32BE(0); 813 | 814 | return frame; 815 | }; 816 | 817 | WindowUpdateFrame.prototype._encodeFlags = function () { 818 | return protocol.FLAG_NONE; 819 | }; 820 | 821 | WindowUpdateFrame.prototype._encodePayload = function () { 822 | var buffer = new Buffer(4); 823 | buffer.writeUInt32BE(this.windowSizeIncrement & 0x7FFFFFFF, 0); 824 | 825 | return buffer; 826 | }; 827 | 828 | WindowUpdateFrame.prototype.setWindowSizeIncrement = function (increment) { 829 | this.windowSizeIncrement = increment; 830 | }; 831 | 832 | 833 | // ============================================== 834 | // CONTINUATION Frame 835 | // ============================================== 836 | function ContinuationFrame () { 837 | Frame.call(this); 838 | 839 | this.type = protocol.FRAME_TYPE_CONTINUATION; 840 | 841 | this.endHeaders = false; 842 | 843 | this.headerBlockFragment = null; 844 | } 845 | 846 | util.inherits(ContinuationFrame, Frame); 847 | 848 | ContinuationFrame.decode = function (header, buffer) { 849 | var frame = new ContinuationFrame(); 850 | var offset = 0; 851 | 852 | frame.length = header.length; 853 | frame.streamId = header.streamId; 854 | 855 | if (header.flags & protocol.FLAG_END_HEADERS) { 856 | frame.endHeaders = true; 857 | } 858 | 859 | var padding = frame.padding || 0; 860 | frame.headerBlockFragment = buffer.slice(offset, frame.length - padding); 861 | 862 | return frame; 863 | }; 864 | 865 | ContinuationFrame.prototype._encodeFlags = function () { 866 | var flags = protocol.FLAG_NONE; 867 | 868 | if (this.endHeaders) { 869 | flags |= protocol.FLAG_END_HEADERS; 870 | } 871 | 872 | return flags; 873 | }; 874 | 875 | ContinuationFrame.prototype._encodePayload = function () { 876 | return this.headerBlockFragment; 877 | }; 878 | 879 | ContinuationFrame.prototype.setHeaderBlockFragment = function (headerBlock) { 880 | if (this.headerBlockFragment !== null) { 881 | this.length -= this.headerBlockFragment.length; 882 | } 883 | 884 | this.headerBlockFragment = headerBlock; 885 | this.length += headerBlock.length; 886 | }; 887 | 888 | 889 | // ============================================== 890 | // Export functions 891 | // ============================================== 892 | exports.decodeFrame = function (buffer) { 893 | var decoder; 894 | 895 | var header = Frame._decodeHeader(buffer); 896 | buffer = buffer.slice(protocol.FRAME_HEADER_LEN); 897 | 898 | switch (header.type) { 899 | case protocol.FRAME_TYPE_DATA: 900 | decoder = DataFrame; 901 | break; 902 | case protocol.FRAME_TYPE_HEADERS: 903 | decoder = HeadersFrame; 904 | break; 905 | case protocol.FRAME_TYPE_PRIORITY: 906 | decoder = PriorityFrame; 907 | break; 908 | case protocol.FRAME_TYPE_RST_STREAM: 909 | decoder = RstStreamFrame; 910 | break; 911 | case protocol.FRAME_TYPE_SETTINGS: 912 | decoder = SettingsFrame; 913 | break; 914 | case protocol.FRAME_TYPE_PUSH_PROMISE: 915 | decoder = PushPromiseFrame; 916 | break; 917 | case protocol.FRAME_TYPE_PING: 918 | decoder = PingFrame; 919 | break; 920 | case protocol.FRAME_TYPE_GOAWAY: 921 | decoder = GoawayFrame; 922 | break; 923 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 924 | decoder = WindowUpdateFrame; 925 | break; 926 | case protocol.FRAME_TYPE_CONTINUATION: 927 | decoder = ContinuationFrame; 928 | break; 929 | default: 930 | throw new Error('Unknown frame type'); 931 | } 932 | 933 | return decoder.decode(header, buffer); 934 | }; 935 | 936 | exports.createDataFrame = function () { 937 | return new DataFrame(); 938 | }; 939 | 940 | exports.createHeadersFrame = function () { 941 | return new HeadersFrame(); 942 | }; 943 | 944 | exports.createPriorityFrame = function () { 945 | return new PriorityFrame(); 946 | }; 947 | 948 | exports.createRstStreamFrame = function () { 949 | return new RstStreamFrame(); 950 | }; 951 | 952 | exports.createSettingsFrame = function () { 953 | return new SettingsFrame(); 954 | }; 955 | 956 | exports.createPushPromiseFrame = function () { 957 | return new PushPromiseFrame(); 958 | }; 959 | 960 | exports.createPingFrame = function () { 961 | return new PingFrame(); 962 | }; 963 | 964 | exports.createGoawayFrame = function () { 965 | return new GoawayFrame(); 966 | }; 967 | 968 | exports.createWindowUpdateFrame = function () { 969 | return new WindowUpdateFrame(); 970 | }; 971 | 972 | exports.createContinuationFrame = function () { 973 | return new ContinuationFrame(); 974 | }; 975 | -------------------------------------------------------------------------------- /lib/hpack.js: -------------------------------------------------------------------------------- 1 | var REP_INDEXED = 0; 2 | var REP_LITERAL_INCR = 1; 3 | var REP_LITERAL = 2; 4 | var REP_LITERAL_NEVER = 3; 5 | var REP_HEADER_TABLE_SIZE = 4; 6 | 7 | var REP_INFO = [ 8 | { prefix: 7, mask: 0x80 }, // REP_INDEXED 9 | { prefix: 6, mask: 0x40 }, // REP_LITERAL_INCR 10 | { prefix: 4, mask: 0x00 }, // REP_LITERAL 11 | { prefix: 4, mask: 0x10 }, // REP_LITERAL_NEVER 12 | { prefix: 5, mask: 0x20 } // REP_HEADER_TABLE_SIZE 13 | ]; 14 | 15 | var STATIC_TABLE_PAIRS = [ 16 | [ ':authority', '' ], 17 | [ ':method', 'GET' ], 18 | [ ':method', 'POST' ], 19 | [ ':path', '/' ], 20 | [ ':path', '/index.html' ], 21 | [ ':scheme', 'http' ], 22 | [ ':scheme', 'https' ], 23 | [ ':status', '200' ], 24 | [ ':status', '204' ], 25 | [ ':status', '206' ], 26 | [ ':status', '304' ], 27 | [ ':status', '400' ], 28 | [ ':status', '404' ], 29 | [ ':status', '500' ], 30 | [ 'accept-charset', '' ], 31 | [ 'accept-encoding', 'gzip, deflate' ], 32 | [ 'accept-language', '' ], 33 | [ 'accept-ranges', '' ], 34 | [ 'accept', '' ], 35 | [ 'access-control-allow-origin', '' ], 36 | [ 'age', '' ], 37 | [ 'allow', '' ], 38 | [ 'authorization', '' ], 39 | [ 'cache-control', '' ], 40 | [ 'content-disposition', '' ], 41 | [ 'content-encoding', '' ], 42 | [ 'content-language', '' ], 43 | [ 'content-length', '' ], 44 | [ 'content-location', '' ], 45 | [ 'content-range', '' ], 46 | [ 'content-type', '' ], 47 | [ 'cookie', '' ], 48 | [ 'date', '' ], 49 | [ 'etag', '' ], 50 | [ 'expect', '' ], 51 | [ 'expires', '' ], 52 | [ 'from', '' ], 53 | [ 'host', '' ], 54 | [ 'if-match', '' ], 55 | [ 'if-modified-since', '' ], 56 | [ 'if-none-match', '' ], 57 | [ 'if-range', '' ], 58 | [ 'if-unmodified-since', '' ], 59 | [ 'last-modified', '' ], 60 | [ 'link', '' ], 61 | [ 'location', '' ], 62 | [ 'max-forwards', '' ], 63 | [ 'proxy-authenticate', '' ], 64 | [ 'proxy-authorization', '' ], 65 | [ 'range', '' ], 66 | [ 'referer', '' ], 67 | [ 'refresh', '' ], 68 | [ 'retry-after', '' ], 69 | [ 'server', '' ], 70 | [ 'set-cookie', '' ], 71 | [ 'strict-transport-security', '' ], 72 | [ 'transfer-encoding', '' ], 73 | [ 'user-agent', '' ], 74 | [ 'vary', '' ], 75 | [ 'via', '' ], 76 | [ 'www-authenticate', '' ] 77 | ]; 78 | 79 | var HUFFMAN_TABLE = [ 80 | { code: 0x1ff8, bit: 13 }, 81 | { code: 0x7fffd8, bit: 23 }, 82 | { code: 0xfffffe2, bit: 28 }, 83 | { code: 0xfffffe3, bit: 28 }, 84 | { code: 0xfffffe4, bit: 28 }, 85 | { code: 0xfffffe5, bit: 28 }, 86 | { code: 0xfffffe6, bit: 28 }, 87 | { code: 0xfffffe7, bit: 28 }, 88 | { code: 0xfffffe8, bit: 28 }, 89 | { code: 0xffffea, bit: 24 }, 90 | { code: 0x3ffffffc, bit: 30 }, 91 | { code: 0xfffffe9, bit: 28 }, 92 | { code: 0xfffffea, bit: 28 }, 93 | { code: 0x3ffffffd, bit: 30 }, 94 | { code: 0xfffffeb, bit: 28 }, 95 | { code: 0xfffffec, bit: 28 }, 96 | { code: 0xfffffed, bit: 28 }, 97 | { code: 0xfffffee, bit: 28 }, 98 | { code: 0xfffffef, bit: 28 }, 99 | { code: 0xffffff0, bit: 28 }, 100 | { code: 0xffffff1, bit: 28 }, 101 | { code: 0xffffff2, bit: 28 }, 102 | { code: 0x3ffffffe, bit: 30 }, 103 | { code: 0xffffff3, bit: 28 }, 104 | { code: 0xffffff4, bit: 28 }, 105 | { code: 0xffffff5, bit: 28 }, 106 | { code: 0xffffff6, bit: 28 }, 107 | { code: 0xffffff7, bit: 28 }, 108 | { code: 0xffffff8, bit: 28 }, 109 | { code: 0xffffff9, bit: 28 }, 110 | { code: 0xffffffa, bit: 28 }, 111 | { code: 0xffffffb, bit: 28 }, 112 | { code: 0x14, bit: 6 }, 113 | { code: 0x3f8, bit: 10 }, 114 | { code: 0x3f9, bit: 10 }, 115 | { code: 0xffa, bit: 12 }, 116 | { code: 0x1ff9, bit: 13 }, 117 | { code: 0x15, bit: 6 }, 118 | { code: 0xf8, bit: 8 }, 119 | { code: 0x7fa, bit: 11 }, 120 | { code: 0x3fa, bit: 10 }, 121 | { code: 0x3fb, bit: 10 }, 122 | { code: 0xf9, bit: 8 }, 123 | { code: 0x7fb, bit: 11 }, 124 | { code: 0xfa, bit: 8 }, 125 | { code: 0x16, bit: 6 }, 126 | { code: 0x17, bit: 6 }, 127 | { code: 0x18, bit: 6 }, 128 | { code: 0x0, bit: 5 }, 129 | { code: 0x1, bit: 5 }, 130 | { code: 0x2, bit: 5 }, 131 | { code: 0x19, bit: 6 }, 132 | { code: 0x1a, bit: 6 }, 133 | { code: 0x1b, bit: 6 }, 134 | { code: 0x1c, bit: 6 }, 135 | { code: 0x1d, bit: 6 }, 136 | { code: 0x1e, bit: 6 }, 137 | { code: 0x1f, bit: 6 }, 138 | { code: 0x5c, bit: 7 }, 139 | { code: 0xfb, bit: 8 }, 140 | { code: 0x7ffc, bit: 15 }, 141 | { code: 0x20, bit: 6 }, 142 | { code: 0xffb, bit: 12 }, 143 | { code: 0x3fc, bit: 10 }, 144 | { code: 0x1ffa, bit: 13 }, 145 | { code: 0x21, bit: 6 }, 146 | { code: 0x5d, bit: 7 }, 147 | { code: 0x5e, bit: 7 }, 148 | { code: 0x5f, bit: 7 }, 149 | { code: 0x60, bit: 7 }, 150 | { code: 0x61, bit: 7 }, 151 | { code: 0x62, bit: 7 }, 152 | { code: 0x63, bit: 7 }, 153 | { code: 0x64, bit: 7 }, 154 | { code: 0x65, bit: 7 }, 155 | { code: 0x66, bit: 7 }, 156 | { code: 0x67, bit: 7 }, 157 | { code: 0x68, bit: 7 }, 158 | { code: 0x69, bit: 7 }, 159 | { code: 0x6a, bit: 7 }, 160 | { code: 0x6b, bit: 7 }, 161 | { code: 0x6c, bit: 7 }, 162 | { code: 0x6d, bit: 7 }, 163 | { code: 0x6e, bit: 7 }, 164 | { code: 0x6f, bit: 7 }, 165 | { code: 0x70, bit: 7 }, 166 | { code: 0x71, bit: 7 }, 167 | { code: 0x72, bit: 7 }, 168 | { code: 0xfc, bit: 8 }, 169 | { code: 0x73, bit: 7 }, 170 | { code: 0xfd, bit: 8 }, 171 | { code: 0x1ffb, bit: 13 }, 172 | { code: 0x7fff0, bit: 19 }, 173 | { code: 0x1ffc, bit: 13 }, 174 | { code: 0x3ffc, bit: 14 }, 175 | { code: 0x22, bit: 6 }, 176 | { code: 0x7ffd, bit: 15 }, 177 | { code: 0x3, bit: 5 }, 178 | { code: 0x23, bit: 6 }, 179 | { code: 0x4, bit: 5 }, 180 | { code: 0x24, bit: 6 }, 181 | { code: 0x5, bit: 5 }, 182 | { code: 0x25, bit: 6 }, 183 | { code: 0x26, bit: 6 }, 184 | { code: 0x27, bit: 6 }, 185 | { code: 0x6, bit: 5 }, 186 | { code: 0x74, bit: 7 }, 187 | { code: 0x75, bit: 7 }, 188 | { code: 0x28, bit: 6 }, 189 | { code: 0x29, bit: 6 }, 190 | { code: 0x2a, bit: 6 }, 191 | { code: 0x7, bit: 5 }, 192 | { code: 0x2b, bit: 6 }, 193 | { code: 0x76, bit: 7 }, 194 | { code: 0x2c, bit: 6 }, 195 | { code: 0x8, bit: 5 }, 196 | { code: 0x9, bit: 5 }, 197 | { code: 0x2d, bit: 6 }, 198 | { code: 0x77, bit: 7 }, 199 | { code: 0x78, bit: 7 }, 200 | { code: 0x79, bit: 7 }, 201 | { code: 0x7a, bit: 7 }, 202 | { code: 0x7b, bit: 7 }, 203 | { code: 0x7ffe, bit: 15 }, 204 | { code: 0x7fc, bit: 11 }, 205 | { code: 0x3ffd, bit: 14 }, 206 | { code: 0x1ffd, bit: 13 }, 207 | { code: 0xffffffc, bit: 28 }, 208 | { code: 0xfffe6, bit: 20 }, 209 | { code: 0x3fffd2, bit: 22 }, 210 | { code: 0xfffe7, bit: 20 }, 211 | { code: 0xfffe8, bit: 20 }, 212 | { code: 0x3fffd3, bit: 22 }, 213 | { code: 0x3fffd4, bit: 22 }, 214 | { code: 0x3fffd5, bit: 22 }, 215 | { code: 0x7fffd9, bit: 23 }, 216 | { code: 0x3fffd6, bit: 22 }, 217 | { code: 0x7fffda, bit: 23 }, 218 | { code: 0x7fffdb, bit: 23 }, 219 | { code: 0x7fffdc, bit: 23 }, 220 | { code: 0x7fffdd, bit: 23 }, 221 | { code: 0x7fffde, bit: 23 }, 222 | { code: 0xffffeb, bit: 24 }, 223 | { code: 0x7fffdf, bit: 23 }, 224 | { code: 0xffffec, bit: 24 }, 225 | { code: 0xffffed, bit: 24 }, 226 | { code: 0x3fffd7, bit: 22 }, 227 | { code: 0x7fffe0, bit: 23 }, 228 | { code: 0xffffee, bit: 24 }, 229 | { code: 0x7fffe1, bit: 23 }, 230 | { code: 0x7fffe2, bit: 23 }, 231 | { code: 0x7fffe3, bit: 23 }, 232 | { code: 0x7fffe4, bit: 23 }, 233 | { code: 0x1fffdc, bit: 21 }, 234 | { code: 0x3fffd8, bit: 22 }, 235 | { code: 0x7fffe5, bit: 23 }, 236 | { code: 0x3fffd9, bit: 22 }, 237 | { code: 0x7fffe6, bit: 23 }, 238 | { code: 0x7fffe7, bit: 23 }, 239 | { code: 0xffffef, bit: 24 }, 240 | { code: 0x3fffda, bit: 22 }, 241 | { code: 0x1fffdd, bit: 21 }, 242 | { code: 0xfffe9, bit: 20 }, 243 | { code: 0x3fffdb, bit: 22 }, 244 | { code: 0x3fffdc, bit: 22 }, 245 | { code: 0x7fffe8, bit: 23 }, 246 | { code: 0x7fffe9, bit: 23 }, 247 | { code: 0x1fffde, bit: 21 }, 248 | { code: 0x7fffea, bit: 23 }, 249 | { code: 0x3fffdd, bit: 22 }, 250 | { code: 0x3fffde, bit: 22 }, 251 | { code: 0xfffff0, bit: 24 }, 252 | { code: 0x1fffdf, bit: 21 }, 253 | { code: 0x3fffdf, bit: 22 }, 254 | { code: 0x7fffeb, bit: 23 }, 255 | { code: 0x7fffec, bit: 23 }, 256 | { code: 0x1fffe0, bit: 21 }, 257 | { code: 0x1fffe1, bit: 21 }, 258 | { code: 0x3fffe0, bit: 22 }, 259 | { code: 0x1fffe2, bit: 21 }, 260 | { code: 0x7fffed, bit: 23 }, 261 | { code: 0x3fffe1, bit: 22 }, 262 | { code: 0x7fffee, bit: 23 }, 263 | { code: 0x7fffef, bit: 23 }, 264 | { code: 0xfffea, bit: 20 }, 265 | { code: 0x3fffe2, bit: 22 }, 266 | { code: 0x3fffe3, bit: 22 }, 267 | { code: 0x3fffe4, bit: 22 }, 268 | { code: 0x7ffff0, bit: 23 }, 269 | { code: 0x3fffe5, bit: 22 }, 270 | { code: 0x3fffe6, bit: 22 }, 271 | { code: 0x7ffff1, bit: 23 }, 272 | { code: 0x3ffffe0, bit: 26 }, 273 | { code: 0x3ffffe1, bit: 26 }, 274 | { code: 0xfffeb, bit: 20 }, 275 | { code: 0x7fff1, bit: 19 }, 276 | { code: 0x3fffe7, bit: 22 }, 277 | { code: 0x7ffff2, bit: 23 }, 278 | { code: 0x3fffe8, bit: 22 }, 279 | { code: 0x1ffffec, bit: 25 }, 280 | { code: 0x3ffffe2, bit: 26 }, 281 | { code: 0x3ffffe3, bit: 26 }, 282 | { code: 0x3ffffe4, bit: 26 }, 283 | { code: 0x7ffffde, bit: 27 }, 284 | { code: 0x7ffffdf, bit: 27 }, 285 | { code: 0x3ffffe5, bit: 26 }, 286 | { code: 0xfffff1, bit: 24 }, 287 | { code: 0x1ffffed, bit: 25 }, 288 | { code: 0x7fff2, bit: 19 }, 289 | { code: 0x1fffe3, bit: 21 }, 290 | { code: 0x3ffffe6, bit: 26 }, 291 | { code: 0x7ffffe0, bit: 27 }, 292 | { code: 0x7ffffe1, bit: 27 }, 293 | { code: 0x3ffffe7, bit: 26 }, 294 | { code: 0x7ffffe2, bit: 27 }, 295 | { code: 0xfffff2, bit: 24 }, 296 | { code: 0x1fffe4, bit: 21 }, 297 | { code: 0x1fffe5, bit: 21 }, 298 | { code: 0x3ffffe8, bit: 26 }, 299 | { code: 0x3ffffe9, bit: 26 }, 300 | { code: 0xffffffd, bit: 28 }, 301 | { code: 0x7ffffe3, bit: 27 }, 302 | { code: 0x7ffffe4, bit: 27 }, 303 | { code: 0x7ffffe5, bit: 27 }, 304 | { code: 0xfffec, bit: 20 }, 305 | { code: 0xfffff3, bit: 24 }, 306 | { code: 0xfffed, bit: 20 }, 307 | { code: 0x1fffe6, bit: 21 }, 308 | { code: 0x3fffe9, bit: 22 }, 309 | { code: 0x1fffe7, bit: 21 }, 310 | { code: 0x1fffe8, bit: 21 }, 311 | { code: 0x7ffff3, bit: 23 }, 312 | { code: 0x3fffea, bit: 22 }, 313 | { code: 0x3fffeb, bit: 22 }, 314 | { code: 0x1ffffee, bit: 25 }, 315 | { code: 0x1ffffef, bit: 25 }, 316 | { code: 0xfffff4, bit: 24 }, 317 | { code: 0xfffff5, bit: 24 }, 318 | { code: 0x3ffffea, bit: 26 }, 319 | { code: 0x7ffff4, bit: 23 }, 320 | { code: 0x3ffffeb, bit: 26 }, 321 | { code: 0x7ffffe6, bit: 27 }, 322 | { code: 0x3ffffec, bit: 26 }, 323 | { code: 0x3ffffed, bit: 26 }, 324 | { code: 0x7ffffe7, bit: 27 }, 325 | { code: 0x7ffffe8, bit: 27 }, 326 | { code: 0x7ffffe9, bit: 27 }, 327 | { code: 0x7ffffea, bit: 27 }, 328 | { code: 0x7ffffeb, bit: 27 }, 329 | { code: 0xffffffe, bit: 28 }, 330 | { code: 0x7ffffec, bit: 27 }, 331 | { code: 0x7ffffed, bit: 27 }, 332 | { code: 0x7ffffee, bit: 27 }, 333 | { code: 0x7ffffef, bit: 27 }, 334 | { code: 0x7fffff0, bit: 27 }, 335 | { code: 0x3ffffee, bit: 26 }, 336 | { code: 0x3fffffff, bit: 30 } 337 | ]; 338 | 339 | var HUFFMAN_TABLE_TREE = (function () { 340 | var root = []; 341 | var idx, len; 342 | 343 | for (idx = 0, len = HUFFMAN_TABLE.length; idx < len; idx++) { 344 | var node = root; 345 | var huffman = HUFFMAN_TABLE[idx]; 346 | 347 | for (var bit=huffman.bit-1; bit>=0; bit--) { 348 | var tree = 0; 349 | if (((huffman.code >> bit) & 0x01) === 1) { 350 | tree = 1; 351 | } 352 | 353 | if (bit === 0) { 354 | node[tree] = idx; 355 | } else { 356 | if (node[tree] === undefined) { 357 | node[tree] = []; 358 | } 359 | node = node[tree]; 360 | } 361 | } 362 | } 363 | 364 | return root; 365 | })(); 366 | 367 | var DEFAULT_HEADER_TABLE_SIZE = 4096; 368 | 369 | var LITERAL_HEADERS = { 370 | 'set-cookie': true, 371 | 'content-length': true, 372 | 'location': true, 373 | 'etag': true, 374 | ':path': true 375 | }; 376 | 377 | 378 | // ======================================================== 379 | // Entry 380 | // ======================================================== 381 | function Entry(name, value) { 382 | this.name = name.toLowerCase(); 383 | this.value = value; 384 | 385 | this.size = 32; 386 | this.size += new Buffer(this.name, 'ascii').length; 387 | this.size += new Buffer(this.value, 'ascii').length; 388 | } 389 | 390 | // ======================================================== 391 | // Static Table 392 | // ======================================================== 393 | var STATIC_TABLE = STATIC_TABLE_PAIRS.map(function(pair){ 394 | return new Entry(pair[0], pair[1]); 395 | }); 396 | var STATIC_TABLE_LENGTH = STATIC_TABLE.length; 397 | 398 | 399 | // ======================================================== 400 | // Header Table 401 | // ======================================================== 402 | function HeaderTable(maxSize) { 403 | this._entries = []; 404 | this.length = 0; 405 | this.size = 0; 406 | this.maxSize = maxSize || DEFAULT_HEADER_TABLE_SIZE; 407 | } 408 | 409 | HeaderTable.prototype.setMaxSize = function (maxSize) { 410 | if (this.maxSize === maxSize) { 411 | return; 412 | } 413 | 414 | while(this.size > maxSize) { 415 | this.pop(); 416 | } 417 | this.maxSize = maxSize; 418 | }; 419 | 420 | HeaderTable.prototype.get = function (index) { 421 | var internalIndex = index - 1; 422 | 423 | if (index < STATIC_TABLE_LENGTH) { 424 | return STATIC_TABLE[internalIndex]; 425 | } 426 | 427 | internalIndex = internalIndex - STATIC_TABLE_LENGTH; 428 | if (internalIndex >= this.length || !this._entries[internalIndex]) { 429 | throw new Error('Invalid index: ' + index); 430 | } 431 | 432 | return this._entries[internalIndex]; 433 | }; 434 | 435 | HeaderTable.prototype.find = function (name, value) { 436 | var idx, len, entry; 437 | var index = null; 438 | var exact = false; 439 | 440 | for (idx=0, len=STATIC_TABLE_LENGTH; idx this.maxSize) { 507 | this._clear(); 508 | return; 509 | } 510 | 511 | while ((this.size + entry.size) > this.maxSize) { 512 | this.pop(); 513 | } 514 | }; 515 | 516 | 517 | // ======================================================== 518 | // Huffman Table 519 | // ======================================================== 520 | function HuffmanTable() { 521 | this._table = HUFFMAN_TABLE; 522 | this._tree = HUFFMAN_TABLE_TREE; 523 | } 524 | 525 | HuffmanTable.prototype.encode = function (buffer) { 526 | var result = []; 527 | var data = 0; 528 | var remains = 8; 529 | var shift; 530 | 531 | for (var i=0, len=buffer.length; i 0) { 536 | if (remains > bit) { 537 | shift = remains - bit; 538 | data += huffman.code << shift; 539 | remains -= bit; 540 | bit = 0; 541 | } else { 542 | shift = bit - remains; 543 | data += huffman.code >> shift; 544 | bit -= remains; 545 | remains -= remains; 546 | } 547 | 548 | if (remains === 0) { 549 | result.push(data); 550 | data = 0; 551 | remains = 8; 552 | } 553 | } 554 | } 555 | 556 | if (remains < 8) { 557 | shift = (this._table[256].bit - remains); 558 | data += this._table[256].code >> shift; 559 | result.push(data); 560 | } 561 | 562 | return new Buffer(result); 563 | }; 564 | 565 | HuffmanTable.prototype.decode = function (buffer) { 566 | var str = ''; 567 | var node = this._tree; 568 | 569 | for (var i=0, len=buffer.length; i=0; shift--) { 573 | if(((data >> shift) & 0x1) === 1) { 574 | node = node[1]; 575 | } else { 576 | node = node[0]; 577 | } 578 | 579 | if (typeof node === 'number') { 580 | str += String.fromCharCode(node); 581 | node = this._tree; 582 | } 583 | } 584 | } 585 | 586 | return str; 587 | }; 588 | 589 | 590 | // ======================================================== 591 | // HPACK Context 592 | // ======================================================== 593 | function Context(options) { 594 | this.huffman = (options.huffman === false) ? false : true; 595 | 596 | this._headerTable = new HeaderTable(options.headerTableSize); 597 | this._huffmanTable = new HuffmanTable(); 598 | 599 | this._updated = false; 600 | } 601 | 602 | Context.prototype.setHeaderTableSize = function (size) { 603 | this._headerTable.setMaxSize(size); 604 | this._updated = true; 605 | }; 606 | 607 | Context.prototype.compress = function (headers) { 608 | var idx, len; 609 | var buffers = []; 610 | 611 | if (this._updated) { 612 | buffers.push(this._encodeHeader({ 613 | type: REP_HEADER_TABLE_SIZE, 614 | size: this._headerTable.maxSize 615 | })); 616 | this._updated = false; 617 | } 618 | 619 | for (idx = 0, len = headers.length; idx < len; idx++) { 620 | var type, buffer; 621 | 622 | var name = headers[idx][0]; 623 | var value = headers[idx][1]; 624 | var result = this._headerTable.find(name, value); 625 | 626 | if (result.exact) { 627 | buffer = this._encodeHeader({ 628 | type: REP_INDEXED, 629 | index: result.index 630 | }); 631 | buffers.push(buffer); 632 | 633 | continue; 634 | } 635 | 636 | if (LITERAL_HEADERS[name]) { 637 | type = REP_LITERAL; 638 | } else { 639 | type = REP_LITERAL_INCR; 640 | 641 | var entry = new Entry(name, value); 642 | this._headerTable.add(entry); 643 | } 644 | 645 | if (result.index !== null) { 646 | buffer = this._encodeHeader({ 647 | type: type, 648 | index: result.index, 649 | value: value 650 | }); 651 | } else { 652 | buffer = this._encodeHeader({ 653 | type: type, 654 | name: name, 655 | value: value 656 | }); 657 | } 658 | 659 | buffers.push(buffer); 660 | } 661 | 662 | return Buffer.concat(buffers); 663 | }; 664 | 665 | Context.prototype.decompress = function (buffer) { 666 | var headers = []; 667 | buffer._cursor = 0; 668 | 669 | while (buffer._cursor < buffer.length) { 670 | var entry; 671 | var cmd = this._decodeHeader(buffer); 672 | 673 | if (cmd.type === REP_HEADER_TABLE_SIZE) { 674 | this._headerTable.setMaxSize(cmd.size); 675 | continue; 676 | } 677 | 678 | if (cmd.type === REP_INDEXED) { 679 | entry = this._headerTable.get(cmd.index); 680 | headers.push([entry.name, entry.value]); 681 | continue; 682 | } 683 | 684 | if (cmd.index && cmd.index !== 0) { 685 | entry = this._headerTable.get(cmd.index); 686 | cmd.name = entry.name; 687 | } 688 | 689 | if (cmd.type === REP_LITERAL_INCR) { 690 | entry = new Entry(cmd.name, cmd.value); 691 | this._headerTable.add(entry); 692 | } 693 | 694 | headers.push([cmd.name, cmd.value]); 695 | } 696 | 697 | return headers; 698 | }; 699 | 700 | Context.prototype._encodeHeader = function (cmd) { 701 | var index, buffers = []; 702 | var rep = REP_INFO[cmd.type]; 703 | 704 | if (cmd.type === REP_HEADER_TABLE_SIZE) { 705 | return this._encodeHeaderTableSizeUpdate(cmd.size); 706 | } 707 | 708 | index = cmd.index || 0; 709 | buffers.push(this._encodeInteger(index, rep.prefix)); 710 | buffers[0][0] |= rep.mask; 711 | 712 | if (cmd.type !== REP_INDEXED) { 713 | if (cmd.name) { 714 | buffers.push(this._encodeString(cmd.name)); 715 | } 716 | buffers.push(this._encodeString(cmd.value)); 717 | } 718 | 719 | return Buffer.concat(buffers); 720 | }; 721 | 722 | Context.prototype._encodeInteger = function (num, prefix) { 723 | var limit = Math.pow(2, prefix) - 1; 724 | 725 | if (num < limit) { 726 | return new Buffer([num]); 727 | } 728 | 729 | var octets = [limit]; 730 | num -= limit; 731 | while (num >= 128) { 732 | octets.push(num % 128 | 0x80); 733 | num >>= 7; 734 | } 735 | octets.push(num); 736 | 737 | return new Buffer(octets); 738 | }; 739 | 740 | Context.prototype._encodeString = function (str) { 741 | var buffers = []; 742 | var value = new Buffer(str, 'ascii'); 743 | 744 | if (this.huffman) { 745 | value = this._huffmanTable.encode(value); 746 | buffers.push(this._encodeInteger(value.length, 7)); 747 | buffers[0][0] |= 0x80; 748 | } else { 749 | buffers.push(this._encodeInteger(value.length, 7)); 750 | } 751 | buffers.push(value); 752 | 753 | return Buffer.concat(buffers); 754 | }; 755 | 756 | Context.prototype._encodeHeaderTableSizeUpdate = function (size) { 757 | var buffer; 758 | var rep = REP_INFO[REP_HEADER_TABLE_SIZE]; 759 | 760 | buffer = this._encodeInteger(size, rep.prefix); 761 | buffer[0] |= rep.mask; 762 | 763 | return buffer; 764 | }; 765 | 766 | Context.prototype._decodeHeader = function (buffer) { 767 | var cmd = {}; 768 | 769 | cmd.type = this._detectRepresentationType(buffer); 770 | var rep = REP_INFO[cmd.type]; 771 | 772 | if (cmd.type === REP_HEADER_TABLE_SIZE) { 773 | cmd.size = this._decodeHeaderTableSizeUpdate(buffer); 774 | return cmd; 775 | } 776 | 777 | var index = this._decodeInteger(buffer, rep.prefix); 778 | if (cmd.type === REP_INDEXED) { 779 | if (index !== 0) { 780 | cmd.index = index; 781 | } 782 | } else { 783 | if (index !== 0) { 784 | cmd.index = index; 785 | } else { 786 | cmd.name = this._decodeString(buffer); 787 | } 788 | 789 | cmd.value = this._decodeString(buffer); 790 | } 791 | 792 | return cmd; 793 | }; 794 | 795 | Context.prototype._detectRepresentationType = function (buffer) { 796 | var idx, len, rep = null; 797 | var bytes = buffer[buffer._cursor]; 798 | 799 | var order = [ 800 | REP_INDEXED, 801 | REP_LITERAL_INCR, 802 | REP_HEADER_TABLE_SIZE, 803 | REP_LITERAL_NEVER, 804 | REP_LITERAL 805 | ]; 806 | 807 | for (idx=0, len=order.length; idx= REP_INFO[current].mask) { 810 | rep = current; 811 | break; 812 | } 813 | } 814 | 815 | if (rep === null) { 816 | throw new Error('Unkown representation'); 817 | } 818 | 819 | return rep; 820 | }; 821 | 822 | Context.prototype._decodeInteger = function (buffer, prefix) { 823 | var limit = Math.pow(2, prefix) - 1; 824 | var shift = 0; 825 | var value = 0; 826 | 827 | value = buffer[buffer._cursor] & limit; 828 | buffer._cursor++; 829 | 830 | if (value === limit) { 831 | do { 832 | value += (buffer[buffer._cursor] & 0x7F) << shift; 833 | shift += 7; 834 | buffer._cursor++; 835 | } while (buffer[buffer._cursor-1] & 0x80); 836 | } 837 | 838 | return value; 839 | }; 840 | 841 | Context.prototype._decodeString = function (buffer) { 842 | var str = ''; 843 | var huffman = (buffer[buffer._cursor] & 0x80) ? true : false; 844 | var length = this._decodeInteger(buffer, 7); 845 | 846 | var value = buffer.slice(buffer._cursor, buffer._cursor+length); 847 | if (huffman) { 848 | str = this._huffmanTable.decode(value); 849 | } else { 850 | str = value.toString('ascii'); 851 | } 852 | buffer._cursor += length; 853 | 854 | return str; 855 | }; 856 | 857 | Context.prototype._decodeHeaderTableSizeUpdate = function (buffer) { 858 | var rep = REP_INFO[REP_HEADER_TABLE_SIZE]; 859 | var size = this._decodeInteger(buffer, rep.prefix); 860 | 861 | return size; 862 | }; 863 | 864 | 865 | // ======================================================== 866 | // Exports 867 | // ======================================================== 868 | exports.createContext = function createRequestContext(options) { 869 | options = options || {}; 870 | return new Context(options); 871 | }; 872 | -------------------------------------------------------------------------------- /lib/http.js: -------------------------------------------------------------------------------- 1 | var net = require('net'), 2 | tls = require('tls'), 3 | events = require('events'), 4 | util = require('util'), 5 | Readable = require('stream').Readable, 6 | Writable = require('stream').Writable; 7 | 8 | var Connection = require('./connection'), 9 | protocol = require('./protocol'); 10 | 11 | var TLS_CIPHERS = [ 12 | 'ECDHE-ECDSA-AES128-GCM-SHA256', 13 | 'ECDHE-RSA-AES256-GCM-SHA384', 14 | 'ECDHE-ECDSA-AES256-GCM-SHA384', 15 | 'DHE-RSA-AES128-GCM-SHA256', 16 | 'DHE-DSS-AES128-GCM-SHA256', 17 | 'ECDHE-RSA-AES128-SHA256', 18 | 'ECDHE-ECDSA-AES128-SHA256', 19 | 'ECDHE-RSA-AES128-SHA', 20 | 'ECDHE-ECDSA-AES128-SHA', 21 | 'ECDHE-RSA-AES256-SHA384', 22 | 'ECDHE-ECDSA-AES256-SHA384', 23 | 'ECDHE-RSA-AES256-SHA', 24 | 'ECDHE-ECDSA-AES256-SHA', 25 | 'DHE-RSA-AES128-SHA256', 26 | 'DHE-RSA-AES128-SHA', 27 | 'DHE-DSS-AES128-SHA256', 28 | 'DHE-RSA-AES256-SHA256', 29 | 'DHE-DSS-AES256-SHA', 30 | 'DHE-RSA-AES256-SHA', 31 | 'kEDH+AESGCM', 32 | 'AES128-GCM-SHA256', 33 | 'AES256-GCM-SHA384', 34 | 'ECDHE-RSA-RC4-SHA', 35 | 'ECDHE-ECDSA-RC4-SHA', 36 | '!aNULL', 37 | '!eNULL', 38 | '!EXPORT', 39 | '!DES', 40 | '!3DES', 41 | '!MD5', 42 | '!DSS', 43 | '!PSK' 44 | ]; 45 | 46 | // ============================================== 47 | // Common functions 48 | // ============================================== 49 | function array2hash (array) { 50 | var hash = {}; 51 | 52 | for (var idx=0, len=array.length; idx= 0) { 369 | this.headerTableSize = value; 370 | } 371 | }; 372 | 373 | Server.prototype.setMaxConcurrentStreams = function (value) { 374 | if (value && (typeof value === 'number') && value >= 0) { 375 | this.maxConcurrentStreams = value; 376 | } 377 | }; 378 | 379 | Server.prototype.setInitialWindowSize = function (value) { 380 | if (value && (typeof value === 'number') && value >= 0) { 381 | this.initialWindowSize = value; 382 | } 383 | }; 384 | 385 | Server.prototype._connectionHandler = function () { 386 | var self = this; 387 | 388 | return function (socket) { 389 | if (self.tls && socket.npnProtocol.indexOf(protocol.IDENTIFIER) === -1) { 390 | return socket.destroy(); 391 | } 392 | 393 | var options = { 394 | headerTableSize: self.headerTableSize, 395 | maxConcurrentStreams: self.maxConcurrentStreams, 396 | initialWindowSize: self.initialWindowSize 397 | }; 398 | 399 | var conn = new Connection(socket, options, true); 400 | conn.on('stream', self._streamHandler()); 401 | }; 402 | }; 403 | 404 | Server.prototype._streamHandler = function () { 405 | var self = this; 406 | 407 | return function (stream) { 408 | var conn = this; 409 | 410 | stream.on('header', function (streamHeaders) { 411 | var headers = array2hash(streamHeaders); 412 | 413 | var req = new IncomingMessage(conn, stream); 414 | req.method = headers[':method']; 415 | req.url = headers[':path']; 416 | req.authority = headers[':authority']; 417 | req.tls = (headers[':scheme'] === 'https'); 418 | req.headers = headers; 419 | 420 | var res = new ServerResponse(conn, stream); 421 | res.request = req; 422 | 423 | setImmediate(function(){ 424 | self.emit('request', req, res); 425 | }); 426 | }); 427 | }; 428 | }; 429 | 430 | 431 | exports.createServer = function (options, reqHandler) { 432 | return new Server(options, reqHandler); 433 | }; 434 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var http = require('./http'), 2 | hpack = require('./hpack'), 3 | protocol = require('./protocol'); 4 | 5 | exports.createServer = http.createServer; 6 | exports.hpack = hpack; 7 | exports.protocol = protocol; 8 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var protocol = require('./protocol'); 3 | 4 | var LOG_INDENT = (function () { 5 | var indent = ''; 6 | while (indent.length < 10) { 7 | indent += ' '; 8 | } 9 | return indent; 10 | })(); 11 | 12 | var colors = { 13 | green: function (text) { 14 | return '\u001b[32m' + text + '\u001b[39m'; 15 | }, 16 | yellow: function (text) { 17 | return '\u001b[33m' + text + '\u001b[39m'; 18 | }, 19 | cyan: function (text) { 20 | return '\u001b[36m' + text + '\u001b[39m'; 21 | }, 22 | brightRed: function (text) { 23 | return '\u001b[91m' + text + '\u001b[39m'; 24 | }, 25 | brightBlue: function (text) { 26 | return '\u001b[94m' + text + '\u001b[39m'; 27 | } 28 | }; 29 | 30 | var loggers = {}; 31 | var start = process.hrtime(); 32 | 33 | 34 | function Logger(component) { 35 | if (!this._enable(component)) { 36 | var dummy = function(){}; 37 | 38 | this.logFrame = dummy; 39 | this.log = dummy; 40 | } 41 | } 42 | 43 | Logger.prototype.logFrame = function (frame, sender) { 44 | var messages = []; 45 | 46 | var length = frame.length; 47 | var flags = frame._encodeFlags(); 48 | var streamId = frame.streamId; 49 | 50 | var label = sender ? 'Send' : 'Recv'; 51 | var frameTypeName = this._getFrameTypeName(frame.type); 52 | if (sender) { 53 | frameTypeName = colors.cyan(frameTypeName); 54 | } else { 55 | frameTypeName = colors.brightRed(frameTypeName); 56 | } 57 | 58 | var head = []; 59 | head.push(util.format('%s %s frame ', label, frameTypeName)); 60 | head.push(util.format('', length, flags.toString(16), streamId)); 61 | messages.push(head.join('')); 62 | 63 | var flagNames = this._getFlagNames(frame.type, flags); 64 | if (flagNames !== '') { 65 | messages.push(util.format('Flags: %s', flagNames)); 66 | } 67 | 68 | if ('padding' in frame && frame.padding !== null) { 69 | messages.push(util.format('Padding: %d', frame.padding)); 70 | } 71 | 72 | var payload = []; 73 | switch (frame.type) { 74 | case protocol.FRAME_TYPE_SETTINGS: 75 | payload = this._getSettingsPayload(frame); 76 | break; 77 | case protocol.FRAME_TYPE_GOAWAY: 78 | payload = this._getGoawayPayload(frame); 79 | break; 80 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 81 | payload = this._getWindowUpdatePayload(frame); 82 | break; 83 | } 84 | if (payload.length > 0) { 85 | messages = messages.concat(payload); 86 | } 87 | 88 | this.log(messages); 89 | }; 90 | 91 | Logger.prototype.log = function (messages) { 92 | if (typeof messages === 'string') { 93 | messages = [messages]; 94 | } 95 | 96 | var time = process.hrtime(start); 97 | var elapsedTime = colors.green(this._formatTime(time)); 98 | var header = util.format('[%s] ', elapsedTime); 99 | 100 | var logs = []; 101 | logs.push(header + messages[0]); 102 | for (var idx = 1, len = messages.length; idx < len; idx++) { 103 | logs.push(LOG_INDENT + messages[idx]); 104 | } 105 | 106 | console.log(logs.join('\n')); 107 | }; 108 | 109 | Logger.prototype._enable = function (component) { 110 | var result = false; 111 | var target = process.env.DEBUG; 112 | 113 | if (target && (target.indexOf(component) !== -1 || target === '*')) { 114 | result = true; 115 | } 116 | 117 | return result; 118 | }; 119 | 120 | Logger.prototype._formatTime = function (time) { 121 | var time1 = time[0].toString(); 122 | while (time1.length < 3) { 123 | time1 = ' ' + time1; 124 | } 125 | 126 | var time2 = Math.round(parseInt(time[1], 10) / 1000000).toString(); 127 | while (time2.length < 3) { 128 | time2 = '0' + time2; 129 | } 130 | 131 | return time1 + '.' + time2; 132 | }; 133 | 134 | Logger.prototype._getFrameTypeName = function (type) { 135 | var name = 'UNKNOWN'; 136 | 137 | switch (type) { 138 | case protocol.FRAME_TYPE_DATA: 139 | name = 'DATA'; 140 | break; 141 | case protocol.FRAME_TYPE_HEADERS: 142 | name = 'HEADERS'; 143 | break; 144 | case protocol.FRAME_TYPE_PRIORITY: 145 | name = 'PRIORITY'; 146 | break; 147 | case protocol.FRAME_TYPE_RST_STREAM: 148 | name = 'RST_STREAM'; 149 | break; 150 | case protocol.FRAME_TYPE_SETTINGS: 151 | name = 'SETTINGS'; 152 | break; 153 | case protocol.FRAME_TYPE_PUSH_PROMISE: 154 | name = 'PUSH_PROMISE'; 155 | break; 156 | case protocol.FRAME_TYPE_PING: 157 | name = 'PING'; 158 | break; 159 | case protocol.FRAME_TYPE_GOAWAY: 160 | name = 'GOAWAY'; 161 | break; 162 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 163 | name = 'WINDOW_UPDATE'; 164 | break; 165 | case protocol.FRAME_TYPE_CONTINUATION: 166 | name = 'CONTINUATION'; 167 | break; 168 | } 169 | 170 | return name; 171 | }; 172 | 173 | Logger.prototype._getFlagNames = function (type, flags) { 174 | var names = []; 175 | var candidates = {}; 176 | 177 | switch (type) { 178 | case protocol.FRAME_TYPE_DATA: 179 | candidates = { 180 | 'END_STREAM': protocol.FLAG_END_STREAM, 181 | 'PADDED': protocol.FLAG_PADDED 182 | }; 183 | break; 184 | case protocol.FRAME_TYPE_HEADERS: 185 | candidates = { 186 | 'END_STREAM': protocol.FLAG_END_STREAM, 187 | 'END_HEADERS': protocol.FLAG_END_HEADERS, 188 | 'PADDED': protocol.FLAG_PADDED, 189 | 'PRIORITY': protocol.FLAG_PRIORITY 190 | }; 191 | break; 192 | case protocol.FRAME_TYPE_SETTINGS: 193 | candidates = { 194 | 'ACK': protocol.FLAG_ACK 195 | }; 196 | break; 197 | case protocol.FRAME_TYPE_PUSH_PROMISE: 198 | candidates = { 199 | 'END_HEADERS': protocol.FLAG_END_HEADERS, 200 | 'PADDED': protocol.FLAG_PADDED 201 | }; 202 | break; 203 | case protocol.FRAME_TYPE_PING: 204 | candidates = { 205 | 'ACK': protocol.FLAG_ACK 206 | }; 207 | break; 208 | case protocol.FRAME_TYPE_CONTINUATION: 209 | candidates = { 210 | 'END_HEADERS': protocol.FLAG_END_HEADERS 211 | }; 212 | break; 213 | } 214 | 215 | for (var name in candidates) { 216 | if (flags & candidates[name]) { 217 | names.push(name); 218 | } 219 | } 220 | 221 | return names.join(' | '); 222 | }; 223 | 224 | Logger.prototype._getSettingsPayload = function (frame) { 225 | var messages = [ 'Settings:' ]; 226 | 227 | if (frame.ack) { 228 | return ''; 229 | } 230 | 231 | for (var param in frame._changed) { 232 | var name = 'UNKNOWN'; 233 | var value = frame[param]; 234 | 235 | switch (param) { 236 | case 'headerTableSize': 237 | name = 'SETTINGS_HEADER_TABLE_SIZE(0x1)'; 238 | break; 239 | case 'enablePush': 240 | name = 'SETTINGS_ENABLE_PUSH(0x2)'; 241 | value = (value === true) ? 1 : 0; 242 | break; 243 | case 'maxConcurrentStreams': 244 | name = 'SETTINGS_MAX_CONCURRENT_STREAMS(0x3)'; 245 | break; 246 | case 'initialWindowSize': 247 | name = 'SETTINGS_INITIAL_WINDOW_SIZE(0x4)'; 248 | break; 249 | case 'maxFrameSize': 250 | name = 'SETTINGS_MAX_FRAME_SIZE(0x5)'; 251 | break; 252 | case 'maxHeaderListSize': 253 | name = 'SETTINGS_MAX_HEADER_LIST_SIZE(0x6)'; 254 | break; 255 | } 256 | 257 | messages.push(util.format(' - %s: %s', name, value)); 258 | } 259 | 260 | return messages; 261 | }; 262 | 263 | Logger.prototype._getGoawayPayload = function (frame) { 264 | var messages = []; 265 | 266 | messages.push(util.format('Last Stream ID: %d', frame.lastStreamId)); 267 | messages.push(util.format('Error Code: %s', frame.errorCode)); 268 | if (frame.debugData) { 269 | messages.push(util.format('Debug Data: %s', frame.debugData)); 270 | } 271 | 272 | return messages; 273 | }; 274 | 275 | Logger.prototype._getWindowUpdatePayload = function (frame) { 276 | return [ util.format('Increment: %d', frame.windowSizeIncrement) ]; 277 | }; 278 | 279 | module.exports = function (component) { 280 | if (!loggers[component]) { 281 | loggers[component] = new Logger(component); 282 | } 283 | 284 | return loggers[component]; 285 | }; 286 | -------------------------------------------------------------------------------- /lib/protocol.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | IDENTIFIER: 'h2-14', 3 | 4 | CONNECTION_PREFACE: 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n', 5 | CONNECTION_PREFACE_LEN: 24, 6 | 7 | FRAME_HEADER_LEN: 9, 8 | FRAME_LEN_MAX: 16383, 9 | 10 | FRAME_TYPE_DATA: 0x0, 11 | FRAME_TYPE_HEADERS: 0x1, 12 | FRAME_TYPE_PRIORITY: 0x2, 13 | FRAME_TYPE_RST_STREAM: 0x3, 14 | FRAME_TYPE_SETTINGS: 0x4, 15 | FRAME_TYPE_PUSH_PROMISE: 0x5, 16 | FRAME_TYPE_PING: 0x6, 17 | FRAME_TYPE_GOAWAY: 0x7, 18 | FRAME_TYPE_WINDOW_UPDATE: 0x8, 19 | FRAME_TYPE_CONTINUATION: 0x9, 20 | FRAME_TYPE_ALTSVC: 0xa, 21 | FRAME_TYPE_BLOCKED: 0xd, 22 | 23 | FLAG_NONE: 0x0, 24 | FLAG_END_STREAM: 0x1, 25 | FLAG_END_SEGMENT: 0x2, 26 | FLAG_PADDED: 0x8, 27 | FLAG_RESERVED: 0x2, 28 | FLAG_END_HEADERS: 0x4, 29 | FLAG_PRIORITY: 0x20, 30 | FLAG_ACK: 0x1, 31 | 32 | SETTINGS_HEADER_TABLE_SIZE: 1, 33 | SETTINGS_ENABLE_PUSH: 2, 34 | SETTINGS_MAX_CONCURRENT_STREAMS: 3, 35 | SETTINGS_INITIAL_WINDOW_SIZE: 4, 36 | 37 | DEFAULT_HEADER_TABLE_SIZE: 4096, 38 | DEFAULT_ENABLE_PUSH: true, 39 | DEFAULT_MAX_CONCURRENT_STREAMS: Infinity, 40 | DEFAULT_INITIAL_WINDOW_SIZE: 65535, 41 | 42 | STATE_IDLE: 0, 43 | STATE_RESERVED_LOCAL: 1, 44 | STATE_RESERVED_REMOTE: 2, 45 | STATE_OPEN: 3, 46 | STATE_HALF_CLOSED_LOCAL: 4, 47 | STATE_HALF_CLOSED_REMOTE: 5, 48 | STATE_CLOSED: 6, 49 | 50 | CODE_NO_ERROR: 0, 51 | CODE_PROTOCOL_ERROR: 1, 52 | CODE_INTERNAL_ERROR: 2, 53 | CODE_FLOW_CONTROL_ERROR: 3, 54 | CODE_SETTINGS_TIMEOUT: 4, 55 | CODE_STREAM_CLOSED: 5, 56 | CODE_FRAME_SIZE_ERROR: 6, 57 | CODE_REFUSED_STREAM: 7, 58 | CODE_CANCEL: 8, 59 | CODE_COMPRESSION_ERROR: 9, 60 | CODE_CONNECT_ERROR: 10, 61 | CODE_ENHANCE_YOUR_CALM: 11, 62 | CODE_INADEQUATE_SECURITY: 12, 63 | 64 | INITIAL_WINDOW_SIZE: 65535 65 | }; 66 | -------------------------------------------------------------------------------- /lib/serializer.js: -------------------------------------------------------------------------------- 1 | var stream = require('stream'), 2 | util = require('util'); 3 | 4 | var logger = require('./logger')('serializer'); 5 | 6 | function Serializer (options) { 7 | options = options || {}; 8 | options.objectMode = true; 9 | stream.Transform.call(this, options); 10 | } 11 | 12 | util.inherits(Serializer, stream.Transform); 13 | 14 | Serializer.prototype._transform = function (frame, encoding, callback) { 15 | logger.logFrame(frame, true); 16 | 17 | var buffer = frame.encode(frame); 18 | this.push(buffer); 19 | 20 | callback(); 21 | }; 22 | 23 | module.exports = Serializer; 24 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var FlowController = require('./flow_controller'), 4 | framer = require('./framer'), 5 | protocol = require('./protocol'); 6 | 7 | var STREAM_CLOSING_TIMEOUT = 1 * 1000; // 1s 8 | 9 | 10 | function Stream(options) { 11 | FlowController.call(this, options.initialWindowSize); 12 | 13 | this.id = options.id; 14 | 15 | this.halfClosed = false; 16 | this.closed = false; 17 | 18 | this._continue = false; 19 | this._headerBlockFragments = []; 20 | 21 | this._compressor = options.compressor; 22 | this._decompressor = options.decompressor; 23 | 24 | this.state = protocol.STATE_IDLE; 25 | if (options.promised === true) { 26 | this._setState(protocol.STATE_RESERVED_LOCAL); 27 | } 28 | } 29 | 30 | util.inherits(Stream, FlowController); 31 | 32 | Stream.prototype.setPrioroty = function(streamDependency, weight, exclusive) { 33 | this.sendPriorityFrame(streamDependency, weight, exclusive); 34 | }; 35 | 36 | Stream.prototype.cancel = function() { 37 | this.sendRstStreamFrame(protocol.CODE_CANCEL); 38 | }; 39 | 40 | Stream.prototype.sendDataFrame = function(data, options) { 41 | if (!options) { 42 | options = {}; 43 | } 44 | 45 | var frame = framer.createDataFrame(); 46 | frame.streamId = this.id; 47 | frame.endStream = (options.endStream === true); 48 | frame.endSegment = (options.endSegment === true); 49 | frame.setData(data); 50 | 51 | if (options.hasOwnProperty('padding')) { 52 | frame.setPadding(options.padding); 53 | } 54 | 55 | this.push(frame); 56 | }; 57 | 58 | Stream.prototype.sendHeadersFrame = function(headers, options) { 59 | if (!options) { 60 | options = {}; 61 | } 62 | 63 | var frame = framer.createHeadersFrame(); 64 | frame.streamId = this.id; 65 | frame.endStream = (options.endStream === true); 66 | frame.endHeaders = (options.endHeaders === true); 67 | frame.setHeaders(headers); 68 | 69 | var headerBlocks = this._compressor.compress(headers); 70 | frame.setHeaderBlockFragment(headerBlocks); 71 | 72 | if (options.hasOwnProperty('padding')) { 73 | frame.setPadding(options.padding); 74 | } 75 | 76 | var frames = this._splitHeadersFrame(frame); 77 | for (var fi = 0, flen = frames.length; fi < flen; fi++) { 78 | this.push(frames[fi]); 79 | } 80 | }; 81 | 82 | Stream.prototype.sendPriorityFrame = function(streamDependency, weight, exclusive) { 83 | var frame = framer.createPriorityFrame(); 84 | frame.streamId = this.id; 85 | 86 | if (streamDependency && weight) { 87 | frame.setPriority(streamDependency, weight, (exclusive === true)); 88 | } 89 | 90 | this.push(frame); 91 | }; 92 | 93 | Stream.prototype.sendRstStreamFrame = function(errorCode) { 94 | if (!errorCode) { 95 | errorCode = protocol.CODE_NO_ERROR; 96 | } 97 | 98 | var frame = framer.createRstStreamFrame(); 99 | frame.streamId = this.id; 100 | frame.setErrorCode(errorCode); 101 | 102 | this.push(frame); 103 | }; 104 | 105 | Stream.prototype.sendPushPromiseFrame = function(promisedStreamId, headers, options) { 106 | if (!options) { 107 | options = {}; 108 | } 109 | 110 | var frame = framer.createPushPromiseFrame(); 111 | frame.streamId = this.id; 112 | frame.endHeaders = (options.endHeaders === true); 113 | frame.setPromisedStreamId(promisedStreamId); 114 | frame.setHeaders(headers); 115 | 116 | var headerBlocks = this._compressor.compress(headers); 117 | frame.setHeaderBlockFragment(headerBlocks); 118 | 119 | if (options.hasOwnProperty('padding')) { 120 | frame.setPadding(options.padding); 121 | } 122 | 123 | var frames = this._splitHeadersFrame(frame); 124 | for (var fi = 0, flen = frames.length; fi < flen; fi++) { 125 | this.push(frames[fi]); 126 | } 127 | }; 128 | 129 | Stream.prototype._splitHeadersFrame = function(frame) { 130 | if (frame.length <= protocol.FRAME_LEN_MAX) { 131 | return [frame]; 132 | } 133 | 134 | var frames = []; 135 | var count = 0; 136 | var headerBlock = frame.headerBlockFragment; 137 | 138 | var last = protocol.FRAME_LEN_MAX - (frame.length - headerBlock.length); 139 | var fragment = headerBlock.slice(0, last); 140 | headerBlock = headerBlock.slice(last); 141 | 142 | frame.setHeaderBlockFragment(fragment); 143 | frame.endHeaders = false; 144 | frames.push(frame); 145 | count++; 146 | 147 | while (headerBlock.length > 0) { 148 | var contFrame = framer.createContinuationFrame(); 149 | 150 | last = protocol.FRAME_LEN_MAX - contFrame.length; 151 | if (last > headerBlock.length) { 152 | last = headerBlock.length; 153 | } 154 | 155 | fragment = headerBlock.slice(0, last); 156 | headerBlock = headerBlock.slice(last); 157 | 158 | contFrame.setHeaderBlockFragment(fragment); 159 | frames.push(contFrame); 160 | count++; 161 | } 162 | 163 | frames[count - 1].endHeaders = true; 164 | 165 | return frames; 166 | }; 167 | 168 | Stream.prototype._send = function(frame) { 169 | var self = this; 170 | 171 | this._transitions(frame, true); 172 | self.emit('send', frame); 173 | }; 174 | 175 | Stream.prototype.process = function(frame) { 176 | var code = protocol.CODE_NO_ERROR; 177 | 178 | if (this._continue && frame.type !== protocol.FRAME_TYPE_CONTINUATION) { 179 | return this._error(protocol.CODE_PROTOCOL_ERROR, true); 180 | } 181 | 182 | switch (frame.type) { 183 | case protocol.FRAME_TYPE_DATA: 184 | code = this._processDataFrame(frame); 185 | break; 186 | case protocol.FRAME_TYPE_HEADERS: 187 | code = this._processHeadersFrame(frame); 188 | break; 189 | case protocol.FRAME_TYPE_PRIORITY: 190 | code = this._processPriorityFrame(frame); 191 | break; 192 | case protocol.FRAME_TYPE_RST_STREAM: 193 | code = this._processRstStreamFrame(frame); 194 | break; 195 | case protocol.FRAME_TYPE_PUSH_PROMISE: 196 | code = this._processPushPromiseFrame(frame); 197 | break; 198 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 199 | code = this._processWindowUpdateFrame(frame); 200 | break; 201 | case protocol.FRAME_TYPE_CONTINUATION: 202 | code = this._processContinuationFrame(frame); 203 | break; 204 | default: 205 | // Ignore unknown frames 206 | return code; 207 | } 208 | 209 | if (code !== protocol.CODE_NO_ERROR) { 210 | return this._error(code); 211 | } 212 | 213 | this._transitions(frame); 214 | }; 215 | 216 | Stream.prototype._processDataFrame = function(frame) { 217 | var self = this; 218 | setImmediate(function(){ 219 | self.emit('data', frame.data); 220 | }); 221 | 222 | return protocol.CODE_NO_ERROR; 223 | }; 224 | 225 | Stream.prototype._processHeadersFrame = function(frame) { 226 | if (!frame.endHeaders) { 227 | this._continue = true; 228 | this._headerBlockFragments.push(frame.headerBlockFragment); 229 | 230 | return protocol.CODE_NO_ERROR; 231 | } 232 | 233 | var headers = this._decompressor.decompress(frame.headerBlockFragment); 234 | 235 | var self = this; 236 | setImmediate(function(){ 237 | self.emit('header', headers); 238 | }); 239 | 240 | return protocol.CODE_NO_ERROR; 241 | }; 242 | 243 | Stream.prototype._processPriorityFrame = function(frame) { 244 | var self = this; 245 | setImmediate(function(){ 246 | self.emit('priority', frame.streamDependency, frame.weight, frame.exclusive); 247 | }); 248 | 249 | return protocol.CODE_NO_ERROR; 250 | }; 251 | 252 | Stream.prototype._processRstStreamFrame = function(frame) { 253 | if (frame.errorCode === protocol.CODE_CANCEL) { 254 | var self = this; 255 | process.nextTick(function() { 256 | self.emit('cancel'); 257 | }); 258 | 259 | return protocol.CODE_NO_ERROR; 260 | } 261 | 262 | return frame.errorCode; 263 | }; 264 | 265 | Stream.prototype._processPushPromiseFrame = function(frame) { 266 | return protocol.CODE_NO_ERROR; 267 | }; 268 | 269 | Stream.prototype._processWindowUpdateFrame = function(frame) { 270 | this.increaseWindowSize(frame.windowSizeIncrement); 271 | return protocol.CODE_NO_ERROR; 272 | }; 273 | 274 | Stream.prototype._processContinuationFrame = function(frame) { 275 | if (!this._continue) { 276 | return protocol.CODE_PROTOCOL_ERROR; 277 | } 278 | 279 | this._headerBlockFragments.push(frame.headerBlockFragment); 280 | 281 | if (frame.endHeaders) { 282 | var headerBlock = Buffer.concat(this._headerBlockFragments); 283 | var headers = this._decompressor.decompress(headerBlock); 284 | 285 | this._continue = false; 286 | this._headerBlockFragments = []; 287 | 288 | var self = this; 289 | setImmediate(function(){ 290 | self.emit('header', headers); 291 | }); 292 | } 293 | 294 | return protocol.CODE_NO_ERROR; 295 | }; 296 | 297 | Stream.prototype._transitions = function(frame, send) { 298 | var self = this; 299 | var next = null; 300 | 301 | send = (send === true); 302 | 303 | switch (this.state) { 304 | case protocol.STATE_IDLE: 305 | next = this._transitionsFromIdle(frame, send); 306 | break; 307 | case protocol.STATE_RESERVED_LOCAL: 308 | next = this._transitionsFromReservedLocal(frame, send); 309 | break; 310 | case protocol.STATE_RESERVED_REMOTE: 311 | next = this._transitionsFromReservedRemote(frame, send); 312 | break; 313 | case protocol.STATE_OPEN: 314 | next = this._transitionsFromOpen(frame, send); 315 | break; 316 | case protocol.STATE_HALF_CLOSED_LOCAL: 317 | next = this._transitionsFromHalfClosedLocal(frame, send); 318 | break; 319 | case protocol.STATE_HALF_CLOSED_REMOTE: 320 | next = this._transitionsFromHalfClosedRemote(frame, send); 321 | break; 322 | case protocol.STATE_CLOSED: 323 | next = this._transitionsFromClosed(frame, send); 324 | break; 325 | default: 326 | throw new Error('Unknown stream state'); 327 | } 328 | 329 | if (next === null) { 330 | return; 331 | } 332 | 333 | if (this.state !== next) { 334 | this._setState(next); 335 | } 336 | 337 | if (this.state < protocol.STATE_OPEN && protocol.STATE_OPEN <= next) { 338 | process.nextTick(function() { 339 | self.emit('active'); 340 | }); 341 | } 342 | 343 | if (frame.endStream === true) { 344 | process.nextTick(function() { 345 | self.emit('end'); 346 | }); 347 | 348 | if (this.state === protocol.STATE_OPEN) { 349 | this._transitions(frame, send); 350 | return; 351 | } 352 | } 353 | 354 | if (this.state === protocol.STATE_CLOSED) { 355 | this._close(); 356 | } 357 | }; 358 | 359 | Stream.prototype._transitionsFromIdle = function(frame, send) { 360 | var next = protocol.STATE_IDLE; 361 | 362 | switch (frame.type) { 363 | case protocol.FRAME_TYPE_HEADERS: 364 | next = protocol.STATE_OPEN; 365 | break; 366 | case protocol.FRAME_TYPE_PUSH_PROMISE: 367 | if (send) { 368 | throw new Error('Implemntation error'); 369 | } else { 370 | next = protocol.STATE_RESERVED_REMOTE; 371 | } 372 | break; 373 | default: 374 | if (send) { 375 | throw new Error('Implemntation error'); 376 | } else { 377 | this._error(protocol.CODE_PROTOCOL_ERROR, true); 378 | next = null; 379 | } 380 | } 381 | 382 | return next; 383 | }; 384 | 385 | Stream.prototype._transitionsFromReservedLocal = function(frame, send) { 386 | var next = protocol.STATE_RESERVED_LOCAL; 387 | 388 | if (send) { 389 | switch (frame.type) { 390 | case protocol.FRAME_TYPE_HEADERS: 391 | next = protocol.STATE_HALF_CLOSED_REMOTE; 392 | break; 393 | case protocol.FRAME_TYPE_RST_STREAM: 394 | next = protocol.STATE_CLOSED; 395 | break; 396 | default: 397 | throw new Error('Implementation error'); 398 | } 399 | } else { 400 | switch (frame.type) { 401 | case protocol.FRAME_TYPE_PRIORITY: 402 | // Nothing to do. 403 | break; 404 | case protocol.FRAME_TYPE_RST_STREAM: 405 | next = protocol.STATE_CLOSED; 406 | break; 407 | default: 408 | this._error(protocol.CODE_PROTOCOL_ERROR, true); 409 | next = null; 410 | } 411 | } 412 | 413 | return next; 414 | }; 415 | 416 | Stream.prototype._transitionsFromReservedRemote = function(frame, send) { 417 | var next = protocol.STATE_RESERVED_REMOTE; 418 | 419 | if (send) { 420 | switch (frame.type) { 421 | case protocol.FRAME_TYPE_PRIORITY: 422 | // Nothing to do. 423 | break; 424 | case protocol.FRAME_TYPE_RST_STREAM: 425 | next = protocol.STATE_CLOSED; 426 | break; 427 | default: 428 | throw new Error('Implementation error'); 429 | } 430 | } else { 431 | switch (frame.type) { 432 | case protocol.FRAME_TYPE_HEADERS: 433 | next = protocol.STATE_HALF_CLOSED_LOCAL; 434 | break; 435 | case protocol.FRAME_TYPE_RST_STREAM: 436 | next = protocol.STATE_CLOSED; 437 | break; 438 | default: 439 | this._error(protocol.CODE_PROTOCOL_ERROR, true); 440 | next = null; 441 | } 442 | } 443 | 444 | return next; 445 | }; 446 | 447 | Stream.prototype._transitionsFromOpen = function(frame, send) { 448 | var next = protocol.STATE_OPEN; 449 | 450 | switch (frame.type) { 451 | case protocol.FRAME_TYPE_DATA: 452 | case protocol.FRAME_TYPE_HEADERS: 453 | if (frame.endStream) { 454 | next = send ? protocol.STATE_HALF_CLOSED_LOCAL : protocol.STATE_HALF_CLOSED_REMOTE; 455 | } 456 | break; 457 | case protocol.FRAME_TYPE_RST_STREAM: 458 | next = protocol.STATE_CLOSED; 459 | break; 460 | } 461 | 462 | return next; 463 | }; 464 | 465 | Stream.prototype._transitionsFromHalfClosedLocal = function(frame, send) { 466 | var next = protocol.STATE_HALF_CLOSED_LOCAL; 467 | 468 | if (send) { 469 | switch (frame.type) { 470 | case protocol.FRAME_TYPE_RST_STREAM: 471 | next = protocol.STATE_CLOSED; 472 | break; 473 | case protocol.FRAME_TYPE_CONTINUATION: 474 | // Nothing to do. 475 | break; 476 | default: 477 | throw new Error('Implementation error'); 478 | } 479 | } else { 480 | switch (frame.type) { 481 | case protocol.FRAME_TYPE_DATA: 482 | case protocol.FRAME_TYPE_HEADERS: 483 | if (frame.endStream) { 484 | next = protocol.STATE_CLOSED; 485 | } 486 | break; 487 | case protocol.FRAME_TYPE_RST_STREAM: 488 | next = protocol.STATE_CLOSED; 489 | break; 490 | } 491 | } 492 | 493 | return next; 494 | }; 495 | 496 | Stream.prototype._transitionsFromHalfClosedRemote = function(frame, send) { 497 | var next = protocol.STATE_HALF_CLOSED_REMOTE; 498 | 499 | if (send) { 500 | switch (frame.type) { 501 | case protocol.FRAME_TYPE_DATA: 502 | case protocol.FRAME_TYPE_HEADERS: 503 | if (frame.endStream) { 504 | next = protocol.STATE_CLOSED; 505 | } 506 | break; 507 | case protocol.FRAME_TYPE_RST_STREAM: 508 | next = protocol.STATE_CLOSED; 509 | break; 510 | } 511 | } else { 512 | switch (frame.type) { 513 | case protocol.FRAME_TYPE_RST_STREAM: 514 | next = protocol.STATE_CLOSED; 515 | break; 516 | case protocol.FRAME_TYPE_WINDOW_UPDATE: 517 | case protocol.FRAME_TYPE_CONTINUATION: 518 | // Nothing to do. 519 | break; 520 | default: 521 | var code = protocol.CODE_STREAM_CLOSED; 522 | this.sendRstStreamFrame(code); 523 | this._error(code); 524 | next = null; 525 | } 526 | } 527 | 528 | return next; 529 | }; 530 | 531 | Stream.prototype._transitionsFromClosed = function(frame, send) { 532 | var next = protocol.STATE_CLOSED; 533 | 534 | if (send) { 535 | switch (frame.type) { 536 | case protocol.FRAME_TYPE_RST_STREAM: 537 | case protocol.FRAME_TYPE_PRIORITY: 538 | // Nothing to do. 539 | break; 540 | default: 541 | throw new Error('Implementation error'); 542 | } 543 | } else { 544 | if (this.closed && frame.type !== protocol.FRAME_TYPE_PRIORITY) { 545 | var code = protocol.CODE_STREAM_CLOSED; 546 | this.sendRstStreamFrame(code); 547 | this._error(code); 548 | next = null; 549 | } 550 | } 551 | 552 | return next; 553 | }; 554 | 555 | Stream.prototype._setState = function(state) { 556 | this.state = state; 557 | this.emit('state', state); 558 | }; 559 | 560 | Stream.prototype._close = function(hadError) { 561 | var self = this; 562 | 563 | setTimeout(function() { 564 | self.closed = true; 565 | }, STREAM_CLOSING_TIMEOUT); 566 | 567 | this.emit('close', hadError); 568 | }; 569 | 570 | Stream.prototype._error = function(errorCode, connectionError) { 571 | var self = this; 572 | 573 | this._setState(protocol.STATE_CLOSED); 574 | 575 | self.emit('error', errorCode, connectionError); 576 | 577 | process.nextTick(function() { 578 | self._close(true); 579 | }); 580 | }; 581 | 582 | 583 | module.exports = Stream; 584 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sasazka", 3 | "version": "0.1.0", 4 | "description": "Yet another HTTP/2 implementation for Node.js", 5 | "main": "lib/index", 6 | "scripts": { 7 | "test": "istanbul cover _mocha -- -R spec", 8 | "lint": "eslint -c .eslintrc lib" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/summerwind/sasazka.git" 13 | }, 14 | "homepage": "https://github.com/summerwind/sasazka", 15 | "keywords": [ 16 | "http2", "hpack" 17 | ], 18 | "author": "Moto Ishizawa ", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "expect.js": "^0.2.0", 22 | "mocha": "^1.14.0", 23 | "istanbul": "^0.2.10", 24 | "waitress": "^0.1.5", 25 | "eslint": "^0.7.4" 26 | }, 27 | "dependencies": { 28 | }, 29 | "engines": { 30 | "node" : ">=0.10.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/connection_test.js: -------------------------------------------------------------------------------- 1 | var events = require('events'), 2 | net = require('net'); 3 | 4 | var expect = require('expect.js'); 5 | 6 | var Connection = require('../lib/connection'), 7 | framer = require('../lib/framer'), 8 | protocol = require('../lib/protocol'); 9 | 10 | var SERVER_PORT = 8888; 11 | 12 | describe('Connection', function () { 13 | var server; 14 | 15 | before(function (done) { 16 | server = net.createServer(); 17 | server.listen(SERVER_PORT, function () { 18 | done(); 19 | }); 20 | }); 21 | 22 | after(function () { 23 | server.close(); 24 | }); 25 | 26 | var createConnection = function (server) { 27 | var socket = net.createConnection(SERVER_PORT); 28 | var conn = new Connection(socket, {}, (server === true)); 29 | conn._setupPipeline(); 30 | 31 | return conn; 32 | }; 33 | 34 | describe('createStream()', function () { 35 | it('returns stream object', function () { 36 | var conn = createConnection(true); 37 | var stream = conn.createStream(); 38 | expect(stream.id).to.be(2); 39 | }); 40 | 41 | it('emits stream event', function (done) { 42 | var stream = null; 43 | 44 | var conn = createConnection(true); 45 | conn.on('stream', function (newStream) { 46 | expect(newStream).to.eql(stream); 47 | done(); 48 | }); 49 | 50 | stream = conn.createStream(); 51 | }); 52 | 53 | context('stream ID already used', function () { 54 | it('emits stream refused error', function (done) { 55 | var conn = createConnection(true); 56 | conn.on('error', function (err) { 57 | expect(err.code).to.eql(protocol.CODE_PROTOCOL_ERROR); 58 | done(); 59 | }); 60 | 61 | var stream = conn.createStream(); 62 | conn.createStream(stream.id); 63 | }); 64 | }); 65 | 66 | describe('Promised Stream', function () { 67 | it('returns promised stream', function () { 68 | var conn = createConnection(true); 69 | stream = conn.createStream(true); 70 | expect(stream.state).to.be(protocol.STATE_RESERVED_LOCAL); 71 | }); 72 | 73 | context('enable push settings is disabled', function () { 74 | it('returns null', function () { 75 | var conn = createConnection(true); 76 | conn._remoteSettings.enablePush = false; 77 | 78 | var stream = conn.createStream(true); 79 | expect(stream).to.be(null); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('ping()', function () { 86 | it('sends PING', function (done) { 87 | var conn = createConnection(true); 88 | 89 | conn._serializer.write = function (frame) { 90 | expect(frame.type).to.be(protocol.FRAME_TYPE_PING); 91 | expect(frame.ack).to.be(false); 92 | done(); 93 | }; 94 | 95 | conn.ping(); 96 | }); 97 | }); 98 | 99 | describe('destroy()', function () { 100 | it('sends GOAWAY', function (done) { 101 | var conn = createConnection(true); 102 | 103 | conn._serializer.write = function (frame) { 104 | expect(frame.type).to.be(protocol.FRAME_TYPE_GOAWAY); 105 | expect(frame.errorCode).to.be(protocol.CODE_NO_ERROR); 106 | done(); 107 | }; 108 | 109 | conn.destroy(); 110 | }); 111 | }); 112 | 113 | describe('sendSettingsFrame()', function () { 114 | it('sends SETTINGS', function (done) { 115 | var conn = createConnection(true); 116 | 117 | var settings = { 118 | headerTableSize: 2048, 119 | initialWindowSize: 100, 120 | invalidSettingName: true 121 | }; 122 | 123 | conn._serializer.write = function (frame) { 124 | expect(frame.type).to.be(protocol.FRAME_TYPE_SETTINGS); 125 | expect(frame.headerTableSize).to.be(settings.headerTableSize); 126 | expect(frame.initialWindowSize).to.be(settings.initialWindowSize); 127 | expect(conn._pendingSettings[0]).to.be(settings); 128 | done(); 129 | }; 130 | 131 | conn.sendSettingsFrame(settings); 132 | }); 133 | 134 | it('sends SETTINGS with ACK', function (done) { 135 | var conn = createConnection(true); 136 | 137 | conn._serializer.write = function (frame) { 138 | expect(frame.type).to.be(protocol.FRAME_TYPE_SETTINGS); 139 | expect(frame.ack).to.be(true); 140 | done(); 141 | }; 142 | 143 | conn.sendSettingsFrame(true); 144 | }); 145 | }); 146 | 147 | describe('sendGoawayFrame()', function () { 148 | it('sends GOAWAY', function (done) { 149 | var conn = createConnection(true); 150 | 151 | conn._serializer.write = function (frame) { 152 | expect(frame.type).to.be(protocol.FRAME_TYPE_GOAWAY); 153 | expect(frame.errorCode).to.be(protocol.CODE_NO_ERROR); 154 | done(); 155 | }; 156 | 157 | conn.sendGoawayFrame(); 158 | }); 159 | 160 | it('sends GOAWAY with error code', function (done) { 161 | var conn = createConnection(true); 162 | 163 | var errorCode = protocol.CODE_PROTOCOL_ERROR; 164 | 165 | conn._serializer.write = function (frame) { 166 | expect(frame.type).to.be(protocol.FRAME_TYPE_GOAWAY); 167 | expect(frame.errorCode).to.be(errorCode); 168 | done(); 169 | }; 170 | 171 | conn.sendGoawayFrame(errorCode); 172 | }); 173 | }); 174 | 175 | describe('sendPingFrame()', function () { 176 | it('sends PING', function (done) { 177 | var conn = createConnection(true); 178 | 179 | conn._serializer.write = function (frame) { 180 | expect(frame.type).to.be(protocol.FRAME_TYPE_PING); 181 | done(); 182 | }; 183 | 184 | conn.sendPingFrame(); 185 | }); 186 | 187 | it('sends PING with opaque data', function (done) { 188 | var conn = createConnection(true); 189 | 190 | var opaqueData = 'test'; 191 | var encodedOpaqueData = new Buffer([ 192 | 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00 193 | ]); 194 | 195 | conn._serializer.write = function (frame) { 196 | expect(frame.type).to.be(protocol.FRAME_TYPE_PING); 197 | expect(frame.opaqueData).to.eql(encodedOpaqueData); 198 | done(); 199 | }; 200 | 201 | conn.sendPingFrame(opaqueData); 202 | }); 203 | }); 204 | 205 | describe('Frame Processing', function () { 206 | context('receiving DATA', function () { 207 | it('emits error event', function (done) { 208 | var conn = createConnection(true); 209 | 210 | var frame = framer.createDataFrame(); 211 | frame.streamId = 0; 212 | 213 | conn.once('error', function (err) { 214 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 215 | done(); 216 | }); 217 | 218 | conn._deserializer.emit('data', frame); 219 | }); 220 | }); 221 | 222 | context('receiving HEADERS', function () { 223 | it('emits error event', function (done) { 224 | var conn = createConnection(true); 225 | 226 | var frame = framer.createHeadersFrame(); 227 | frame.streamId = 0; 228 | 229 | conn.once('error', function (err) { 230 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 231 | done(); 232 | }); 233 | 234 | conn._deserializer.emit('data', frame); 235 | }); 236 | }); 237 | 238 | context('receiving RST_STREAM', function () { 239 | it('emits error event', function (done) { 240 | var conn = createConnection(true); 241 | 242 | var frame = framer.createRstStreamFrame(); 243 | frame.streamId = 0; 244 | 245 | conn.once('error', function (err) { 246 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 247 | done(); 248 | }); 249 | 250 | conn._deserializer.emit('data', frame); 251 | }); 252 | }); 253 | 254 | context('receiving PUSH_PROMISE', function () { 255 | it('emits error event', function (done) { 256 | var conn = createConnection(true); 257 | 258 | var frame = framer.createPushPromiseFrame(); 259 | frame.streamId = 0; 260 | 261 | conn.once('error', function (err) { 262 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 263 | done(); 264 | }); 265 | 266 | conn._deserializer.emit('data', frame); 267 | }); 268 | }); 269 | 270 | context('receiving CONTINUATION', function () { 271 | it('emits error event', function (done) { 272 | var conn = createConnection(true); 273 | 274 | var frame = framer.createContinuationFrame(); 275 | frame.streamId = 0; 276 | 277 | conn.once('error', function (err) { 278 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 279 | done(); 280 | }); 281 | 282 | conn._deserializer.emit('data', frame); 283 | }); 284 | }); 285 | 286 | context('receiving PRIORITY', function () { 287 | it('emits error event', function (done) { 288 | var conn = createConnection(true); 289 | 290 | var frame = framer.createPriorityFrame(); 291 | frame.streamId = 0; 292 | 293 | conn.once('error', function (err) { 294 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 295 | done(); 296 | }); 297 | 298 | conn._deserializer.emit('data', frame); 299 | }); 300 | }); 301 | 302 | context('receiving GOAWAY', function () { 303 | it('emits error event', function (done) { 304 | var conn = createConnection(true); 305 | 306 | var frame = framer.createGoawayFrame(); 307 | frame.lastStreamId = 3; 308 | frame.errorCode = protocol.CODE_PROTOCOL_ERROR; 309 | 310 | conn.once('error', function (err) { 311 | expect(err.code).to.be(protocol.CODE_PROTOCOL_ERROR); 312 | done(); 313 | }); 314 | 315 | conn._deserializer.emit('data', frame); 316 | }); 317 | }); 318 | 319 | context('receiving SETTINGS', function () { 320 | it('sends SETTINGS frame with ACK flag', function (done) { 321 | var conn = createConnection(true); 322 | 323 | var frame = framer.createSettingsFrame(); 324 | frame.setEnablePush(false); 325 | frame.setMaxConcurrentStreams(100); 326 | 327 | conn._serializer.write = function (frame) { 328 | expect(frame.type).to.be(protocol.FRAME_TYPE_SETTINGS); 329 | expect(frame.ack).to.be(true); 330 | done(); 331 | }; 332 | 333 | conn._deserializer.emit('data', frame); 334 | }); 335 | 336 | it('applies new settings', function () { 337 | var conn = createConnection(true); 338 | 339 | var settings = { 340 | enablePush: false, 341 | maxConcurrentStreams: 100 342 | }; 343 | 344 | var frame = framer.createSettingsFrame(); 345 | frame.ack = true; 346 | 347 | conn._pendingSettings.push(settings); 348 | conn._deserializer.emit('data', frame); 349 | expect(conn._localSettings.enablePush).to.be(settings.enablePush); 350 | expect(conn._localSettings.maxConcurrentStreams).to.be(settings.maxConcurrentStreams); 351 | }); 352 | }); 353 | 354 | context('receiving PING', function () { 355 | it('sends PING with ACK flag', function (done) { 356 | var conn = createConnection(true); 357 | 358 | var frame = framer.createPingFrame(); 359 | 360 | conn._serializer.write = function (frame) { 361 | expect(frame.type).to.be(protocol.FRAME_TYPE_PING); 362 | expect(frame.ack).to.be(true); 363 | done(); 364 | }; 365 | 366 | conn._deserializer.emit('data', frame); 367 | }); 368 | 369 | it('emits ping event', function (done) { 370 | var conn = createConnection(true); 371 | 372 | var frame = framer.createPingFrame(); 373 | frame.ack = true; 374 | 375 | conn.once('ping', function () { 376 | done(); 377 | }); 378 | 379 | conn._deserializer.emit('data', frame); 380 | }); 381 | }); 382 | 383 | context('receiving WINDOW_UPDATE', function () { 384 | it('updates connection level flow control window', function () { 385 | var conn = createConnection(true); 386 | 387 | var currentWindowSize = conn.currentWindowSize; 388 | var increment = 1000; 389 | 390 | var frame = framer.createWindowUpdateFrame(); 391 | frame.setWindowSizeIncrement(increment); 392 | 393 | conn._deserializer.emit('data', frame); 394 | expect(conn.currentWindowSize).to.be(currentWindowSize + increment); 395 | }); 396 | }); 397 | 398 | context('receiving a frame associated with the stream', function () { 399 | it('creates the new stream', function (done) { 400 | var conn = createConnection(true); 401 | 402 | var headerBlock = new Buffer([ 403 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 404 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 405 | 0x2e, 0x63, 0x6f, 0x6d 406 | ]); 407 | 408 | var frame = framer.createHeadersFrame(); 409 | frame.streamId = 3; 410 | frame.endHeaders = true; 411 | frame.setHeaderBlockFragment(headerBlock); 412 | 413 | conn.on('stream', function (stream) { 414 | expect(stream.id).to.be(frame.streamId); 415 | done(); 416 | }); 417 | 418 | conn._deserializer.emit('data', frame); 419 | }); 420 | 421 | it('passes the frame to the stream', function (done) { 422 | var conn = createConnection(true); 423 | 424 | var headerBlock = new Buffer([ 425 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 426 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 427 | 0x2e, 0x63, 0x6f, 0x6d 428 | ]); 429 | 430 | var data = 'test'; 431 | 432 | var headersFrame = framer.createHeadersFrame(); 433 | headersFrame.streamId = 3; 434 | headersFrame.endHeaders = true; 435 | headersFrame.setHeaderBlockFragment(headerBlock); 436 | 437 | var dataFrame = framer.createDataFrame(); 438 | dataFrame.streamId = 3; 439 | dataFrame.endStream = true; 440 | dataFrame.setData(data); 441 | 442 | conn.on('stream', function (stream) { 443 | stream.on('data', function (chunk) { 444 | expect(chunk).to.eql(new Buffer(data)); 445 | done(); 446 | }); 447 | }); 448 | 449 | conn._deserializer.emit('data', headersFrame); 450 | conn._deserializer.emit('data', dataFrame); 451 | }); 452 | }); 453 | }); 454 | 455 | describe('Socket Management', function () { 456 | context('emitted close event', function () { 457 | it('emits close event', function (done) { 458 | var conn = createConnection(true); 459 | 460 | conn.on('close', function () { 461 | done(); 462 | }); 463 | 464 | conn.socket.emit('close'); 465 | }); 466 | }); 467 | 468 | context('emitted error event', function () { 469 | it('emits error event', function (done) { 470 | var conn = createConnection(true); 471 | 472 | conn.on('error', function () { 473 | done(); 474 | }); 475 | 476 | conn.socket.emit('error', new Error()); 477 | }); 478 | }); 479 | 480 | context('emitted timeout event', function () { 481 | it('emits error event', function (done) { 482 | var conn = createConnection(true); 483 | 484 | conn.on('timeout', function () { 485 | done(); 486 | }); 487 | 488 | conn.socket.emit('timeout'); 489 | }); 490 | }); 491 | }); 492 | }); -------------------------------------------------------------------------------- /test/flow_controller_test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'), 2 | waitress = require('waitress'); 3 | 4 | var FlowController = require('../lib/flow_controller'), 5 | framer = require('../lib/framer'), 6 | protocol = require('../lib/protocol'); 7 | 8 | describe('Flow Controller', function () { 9 | function createController () { 10 | var controller = new FlowController(); 11 | controller._send = function () {}; 12 | 13 | return controller; 14 | } 15 | 16 | describe('updateInitialWindowSize()', function () { 17 | it('sets new initial window size', function () { 18 | var controller = createController(); 19 | 20 | var windowSize = 100; 21 | 22 | controller.updateInitialWindowSize(windowSize); 23 | expect(controller.initialWindowSize).to.be(windowSize); 24 | expect(controller.currentWindowSize).to.be(windowSize); 25 | }); 26 | 27 | it('sends blocked frames', function (done) { 28 | var controller = createController(); 29 | 30 | var windowSize = 70000; 31 | 32 | var frame = framer.createDataFrame(); 33 | frame.streamId = 3; 34 | frame.endStream = true; 35 | frame.setData('test'); 36 | 37 | controller.currentWindowSize = 0; 38 | controller._queue.push(frame); 39 | 40 | controller._send = function (sendFrame) { 41 | expect(sendFrame).to.eql(frame); 42 | expect(controller.initialWindowSize).to.be(windowSize); 43 | expect(controller.currentWindowSize).to.be(4461); 44 | done(); 45 | }; 46 | 47 | controller.updateInitialWindowSize(windowSize); 48 | }); 49 | }); 50 | 51 | describe('increaseWindowSize()', function () { 52 | it('increases current window size', function () { 53 | var controller = createController(); 54 | 55 | var currentWindowSize = controller.currentWindowSize; 56 | var windowSize = 100; 57 | 58 | controller.increaseWindowSize(windowSize); 59 | expect(controller.currentWindowSize).to.be(currentWindowSize + windowSize); 60 | }); 61 | 62 | it('sends blocked frames', function (done) { 63 | var controller = createController(); 64 | 65 | var windowSize = 100; 66 | 67 | var frame = framer.createDataFrame(); 68 | frame.streamId = 3; 69 | frame.endStream = true; 70 | frame.setData('test'); 71 | 72 | controller.currentWindowSize = 0; 73 | controller._queue.push(frame); 74 | 75 | controller._send = function (sendFrame) { 76 | expect(sendFrame).to.eql(frame); 77 | expect(controller.currentWindowSize).to.be(96); 78 | done(); 79 | }; 80 | 81 | controller.increaseWindowSize(windowSize); 82 | }); 83 | }); 84 | 85 | describe('push()', function () { 86 | it('sends flow controlled frames', function (done) { 87 | var controller = createController(); 88 | 89 | var frame = framer.createDataFrame(); 90 | frame.streamId = 3; 91 | frame.endStream = true; 92 | frame.setData('test'); 93 | 94 | controller._send = function (sendFrame) { 95 | expect(sendFrame).to.eql(frame); 96 | done(); 97 | }; 98 | 99 | controller.push(frame); 100 | }); 101 | 102 | it('blocks flow controlled frames', function () { 103 | var controller = createController(); 104 | 105 | var frame = framer.createDataFrame(); 106 | frame.streamId = 3; 107 | frame.endStream = true; 108 | frame.setData('test'); 109 | 110 | controller.currentWindowSize = 0; 111 | controller.push(frame); 112 | expect(controller._queue[0]).to.be(frame); 113 | }); 114 | 115 | it('splits large flow controlled frames', function (done) { 116 | var controller = createController(); 117 | 118 | var data = ''; 119 | for (var i=0; i<65536; i++) { 120 | data += 'x'; 121 | } 122 | 123 | var frame = framer.createDataFrame(); 124 | frame.streamId = 3; 125 | frame.endStream = true; 126 | frame.setData(data); 127 | frame.setPadding(8); 128 | 129 | var frames = []; 130 | var check = waitress(4, function() { 131 | process.nextTick(function () { 132 | expect(frames[0].length).to.be(protocol.FRAME_LEN_MAX); 133 | expect(frames[1].length).to.be(protocol.FRAME_LEN_MAX); 134 | expect(frames[2].length).to.be(protocol.FRAME_LEN_MAX); 135 | expect(frames[3].length).to.be(protocol.FRAME_LEN_MAX); 136 | expect(controller._queue[0].data.length).to.be(40); 137 | done(); 138 | }); 139 | }); 140 | 141 | controller._send = function (sendFrame) { 142 | frames.push(sendFrame); 143 | check(); 144 | }; 145 | 146 | controller.push(frame); 147 | }); 148 | 149 | it('sends frames immediately', function (done) { 150 | var controller = createController(); 151 | 152 | var headerBlock = new Buffer([ 153 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 154 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 155 | 0x2e, 0x63, 0x6f, 0x6d 156 | ]); 157 | 158 | var frame = framer.createHeadersFrame(); 159 | frame.streamId = 3; 160 | frame.endHeaders = true; 161 | frame.setHeaderBlockFragment(headerBlock); 162 | 163 | controller._send = function (sendFrame) { 164 | expect(sendFrame).to.eql(frame); 165 | done(); 166 | }; 167 | 168 | controller.push(frame); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /test/framer_test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | 3 | var framer = require('../lib/framer'), 4 | protocol = require('../lib/protocol'); 5 | 6 | describe('Framer', function () { 7 | describe('DATA Frame', function () { 8 | describe('encode()', function () { 9 | it('returns buffer', function () { 10 | var expected = new Buffer([ 11 | 0x00, 0x00, 0x0d, 0x00, 0x0b, 0x00, 0x00, 0x00, 12 | 0x01, 0x08, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 14 | ]); 15 | 16 | var frame = framer.createDataFrame(); 17 | frame.streamId = 1; 18 | frame.endStream = true; 19 | frame.endSegment = true; 20 | frame.setData('Test'); 21 | frame.setPadding(8); 22 | 23 | var encodedFrame = frame.encode(); 24 | var length = frame.length + protocol.FRAME_HEADER_LEN; 25 | 26 | expect(encodedFrame.length).to.be(length); 27 | expect(encodedFrame).to.eql(expected); 28 | }); 29 | }); 30 | 31 | describe('decode()', function () { 32 | it('returns DATA Frame object', function () { 33 | var encodedFrame = new Buffer([ 34 | 0x00, 0x00, 0x0d, 0x00, 0x0b, 0x00, 0x00, 0x00, 35 | 0x01, 0x08, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 37 | ]); 38 | 39 | var frame = framer.decodeFrame(encodedFrame); 40 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 41 | 42 | expect(frame.length).to.be(length); 43 | expect(frame.streamId).to.be(1); 44 | expect(frame.endStream).to.be(true); 45 | expect(frame.endSegment).to.be(true); 46 | expect(frame.data).to.eql(new Buffer('Test')); 47 | expect(frame.padding).to.be(8); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('HEADERS Frame', function () { 53 | describe('encode()', function () { 54 | it('returns buffer', function () { 55 | var expected = new Buffer([ 56 | 0x00, 0x00, 0x22, 0x01, 0x2d, 0x00, 0x00, 0x00, 57 | 0x03, 0x08, 0x80, 0x00, 0x00, 0x01, 0x05, 0x82, 58 | 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 0x2e, 59 | 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 60 | 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00 62 | ]); 63 | 64 | var headerBlock = new Buffer([ 65 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 66 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 67 | 0x2e, 0x63, 0x6f, 0x6d 68 | ]); 69 | 70 | var frame = framer.createHeadersFrame(); 71 | frame.streamId = 3; 72 | frame.endStream = true; 73 | frame.endHeaders = true; 74 | frame.setPriority(1, 5, true); 75 | frame.setHeaderBlockFragment(headerBlock); 76 | frame.setPadding(8); 77 | 78 | var encodedFrame = frame.encode(); 79 | var length = frame.length += protocol.FRAME_HEADER_LEN; 80 | 81 | expect(encodedFrame.length).to.be(length); 82 | expect(encodedFrame).to.eql(expected); 83 | }); 84 | }); 85 | 86 | describe('decode()', function () { 87 | it('returns HEADERS Frame object', function () { 88 | var encodedFrame = new Buffer([ 89 | 0x00, 0x00, 0x22, 0x01, 0x2d, 0x00, 0x00, 0x00, 90 | 0x03, 0x08, 0x80, 0x00, 0x00, 0x01, 0x05, 0x82, 91 | 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 0x2e, 92 | 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 93 | 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00 95 | ]); 96 | 97 | var headerBlock = new Buffer([ 98 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 99 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 100 | 0x2e, 0x63, 0x6f, 0x6d 101 | ]); 102 | 103 | var frame = framer.decodeFrame(encodedFrame); 104 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 105 | 106 | expect(frame.length).to.be(length); 107 | expect(frame.streamId).to.be(3); 108 | expect(frame.endStream).to.be(true); 109 | expect(frame.endHeaders).to.be(true); 110 | expect(frame.priority).to.be(true); 111 | expect(frame.headerBlockFragment).to.eql(headerBlock); 112 | expect(frame.exclusive).to.be(true); 113 | expect(frame.streamDependency).to.be(1); 114 | expect(frame.weight).to.be(5); 115 | expect(frame.padding).to.be(8); 116 | }); 117 | }); 118 | }); 119 | 120 | describe('PRIORITY Frame', function () { 121 | describe('encode()', function () { 122 | it('returns buffer', function () { 123 | var expected = new Buffer([ 124 | 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 125 | 0x03, 0x80, 0x00, 0x00, 0x01, 0x08 126 | ]); 127 | 128 | var frame = framer.createPriorityFrame(); 129 | frame.streamId = 3; 130 | frame.setPriority(1, 8, true); 131 | 132 | var encodedFrame = frame.encode(); 133 | var length = frame.length += protocol.FRAME_HEADER_LEN; 134 | 135 | expect(encodedFrame.length).to.be(length); 136 | expect(encodedFrame).to.eql(expected); 137 | }); 138 | }); 139 | 140 | describe('decode()', function () { 141 | it('returns PRIORITY Frame object', function () { 142 | var encodedFrame = new Buffer([ 143 | 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 144 | 0x03, 0x80, 0x00, 0x00, 0x01, 0x08 145 | ]); 146 | 147 | var frame = framer.decodeFrame(encodedFrame); 148 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 149 | 150 | expect(frame.length).to.be(length); 151 | expect(frame.streamId).to.be(3); 152 | expect(frame.streamDependency).to.be(1); 153 | expect(frame.weight).to.be(8); 154 | expect(frame.exclusive).to.be(true); 155 | }); 156 | }); 157 | }); 158 | 159 | describe('RST_STREAM Frame', function () { 160 | describe('encode()', function () { 161 | it('returns buffer', function () { 162 | var expected = new Buffer([ 163 | 0x00, 0x00, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 164 | 0x01, 0x00, 0x00, 0x00, 0x01 165 | ]); 166 | 167 | var frame = framer.createRstStreamFrame(); 168 | frame.streamId = 1; 169 | frame.setErrorCode(protocol.CODE_PROTOCOL_ERROR); 170 | 171 | var encodedFrame = frame.encode(); 172 | var length = frame.length += protocol.FRAME_HEADER_LEN; 173 | 174 | expect(encodedFrame.length).to.be(length); 175 | expect(encodedFrame).to.eql(expected); 176 | }); 177 | }); 178 | 179 | describe('decode()', function () { 180 | it('returns RST_STREAM Frame object', function () { 181 | var encodedFrame = new Buffer([ 182 | 0x00, 0x00, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 183 | 0x01, 0x00, 0x00, 0x00, 0x01 184 | ]); 185 | 186 | var frame = framer.decodeFrame(encodedFrame); 187 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 188 | 189 | expect(frame.length).to.be(length); 190 | expect(frame.streamId).to.be(1); 191 | expect(frame.errorCode).to.be(protocol.CODE_PROTOCOL_ERROR); 192 | }); 193 | }); 194 | }); 195 | 196 | describe('SETTINGS Frame', function () { 197 | describe('encode()', function () { 198 | it('returns buffer', function () { 199 | var expected = new Buffer([ 200 | 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 201 | 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 202 | 0x03, 0x00, 0x00, 0x00, 0x64 203 | ]); 204 | 205 | var frame = framer.createSettingsFrame(); 206 | frame.setEnablePush(false); 207 | frame.setMaxConcurrentStreams(100); 208 | 209 | var encodedFrame = frame.encode(); 210 | var length = frame.length += protocol.FRAME_HEADER_LEN; 211 | 212 | expect(encodedFrame.length).to.be(length); 213 | expect(encodedFrame).to.eql(expected); 214 | }); 215 | }); 216 | 217 | describe('decode()', function () { 218 | it('returns SETTINGS Frame object', function () { 219 | var encodedFrame = new Buffer([ 220 | 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x00, 221 | 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 222 | 0x03, 0x00, 0x00, 0x00, 0x64 223 | ]); 224 | 225 | var frame = framer.decodeFrame(encodedFrame); 226 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 227 | 228 | expect(frame.length).to.be(length); 229 | expect(frame.streamId).to.be(0); 230 | expect(frame.ack).to.be(false); 231 | expect(frame.headerTableSize).to.be(4096); 232 | expect(frame.enablePush).to.be(false); 233 | expect(frame.maxConcurrentStreams).to.eql(100); 234 | expect(frame.initialWindowSize).to.be(65535); 235 | }); 236 | }); 237 | }); 238 | 239 | describe('PUSH_PROMISE Frame', function () { 240 | describe('encode()', function () { 241 | it('returns buffer', function () { 242 | var expected = new Buffer([ 243 | 0x00, 0x00, 0x21, 0x05, 0x0c, 0x00, 0x00, 0x00, 244 | 0x01, 0x08, 0x00, 0x00, 0x00, 0x03, 0x82, 0x87, 245 | 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 246 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 247 | 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 248 | 0x00, 0x00 249 | ]); 250 | 251 | var headerBlock = new Buffer([ 252 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 253 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 254 | 0x2e, 0x63, 0x6f, 0x6d 255 | ]); 256 | 257 | var frame = framer.createPushPromiseFrame(); 258 | frame.streamId = 1; 259 | frame.endHeaders = true; 260 | frame.setPromisedStreamId(3); 261 | frame.setHeaderBlockFragment(headerBlock); 262 | frame.setPadding(8); 263 | 264 | var encodedFrame = frame.encode(); 265 | var length = frame.length += protocol.FRAME_HEADER_LEN; 266 | 267 | expect(encodedFrame.length).to.be(length); 268 | expect(encodedFrame).to.eql(expected); 269 | }); 270 | }); 271 | 272 | describe('decode()', function () { 273 | it('returns PUSH_PROMISE Frame object', function () { 274 | var encodedFrame = new Buffer([ 275 | 0x00, 0x00, 0x21, 0x05, 0x0c, 0x00, 0x00, 0x00, 276 | 0x01, 0x08, 0x00, 0x00, 0x00, 0x03, 0x82, 0x87, 277 | 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 278 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 279 | 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 280 | 0x00, 0x00 281 | ]); 282 | 283 | var headerBlock = new Buffer([ 284 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 285 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 286 | 0x2e, 0x63, 0x6f, 0x6d 287 | ]); 288 | 289 | var frame = framer.decodeFrame(encodedFrame); 290 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 291 | 292 | expect(frame.length).to.be(length); 293 | expect(frame.streamId).to.be(1); 294 | expect(frame.endHeaders).to.be(true); 295 | expect(frame.promisedStreamId).to.be(3); 296 | expect(frame.headerBlockFragment).to.eql(headerBlock); 297 | expect(frame.padding).to.eql(8); 298 | }); 299 | }); 300 | }); 301 | 302 | describe('PING Frame', function () { 303 | describe('encode()', function () { 304 | it('returns buffer', function () { 305 | var expected = new Buffer([ 306 | 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 307 | 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 308 | 0x00 309 | ]); 310 | 311 | var frame = framer.createPingFrame(); 312 | frame.setOpaqueData('test'); 313 | 314 | var encodedFrame = frame.encode(); 315 | var length = frame.length += protocol.FRAME_HEADER_LEN; 316 | 317 | expect(encodedFrame.length).to.be(length); 318 | expect(encodedFrame).to.eql(expected); 319 | }); 320 | }); 321 | 322 | describe('decode()', function () { 323 | it('returns PING Frame object', function () { 324 | var encodedFrame = new Buffer([ 325 | 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 326 | 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 327 | 0x00 328 | ]); 329 | 330 | var expected = new Buffer(8); 331 | expected.fill(0); 332 | expected.write('test'); 333 | 334 | var frame = framer.decodeFrame(encodedFrame); 335 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 336 | 337 | expect(frame.length).to.be(length); 338 | expect(frame.streamId).to.be(0); 339 | expect(frame.opaqueData).to.eql(expected); 340 | }); 341 | }); 342 | }); 343 | 344 | describe('GOAWAY Frame', function () { 345 | describe('encode()', function () { 346 | it('returns buffer', function () { 347 | var expected = new Buffer([ 348 | 0x00, 0x00, 0x17, 0x07, 0x00, 0x00, 0x00, 0x00, 349 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 350 | 0x01, 0x53, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 351 | 0x6e, 0x67, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67 352 | ]); 353 | 354 | var frame = framer.createGoawayFrame(); 355 | frame.lastStreamId = 3; 356 | frame.errorCode = protocol.CODE_PROTOCOL_ERROR; 357 | frame.setDebugData('Something wrong'); 358 | 359 | var encodedFrame = frame.encode(); 360 | var length = frame.length += protocol.FRAME_HEADER_LEN; 361 | 362 | expect(encodedFrame.length).to.be(length); 363 | expect(encodedFrame).to.eql(expected); 364 | }); 365 | }); 366 | 367 | describe('decode()', function () { 368 | it('returns GOAWAY Frame object', function () { 369 | var encodedFrame = new Buffer([ 370 | 0x00, 0x00, 0x17, 0x07, 0x00, 0x00, 0x00, 0x00, 371 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 372 | 0x01, 0x53, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, 373 | 0x6e, 0x67, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0x67 374 | ]); 375 | 376 | var frame = framer.decodeFrame(encodedFrame); 377 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 378 | 379 | expect(frame.length).to.be(length); 380 | expect(frame.streamId).to.be(0); 381 | expect(frame.lastStreamId).to.be(3); 382 | expect(frame.errorCode).to.be(protocol.CODE_PROTOCOL_ERROR); 383 | expect(frame.debugData).to.eql(new Buffer('Something wrong')); 384 | }); 385 | }); 386 | }); 387 | 388 | describe('WINDOW_UPDATE Frame', function () { 389 | describe('encode()', function () { 390 | it('returns buffer', function () { 391 | var expected = new Buffer([ 392 | 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 393 | 0x00, 0x00, 0x00, 0x00, 0x64 394 | ]); 395 | 396 | var frame = framer.createWindowUpdateFrame(); 397 | frame.lastStreamId = 3; 398 | frame.setWindowSizeIncrement(100); 399 | 400 | var encodedFrame = frame.encode(); 401 | var length = frame.length += protocol.FRAME_HEADER_LEN; 402 | 403 | expect(encodedFrame.length).to.be(length); 404 | expect(encodedFrame).to.eql(expected); 405 | }); 406 | }); 407 | 408 | describe('decode()', function () { 409 | it('returns WINDOW_UPDATE Frame object', function () { 410 | var encodedFrame = new Buffer([ 411 | 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 412 | 0x00, 0x00, 0x00, 0x00, 0x64 413 | ]); 414 | 415 | var frame = framer.decodeFrame(encodedFrame); 416 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 417 | 418 | expect(frame.length).to.be(length); 419 | expect(frame.windowSizeIncrement).to.be(100); 420 | }); 421 | }); 422 | }); 423 | 424 | describe('CONTINUATION Frame', function () { 425 | describe('encode()', function () { 426 | it('returns buffer', function () { 427 | var expected = new Buffer([ 428 | 0x00, 0x00, 0x14, 0x09, 0x04, 0x00, 0x00, 0x00, 429 | 0x01, 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 430 | 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 431 | 0x65, 0x2e, 0x63, 0x6f, 0x6d 432 | ]); 433 | 434 | var headerBlock = new Buffer([ 435 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 436 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 437 | 0x2e, 0x63, 0x6f, 0x6d 438 | ]); 439 | 440 | var frame = framer.createContinuationFrame(); 441 | frame.streamId = 1; 442 | frame.endHeaders = true; 443 | frame.setHeaderBlockFragment(headerBlock); 444 | 445 | var encodedFrame = frame.encode(); 446 | var length = frame.length += protocol.FRAME_HEADER_LEN; 447 | 448 | expect(encodedFrame.length).to.be(length); 449 | expect(encodedFrame).to.eql(expected); 450 | }); 451 | }); 452 | 453 | describe('decode()', function () { 454 | it('returns CONTINUATION Frame object', function () { 455 | var encodedFrame = new Buffer([ 456 | 0x00, 0x00, 0x14, 0x09, 0x04, 0x00, 0x00, 0x00, 457 | 0x01, 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 458 | 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 459 | 0x65, 0x2e, 0x63, 0x6f, 0x6d 460 | ]); 461 | 462 | var headerBlock = new Buffer([ 463 | 0x82, 0x87, 0x86, 0x44, 0x0f, 0x77, 0x77, 0x77, 464 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 465 | 0x2e, 0x63, 0x6f, 0x6d 466 | ]); 467 | 468 | var frame = framer.decodeFrame(encodedFrame); 469 | var length = encodedFrame.length - protocol.FRAME_HEADER_LEN; 470 | 471 | expect(frame.length).to.be(length); 472 | expect(frame.streamId).to.be(1); 473 | expect(frame.endHeaders).to.be(true); 474 | expect(frame.headerBlockFragment).to.eql(headerBlock); 475 | }); 476 | }); 477 | }); 478 | }); 479 | -------------------------------------------------------------------------------- /test/hpack_test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | 3 | var hpack = require('../lib/hpack'); 4 | 5 | describe('HPACK', function () { 6 | describe('compress()', function () { 7 | describe('Request Examples with Huffman Coding', function () { 8 | var encoder = hpack.createContext(); 9 | 10 | it('encodes the first request headers', function () { 11 | var headers = [ 12 | [ ':method', 'GET' ], 13 | [ ':scheme', 'http' ], 14 | [ ':path', '/' ], 15 | [ ':authority', 'www.example.com' ] 16 | ]; 17 | 18 | var expected = new Buffer([ 19 | 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 20 | 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 21 | 0xff 22 | ]); 23 | 24 | var buffer = encoder.compress(headers); 25 | expect(buffer).to.eql(expected); 26 | }); 27 | 28 | it('encodes the second request headers', function () { 29 | var headers = [ 30 | [ ':method', 'GET' ], 31 | [ ':scheme', 'http' ], 32 | [ ':path', '/' ], 33 | [ ':authority', 'www.example.com' ], 34 | [ 'cache-control', 'no-cache' ] 35 | ]; 36 | 37 | var expected = new Buffer([ 38 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 39 | 0x10, 0x64, 0x9c, 0xbf 40 | ]); 41 | 42 | var buffer = encoder.compress(headers); 43 | expect(buffer).to.eql(expected); 44 | }); 45 | 46 | it('encodes the third request headers', function () { 47 | var headers = [ 48 | [ ':method', 'GET' ], 49 | [ ':scheme', 'https' ], 50 | [ ':path', '/index.html' ], 51 | [ ':authority', 'www.example.com' ], 52 | [ 'custom-key', 'custom-value' ] 53 | ]; 54 | 55 | var expected = new Buffer([ 56 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 57 | 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, 58 | 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf 59 | ]); 60 | 61 | var buffer = encoder.compress(headers); 62 | expect(buffer).to.eql(expected); 63 | }); 64 | }); 65 | 66 | context('Request Examples without Huffman Coding', function () { 67 | var encoder = hpack.createContext({ huffman: false }); 68 | 69 | it('encodes the first request headers', function () { 70 | var headers = [ 71 | [ ':method', 'GET' ], 72 | [ ':scheme', 'http' ], 73 | [ ':path', '/' ], 74 | [ ':authority', 'www.example.com' ] 75 | ]; 76 | 77 | var expected = new Buffer([ 78 | 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 79 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 80 | 0x2e, 0x63, 0x6f, 0x6d 81 | ]); 82 | 83 | var buffer = encoder.compress(headers); 84 | expect(buffer).to.eql(expected); 85 | }); 86 | 87 | it('encodes the second request headers', function () { 88 | var headers = [ 89 | [ ':method', 'GET' ], 90 | [ ':scheme', 'http' ], 91 | [ ':path', '/' ], 92 | [ ':authority', 'www.example.com' ], 93 | [ 'cache-control', 'no-cache' ] 94 | ]; 95 | 96 | var expected = new Buffer([ 97 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 98 | 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65 99 | ]); 100 | 101 | var buffer = encoder.compress(headers); 102 | expect(buffer).to.eql(expected); 103 | }); 104 | 105 | it('encodes the third request headers', function () { 106 | var headers = [ 107 | [ ':method', 'GET' ], 108 | [ ':scheme', 'https' ], 109 | [ ':path', '/index.html' ], 110 | [ ':authority', 'www.example.com' ], 111 | [ 'custom-key', 'custom-value' ] 112 | ]; 113 | 114 | var expected = new Buffer([ 115 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 116 | 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 117 | 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 118 | 0x76, 0x61, 0x6c, 0x75, 0x65 119 | ]); 120 | 121 | var buffer = encoder.compress(headers); 122 | expect(buffer).to.eql(expected); 123 | }); 124 | }); 125 | 126 | describe('Response Examples with Huffman Coding', function () { 127 | var encoder = hpack.createContext(); 128 | 129 | it('encodes the first response headers', function () { 130 | var headers = [ 131 | [ ':status', '302' ], 132 | [ 'cache-control', 'private' ], 133 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 134 | [ 'location', 'https://www.example.com' ] 135 | ]; 136 | 137 | var expected = new Buffer([ 138 | 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 139 | 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 140 | 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 141 | 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 142 | 0x2d, 0x1b, 0xff, 0x0f, 0x1f, 0x91, 0x9d, 0x29, 143 | 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 144 | 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3 145 | ]); 146 | 147 | var buffer = encoder.compress(headers); 148 | expect(buffer).to.eql(expected); 149 | }); 150 | 151 | it('encodes the second response headers', function () { 152 | var headers = [ 153 | [ ':status', '307' ], 154 | [ 'cache-control', 'private' ], 155 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 156 | [ 'location', 'https://www.example.com' ] 157 | ]; 158 | 159 | var expected = new Buffer([ 160 | 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc0, 0xbf, 0x0f, 161 | 0x1f, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 162 | 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 163 | 0xae, 0x43, 0xd3 164 | ]); 165 | 166 | var buffer = encoder.compress(headers); 167 | expect(buffer).to.eql(expected); 168 | }); 169 | 170 | it('encodes the third response headers', function () { 171 | var cookie = 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'; 172 | var headers = [ 173 | [ ':status', '200' ], 174 | [ 'cache-control', 'private' ], 175 | [ 'date', 'Mon, 21 Oct 2013 20:13:22 GMT' ], 176 | [ 'location', 'https://www.example.com' ], 177 | [ 'content-encoding', 'gzip' ], 178 | [ 'set-cookie', cookie ] 179 | ]; 180 | 181 | var expected = new Buffer([ 182 | 0x88, 0xc0, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 183 | 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 184 | 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 185 | 0x1b, 0xff, 0x0f, 0x1f, 0x91, 0x9d, 0x29, 0xad, 186 | 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 187 | 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3, 0x5a, 0x83, 188 | 0x9b, 0xd9, 0xab, 0x0f, 0x28, 0xad, 0x94, 0xe7, 189 | 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 190 | 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5, 0xaf, 191 | 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 192 | 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 193 | 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 194 | 0x3d, 0x50, 0x07 195 | ]); 196 | 197 | var buffer = encoder.compress(headers); 198 | expect(buffer).to.eql(expected); 199 | }); 200 | }); 201 | 202 | describe('Response Examples without Huffman Coding', function () { 203 | var encoder = hpack.createContext({ huffman: false }); 204 | 205 | it('encodes the first response headers', function () { 206 | var headers = [ 207 | [ ':status', '302' ], 208 | [ 'cache-control', 'private' ], 209 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 210 | [ 'location', 'https://www.example.com' ] 211 | ]; 212 | 213 | var expected = new Buffer([ 214 | 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 215 | 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 216 | 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 217 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 218 | 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 219 | 0x31, 0x20, 0x47, 0x4d, 0x54, 0x0f, 0x1f, 0x17, 220 | 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 221 | 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 222 | 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d 223 | ]); 224 | 225 | var buffer = encoder.compress(headers); 226 | expect(buffer).to.eql(expected); 227 | }); 228 | 229 | it('encodes the second response headers', function () { 230 | var headers = [ 231 | [ ':status', '307' ], 232 | [ 'cache-control', 'private' ], 233 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 234 | [ 'location', 'https://www.example.com' ] 235 | ]; 236 | 237 | var expected = new Buffer([ 238 | 0x48, 0x03, 0x33, 0x30, 0x37, 0xc0, 0xbf, 0x0f, 239 | 0x1f, 0x17, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 240 | 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 241 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 242 | 0x6d 243 | ]); 244 | 245 | var buffer = encoder.compress(headers); 246 | expect(buffer).to.eql(expected); 247 | }); 248 | 249 | it('encodes the third response headers', function () { 250 | var cookie = 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'; 251 | var headers = [ 252 | [ ':status', '200' ], 253 | [ 'cache-control', 'private' ], 254 | [ 'date', 'Mon, 21 Oct 2013 20:13:22 GMT' ], 255 | [ 'location', 'https://www.example.com' ], 256 | [ 'content-encoding', 'gzip' ], 257 | [ 'set-cookie', cookie ] 258 | ]; 259 | 260 | var expected = new Buffer([ 261 | 0x88, 0xc0, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 262 | 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 263 | 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 264 | 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 265 | 0x54, 0x0f, 0x1f, 0x17, 0x68, 0x74, 0x74, 0x70, 266 | 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 267 | 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 268 | 0x63, 0x6f, 0x6d, 0x5a, 0x04, 0x67, 0x7a, 0x69, 269 | 0x70, 0x0f, 0x28, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 270 | 0x41, 0x53, 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 271 | 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 272 | 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 273 | 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 274 | 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 275 | 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 276 | 0x6f, 0x6e, 0x3d, 0x31 277 | ]); 278 | 279 | var buffer = encoder.compress(headers); 280 | expect(buffer).to.eql(expected); 281 | }); 282 | }); 283 | }); 284 | 285 | describe('decompress()', function () { 286 | describe('Request Examples with Huffman Coding', function () { 287 | var decoder = hpack.createContext(); 288 | 289 | it('decodes the first request headers', function () { 290 | var buffer = new Buffer([ 291 | 0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 292 | 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 293 | 0xff 294 | ]); 295 | 296 | var expected = [ 297 | [ ':method', 'GET' ], 298 | [ ':scheme', 'http' ], 299 | [ ':path', '/' ], 300 | [ ':authority', 'www.example.com' ] 301 | ]; 302 | 303 | var headers = decoder.decompress(buffer); 304 | expect(headers).to.eql(expected); 305 | }); 306 | 307 | it('decodes the second request headers', function () { 308 | var buffer = new Buffer([ 309 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 310 | 0x10, 0x64, 0x9c, 0xbf 311 | ]); 312 | 313 | var expected = [ 314 | [ ':method', 'GET' ], 315 | [ ':scheme', 'http' ], 316 | [ ':path', '/' ], 317 | [ ':authority', 'www.example.com' ], 318 | [ 'cache-control', 'no-cache' ] 319 | ]; 320 | 321 | var headers = decoder.decompress(buffer); 322 | expect(headers).to.eql(expected); 323 | }); 324 | 325 | it('decodes the third request headers', function () { 326 | var buffer = new Buffer([ 327 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 328 | 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 0x89, 0x25, 329 | 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf 330 | ]); 331 | 332 | var expected = [ 333 | [ ':method', 'GET' ], 334 | [ ':scheme', 'https' ], 335 | [ ':path', '/index.html' ], 336 | [ ':authority', 'www.example.com' ], 337 | [ 'custom-key', 'custom-value' ] 338 | ]; 339 | 340 | var headers = decoder.decompress(buffer); 341 | expect(headers).to.eql(expected); 342 | }); 343 | }); 344 | 345 | context('Request Examples without Huffman Coding', function () { 346 | var decoder = hpack.createContext({ huffman: false }); 347 | 348 | it('decodes the first request headers', function () { 349 | var buffer = new Buffer([ 350 | 0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 351 | 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 352 | 0x2e, 0x63, 0x6f, 0x6d 353 | ]); 354 | 355 | var expected = [ 356 | [ ':method', 'GET' ], 357 | [ ':scheme', 'http' ], 358 | [ ':path', '/' ], 359 | [ ':authority', 'www.example.com' ] 360 | ]; 361 | 362 | var headers = decoder.decompress(buffer); 363 | expect(headers).to.eql(expected); 364 | }); 365 | 366 | it('decodes the second request headers', function () { 367 | var buffer = new Buffer([ 368 | 0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 369 | 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65 370 | ]); 371 | 372 | var expected = [ 373 | [ ':method', 'GET' ], 374 | [ ':scheme', 'http' ], 375 | [ ':path', '/' ], 376 | [ ':authority', 'www.example.com' ], 377 | [ 'cache-control', 'no-cache' ] 378 | ]; 379 | 380 | var headers = decoder.decompress(buffer); 381 | expect(headers).to.eql(expected); 382 | }); 383 | 384 | it('decodes the third request headers', function () { 385 | var buffer = new Buffer([ 386 | 0x82, 0x87, 0x85, 0xbf, 0x40, 0x0a, 0x63, 0x75, 387 | 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 388 | 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 389 | 0x76, 0x61, 0x6c, 0x75, 0x65 390 | ]); 391 | 392 | var expected = [ 393 | [ ':method', 'GET' ], 394 | [ ':scheme', 'https' ], 395 | [ ':path', '/index.html' ], 396 | [ ':authority', 'www.example.com' ], 397 | [ 'custom-key', 'custom-value' ] 398 | ]; 399 | 400 | var headers = decoder.decompress(buffer); 401 | expect(headers).to.eql(expected); 402 | }); 403 | }); 404 | 405 | describe('Response Examples with Huffman Coding', function () { 406 | var decoder = hpack.createContext(); 407 | 408 | it('decodes the first request headers', function () { 409 | var buffer = new Buffer([ 410 | 0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 411 | 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 412 | 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 413 | 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x82, 0xa6, 414 | 0x2d, 0x1b, 0xff, 0x0f, 0x1f, 0x91, 0x9d, 0x29, 415 | 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 416 | 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3 417 | ]); 418 | 419 | var expected = [ 420 | [ ':status', '302' ], 421 | [ 'cache-control', 'private' ], 422 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 423 | [ 'location', 'https://www.example.com' ] 424 | ]; 425 | 426 | var headers = decoder.decompress(buffer); 427 | expect(headers).to.eql(expected); 428 | }); 429 | 430 | it('decodes the second request headers', function () { 431 | var buffer = new Buffer([ 432 | 0x48, 0x83, 0x64, 0x0e, 0xff, 0xc0, 0xbf, 0x0f, 433 | 0x1f, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 434 | 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 435 | 0xae, 0x43, 0xd3 436 | ]); 437 | 438 | var expected = [ 439 | [ ':status', '307' ], 440 | [ 'cache-control', 'private' ], 441 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 442 | [ 'location', 'https://www.example.com' ] 443 | ]; 444 | 445 | var headers = decoder.decompress(buffer); 446 | expect(headers).to.eql(expected); 447 | }); 448 | 449 | it('decodes the third request headers', function () { 450 | var cookie = 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'; 451 | var buffer = new Buffer([ 452 | 0x88, 0xc0, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 453 | 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 454 | 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 455 | 0x1b, 0xff, 0x0f, 0x1f, 0x91, 0x9d, 0x29, 0xad, 456 | 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 457 | 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3, 0x5a, 0x83, 458 | 0x9b, 0xd9, 0xab, 0x0f, 0x28, 0xad, 0x94, 0xe7, 459 | 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7, 0xb3, 0x35, 460 | 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5, 0xaf, 461 | 0x27, 0x08, 0x7f, 0x36, 0x72, 0xc1, 0xab, 0x27, 462 | 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 463 | 0x65, 0xc0, 0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 464 | 0x3d, 0x50, 0x07 465 | ]); 466 | 467 | var expected = [ 468 | [ ':status', '200' ], 469 | [ 'cache-control', 'private' ], 470 | [ 'date', 'Mon, 21 Oct 2013 20:13:22 GMT' ], 471 | [ 'location', 'https://www.example.com' ], 472 | [ 'content-encoding', 'gzip' ], 473 | [ 'set-cookie', cookie ] 474 | ]; 475 | 476 | var headers = decoder.decompress(buffer); 477 | expect(headers).to.eql(expected); 478 | }); 479 | }); 480 | 481 | describe('Response Examples without Huffman Coding', function () { 482 | var decoder = hpack.createContext({ huffman: false }); 483 | 484 | it('decodes the first request headers', function () { 485 | var buffer = new Buffer([ 486 | 0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 487 | 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x61, 0x1d, 488 | 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 489 | 0x4f, 0x63, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 490 | 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 491 | 0x31, 0x20, 0x47, 0x4d, 0x54, 0x0f, 0x1f, 0x17, 492 | 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 493 | 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 494 | 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d 495 | ]); 496 | 497 | var expected = [ 498 | [ ':status', '302' ], 499 | [ 'cache-control', 'private' ], 500 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 501 | [ 'location', 'https://www.example.com' ] 502 | ]; 503 | 504 | var headers = decoder.decompress(buffer); 505 | expect(headers).to.eql(expected); 506 | }); 507 | 508 | it('decodes the second request headers', function () { 509 | var buffer = new Buffer([ 510 | 0x48, 0x03, 0x33, 0x30, 0x37, 0xc0, 0xbf, 0x0f, 511 | 0x1f, 0x17, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 512 | 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 513 | 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 514 | 0x6d 515 | ]); 516 | 517 | var expected = [ 518 | [ ':status', '307' ], 519 | [ 'cache-control', 'private' ], 520 | [ 'date', 'Mon, 21 Oct 2013 20:13:21 GMT' ], 521 | [ 'location', 'https://www.example.com' ] 522 | ]; 523 | 524 | var headers = decoder.decompress(buffer); 525 | expect(headers).to.eql(expected); 526 | }); 527 | 528 | it('decodes the third request headers', function () { 529 | var cookie = 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1'; 530 | var buffer = new Buffer([ 531 | 0x88, 0xc0, 0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 532 | 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20, 533 | 0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 534 | 0x31, 0x33, 0x3a, 0x32, 0x32, 0x20, 0x47, 0x4d, 535 | 0x54, 0x0f, 0x1f, 0x17, 0x68, 0x74, 0x74, 0x70, 536 | 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 537 | 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 538 | 0x63, 0x6f, 0x6d, 0x5a, 0x04, 0x67, 0x7a, 0x69, 539 | 0x70, 0x0f, 0x28, 0x38, 0x66, 0x6f, 0x6f, 0x3d, 540 | 0x41, 0x53, 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 541 | 0x42, 0x5a, 0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 542 | 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45, 543 | 0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 544 | 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33, 0x36, 0x30, 545 | 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 546 | 0x6f, 0x6e, 0x3d, 0x31 547 | ]); 548 | 549 | var expected = [ 550 | [ ':status', '200' ], 551 | [ 'cache-control', 'private' ], 552 | [ 'date', 'Mon, 21 Oct 2013 20:13:22 GMT' ], 553 | [ 'location', 'https://www.example.com' ], 554 | [ 'content-encoding', 'gzip' ], 555 | [ 'set-cookie', cookie ] 556 | ]; 557 | 558 | var headers = decoder.decompress(buffer); 559 | expect(headers).to.eql(expected); 560 | }); 561 | }); 562 | }); 563 | }); -------------------------------------------------------------------------------- /test/stream_test.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | 3 | var expect = require('expect.js'), 4 | waitress = require('waitress'); 5 | 6 | var Stream = require('../lib/stream'), 7 | hpack = require('../lib/hpack'), 8 | framer = require('../lib/framer'), 9 | protocol = require('../lib/protocol'); 10 | 11 | describe('Stream', function () { 12 | var socket = new events.EventEmitter(); 13 | 14 | var createStream = function (type) { 15 | var options = { 16 | id: (type === 'client') ? 1 : 2, 17 | initialWindowSize: protocol.INITIAL_WINDOW_SIZE, 18 | compressor: hpack.createContext(), 19 | decompressor: hpack.createContext(), 20 | promised: false 21 | }; 22 | return new Stream(options); 23 | }; 24 | 25 | describe('setPrioroty()', function () { 26 | it('sends PRIORITY', function (done) { 27 | var stream = createStream('client'); 28 | stream.state = protocol.STATE_OPEN; 29 | 30 | var streamDependency = 3; 31 | var weight = 1; 32 | var exclusive = true; 33 | 34 | stream.on('send', function (frame) { 35 | expect(frame.type).to.be(protocol.FRAME_TYPE_PRIORITY); 36 | expect(frame.streamDependency).to.be(streamDependency); 37 | expect(frame.weight).to.be(weight); 38 | expect(frame.exclusive).to.be(exclusive); 39 | done(); 40 | }); 41 | 42 | stream.setPrioroty(streamDependency, weight, exclusive); 43 | }); 44 | }); 45 | 46 | describe('cancel()', function () { 47 | it('sends RST_STREAM', function (done) { 48 | var stream = createStream('client'); 49 | stream.state = protocol.STATE_OPEN; 50 | 51 | stream.on('send', function (frame) { 52 | expect(frame.type).to.be(protocol.FRAME_TYPE_RST_STREAM); 53 | expect(frame.errorCode).to.be(protocol.CODE_CANCEL); 54 | done(); 55 | }); 56 | 57 | stream.cancel(); 58 | }); 59 | }); 60 | 61 | describe('sendDataFrame()', function () { 62 | it('sends DATA', function (done) { 63 | var stream = createStream('client'); 64 | stream.state = protocol.STATE_OPEN; 65 | 66 | var data = 'test'; 67 | var options = { 68 | endStream: true, 69 | endSegment: true, 70 | padding: 8 71 | }; 72 | 73 | stream.on('send', function (frame) { 74 | expect(frame.type).to.be(protocol.FRAME_TYPE_DATA); 75 | expect(frame.endStream).to.be(options.endStream); 76 | expect(frame.endSegment).to.be(options.endSegment); 77 | expect(frame.padding).to.be(options.padding); 78 | expect(frame.data).to.eql(new Buffer(data)); 79 | done(); 80 | }); 81 | 82 | stream.sendDataFrame('test', options); 83 | }); 84 | }); 85 | 86 | describe('sendDataFrame()', function () { 87 | it('sends DATA', function (done) { 88 | var stream = createStream('client'); 89 | stream.state = protocol.STATE_OPEN; 90 | 91 | var data = 'test'; 92 | var options = { 93 | endStream: true, 94 | endSegment: true, 95 | padding: 8 96 | }; 97 | 98 | stream.on('send', function (frame) { 99 | expect(frame.type).to.be(protocol.FRAME_TYPE_DATA); 100 | expect(frame.endStream).to.be(options.endStream); 101 | expect(frame.endSegment).to.be(options.endSegment); 102 | expect(frame.padding).to.be(options.padding); 103 | expect(frame.data).to.eql(new Buffer(data)); 104 | done(); 105 | }); 106 | 107 | stream.sendDataFrame('test', options); 108 | }); 109 | }); 110 | 111 | describe('sendHeadersFrame()', function () { 112 | it('sends HEADERS', function (done) { 113 | var stream = createStream('client'); 114 | stream.state = protocol.STATE_OPEN; 115 | 116 | var headers = [ 117 | [ ':status', '200' ], 118 | ]; 119 | 120 | var options = { 121 | endStream: true, 122 | endHeaders: true, 123 | padding: 8 124 | }; 125 | 126 | stream.on('send', function (frame) { 127 | expect(frame.type).to.be(protocol.FRAME_TYPE_HEADERS); 128 | expect(frame.endStream).to.be(options.endStream); 129 | expect(frame.endHeaders).to.be(options.endHeaders); 130 | expect(frame.padding).to.be(options.padding); 131 | expect(frame.headers).to.eql(headers); 132 | done(); 133 | }); 134 | 135 | stream.sendHeadersFrame(headers, options); 136 | }); 137 | 138 | it('sends HEADERS and CONTINUATION', function (done) { 139 | var stream = createStream('client'); 140 | stream.state = protocol.STATE_OPEN; 141 | 142 | var value = ''; 143 | for (var i=0; i<20000; i++) { 144 | value += 'x'; 145 | } 146 | 147 | var headers = [ 148 | [ ':status', '200' ], 149 | [ 'x-custom1', value ], 150 | [ 'x-custom2', value ], 151 | ]; 152 | 153 | var options = { 154 | endStream: true, 155 | endHeaders: true, 156 | padding: 8 157 | }; 158 | 159 | var frames = []; 160 | var check = waitress(3, function() { 161 | expect(frames[0].type).to.be(protocol.FRAME_TYPE_HEADERS); 162 | expect(frames[0].length).to.be(protocol.FRAME_LEN_MAX); 163 | expect(frames[0].endStream).to.be(options.endStream); 164 | expect(frames[0].endHeaders).to.be(false); 165 | expect(frames[0].padding).to.be(options.padding); 166 | expect(frames[0].headers).to.eql(headers); 167 | 168 | expect(frames[1].type).to.be(protocol.FRAME_TYPE_CONTINUATION); 169 | expect(frames[1].length).to.be(protocol.FRAME_LEN_MAX); 170 | expect(frames[1].endHeaders).to.be(false); 171 | 172 | expect(frames[2].type).to.be(protocol.FRAME_TYPE_CONTINUATION); 173 | expect(frames[2].length).to.be(2270); 174 | expect(frames[2].endHeaders).to.be(true); 175 | 176 | done(); 177 | }); 178 | 179 | stream.on('send', function (frame) { 180 | frames.push(frame); 181 | check(); 182 | }); 183 | 184 | stream.sendHeadersFrame(headers, options); 185 | }); 186 | }); 187 | 188 | describe('sendPriorityFrame()', function () { 189 | it('sends PRIORITY', function (done) { 190 | var stream = createStream('client'); 191 | stream.state = protocol.STATE_OPEN; 192 | 193 | var streamDependency = 3; 194 | var weight = 1; 195 | var exclusive = true; 196 | 197 | stream.on('send', function (frame) { 198 | expect(frame.type).to.be(protocol.FRAME_TYPE_PRIORITY); 199 | expect(frame.streamDependency).to.be(streamDependency); 200 | expect(frame.weight).to.be(weight); 201 | expect(frame.exclusive).to.be(exclusive); 202 | done(); 203 | }); 204 | 205 | stream.sendPriorityFrame(streamDependency, weight, exclusive); 206 | }); 207 | }); 208 | 209 | describe('sendRstStreamFrame()', function () { 210 | it('sends RST_STREAM', function (done) { 211 | var stream = createStream('client'); 212 | stream.state = protocol.STATE_OPEN; 213 | 214 | var errorCode = protocol.CODE_NO_ERROR; 215 | 216 | stream.on('send', function (frame) { 217 | expect(frame.type).to.be(protocol.FRAME_TYPE_RST_STREAM); 218 | expect(frame.errorCode).to.be(errorCode); 219 | done(); 220 | }); 221 | 222 | stream.sendRstStreamFrame(errorCode); 223 | }); 224 | }); 225 | 226 | describe('sendPushPromiseFrame()', function () { 227 | it('sends PUSH_PROMISE', function (done) { 228 | var stream = createStream('client'); 229 | stream.state = protocol.STATE_OPEN; 230 | 231 | var promisedStreamId = 3; 232 | var headers = [ 233 | [ ':method', 'GET' ], 234 | [ ':scheme', 'http' ], 235 | [ ':path', '/' ] 236 | ]; 237 | 238 | var options = { 239 | endHeaders: true, 240 | padding: 8 241 | }; 242 | 243 | stream.on('send', function (frame) { 244 | expect(frame.type).to.be(protocol.FRAME_TYPE_PUSH_PROMISE); 245 | expect(frame.promisedStreamId).to.be(promisedStreamId); 246 | expect(frame.endHeaders).to.be(options.endHeaders); 247 | expect(frame.padding).to.be(options.padding); 248 | expect(frame.headers).to.eql(headers); 249 | done(); 250 | }); 251 | 252 | stream.sendPushPromiseFrame(promisedStreamId, headers, options); 253 | }); 254 | 255 | it('sends PUSH_PROMISE and CONTINUATION', function (done) { 256 | var stream = createStream('client'); 257 | stream.state = protocol.STATE_OPEN; 258 | 259 | var value = ''; 260 | for (var i=0; i<20000; i++) { 261 | value += 'x'; 262 | } 263 | 264 | var promisedStreamId = 3; 265 | var headers = [ 266 | [ ':status', '200' ], 267 | [ 'x-custom1', value ], 268 | [ 'x-custom2', value ], 269 | ]; 270 | 271 | var options = { 272 | endHeaders: true, 273 | padding: 8 274 | }; 275 | 276 | var frames = []; 277 | var check = waitress(3, function() { 278 | expect(frames[0].type).to.be(protocol.FRAME_TYPE_PUSH_PROMISE); 279 | expect(frames[0].length).to.be(protocol.FRAME_LEN_MAX); 280 | expect(frames[0].promisedStreamId).to.be(promisedStreamId); 281 | expect(frames[0].endHeaders).to.be(false); 282 | expect(frames[0].padding).to.be(options.padding); 283 | expect(frames[0].headers).to.eql(headers); 284 | 285 | expect(frames[1].type).to.be(protocol.FRAME_TYPE_CONTINUATION); 286 | expect(frames[1].length).to.be(protocol.FRAME_LEN_MAX); 287 | expect(frames[1].endHeaders).to.be(false); 288 | 289 | expect(frames[2].type).to.be(protocol.FRAME_TYPE_CONTINUATION); 290 | expect(frames[2].length).to.be(2274); 291 | expect(frames[2].endHeaders).to.be(true); 292 | 293 | done(); 294 | }); 295 | 296 | stream.on('send', function (frame) { 297 | frames.push(frame); 298 | check(); 299 | }); 300 | 301 | stream.sendPushPromiseFrame(promisedStreamId, headers, options); 302 | }); 303 | }); 304 | 305 | describe('process()', function () { 306 | context('receiving DATA', function () { 307 | it('emits data event', function (done) { 308 | var stream = createStream('client'); 309 | stream.state = protocol.STATE_OPEN; 310 | 311 | var data = 'test'; 312 | 313 | var frame = framer.createDataFrame(); 314 | frame.streamId = stream.id; 315 | frame.endStream = true; 316 | frame.endSegment = true; 317 | frame.setData(data); 318 | 319 | stream.on('data', function (frameData) { 320 | expect(frameData).to.eql(new Buffer(data)); 321 | done(); 322 | }); 323 | 324 | stream.process(frame); 325 | }); 326 | }); 327 | 328 | context('receiving HEADERS', function () { 329 | it('emits header event', function (done) { 330 | var stream = createStream('client'); 331 | stream.state = protocol.STATE_OPEN; 332 | 333 | var headers = [ 334 | [ ':status', '200' ], 335 | ]; 336 | var headerBlock = new Buffer([ 0x88 ]); 337 | 338 | var frame = framer.createHeadersFrame(); 339 | frame.streamId = stream.id; 340 | frame.endStream = true; 341 | frame.endHeaders = true; 342 | frame.setHeaders(headers); 343 | frame.setHeaderBlockFragment(headerBlock); 344 | 345 | stream.on('header', function (frameHeaders) { 346 | expect(headers).to.eql(frameHeaders); 347 | done(); 348 | }); 349 | 350 | stream.process(frame); 351 | }); 352 | }); 353 | 354 | context('receiving PRIORITY', function () { 355 | it('emits priority event', function (done) { 356 | var stream = createStream('client'); 357 | stream.state = protocol.STATE_OPEN; 358 | 359 | var streamDependency = 3; 360 | var weight = 1; 361 | var exclusive = true; 362 | 363 | var frame = framer.createPriorityFrame(); 364 | frame.streamId = stream.id; 365 | frame.setPriority(streamDependency, weight, exclusive); 366 | 367 | stream.on('priority', function (d, w, e) { 368 | expect(d).to.eql(streamDependency); 369 | expect(w).to.eql(weight); 370 | expect(e).to.eql(exclusive); 371 | done(); 372 | }); 373 | 374 | stream.process(frame); 375 | }); 376 | }); 377 | 378 | context('receiving RST_STREAM', function () { 379 | it('emits cancel event', function (done) { 380 | var stream = createStream('client'); 381 | stream.state = protocol.STATE_OPEN; 382 | 383 | var frame = framer.createRstStreamFrame(); 384 | frame.streamId = stream.id; 385 | frame.setErrorCode(protocol.CODE_CANCEL); 386 | 387 | stream.on('cancel', function () { 388 | done(); 389 | }); 390 | 391 | stream.process(frame); 392 | }); 393 | 394 | it('emits error event', function (done) { 395 | var stream = createStream('client'); 396 | stream.state = protocol.STATE_OPEN; 397 | 398 | var frame = framer.createRstStreamFrame(); 399 | frame.streamId = stream.id; 400 | frame.setErrorCode(protocol.CODE_STREAM_CLOSED); 401 | 402 | stream.on('error', function (errorCode) { 403 | expect(errorCode).to.eql(protocol.CODE_STREAM_CLOSED); 404 | done(); 405 | }); 406 | 407 | stream.process(frame); 408 | }); 409 | }); 410 | 411 | context('receiving CONTINUATION', function () { 412 | it('emits header event', function (done) { 413 | var stream = createStream('client'); 414 | stream.state = protocol.STATE_OPEN; 415 | 416 | var value = ''; 417 | for (var i=0; i<20000; i++) { 418 | value += 'x'; 419 | } 420 | 421 | var headers = [ 422 | [ ':status', '200' ], 423 | [ 'x-custom1', value ], 424 | [ 'x-custom2', value ], 425 | ]; 426 | var headerBlock = stream._compressor.compress(headers); 427 | 428 | var frame = framer.createHeadersFrame(); 429 | frame.streamId = stream.id; 430 | frame.endStream = true; 431 | frame.endHeaders = true; 432 | frame.setHeaderBlockFragment(headerBlock); 433 | 434 | stream.on('header', function (frameHeaders) { 435 | expect(frameHeaders).to.eql(headers); 436 | done(); 437 | }); 438 | 439 | var frames = stream._splitHeadersFrame(frame); 440 | for (var fi=0, flen=frames.length; fi