testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]:
michael@0 2 // the Serializer and the Deserializer
michael@0 3 // [1]: http://nodejs.org/api/stream.html#stream_class_stream_transform
michael@0 4 // [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
michael@0 5 var assert = require('assert');
michael@0 6
michael@0 7 var Transform = require('stream').Transform;
michael@0 8
michael@0 9 exports.Serializer = Serializer;
michael@0 10 exports.Deserializer = Deserializer;
michael@0 11
michael@0 12 var logData = Boolean(process.env.HTTP2_LOG_DATA);
michael@0 13
michael@0 14 var MAX_PAYLOAD_SIZE = 16383;
michael@0 15
michael@0 16 // Serializer
michael@0 17 // ----------
michael@0 18 //
michael@0 19 // Frame Objects
michael@0 20 // * * * * * * * --+---------------------------
michael@0 21 // | |
michael@0 22 // v v Buffers
michael@0 23 // [] -----> Payload Ser. --[buffers]--> Header Ser. --> * * * *
michael@0 24 // empty adds payload adds header
michael@0 25 // array buffers buffer
michael@0 26
michael@0 27 function Serializer(log, sizeLimit) {
michael@0 28 this._log = log.child({ component: 'serializer' });
michael@0 29 this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
michael@0 30 Transform.call(this, { objectMode: true });
michael@0 31 }
michael@0 32 Serializer.prototype = Object.create(Transform.prototype, { constructor: { value: Serializer } });
michael@0 33
michael@0 34 // When there's an incoming frame object, it first generates the frame type specific part of the
michael@0 35 // frame (payload), and then then adds the header part which holds fields that are common to all
michael@0 36 // frame types (like the length of the payload).
michael@0 37 Serializer.prototype._transform = function _transform(frame, encoding, done) {
michael@0 38 this._log.trace({ frame: frame }, 'Outgoing frame');
michael@0 39
michael@0 40 assert(frame.type in Serializer, 'Unknown frame type: ' + frame.type);
michael@0 41
michael@0 42 var buffers = [];
michael@0 43 Serializer[frame.type](frame, buffers);
michael@0 44 Serializer.commonHeader(frame, buffers);
michael@0 45
michael@0 46 assert(buffers[0].readUInt16BE(0) <= this._sizeLimit, 'Frame too large!');
michael@0 47
michael@0 48 for (var i = 0; i < buffers.length; i++) {
michael@0 49 if (logData) {
michael@0 50 this._log.trace({ data: buffers[i] }, 'Outgoing data');
michael@0 51 }
michael@0 52 this.push(buffers[i]);
michael@0 53 }
michael@0 54
michael@0 55 done();
michael@0 56 };
michael@0 57
michael@0 58 // Deserializer
michael@0 59 // ------------
michael@0 60 //
michael@0 61 // Buffers
michael@0 62 // * * * * --------+-------------------------
michael@0 63 // | |
michael@0 64 // v v Frame Objects
michael@0 65 // {} -----> Header Des. --{frame}--> Payload Des. --> * * * * * * *
michael@0 66 // empty adds parsed adds parsed
michael@0 67 // object header properties payload properties
michael@0 68
michael@0 69 function Deserializer(log, sizeLimit, role) {
michael@0 70 this._role = role;
michael@0 71 this._log = log.child({ component: 'deserializer' });
michael@0 72 this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
michael@0 73 Transform.call(this, { objectMode: true });
michael@0 74 this._next(COMMON_HEADER_SIZE);
michael@0 75 }
michael@0 76 Deserializer.prototype = Object.create(Transform.prototype, { constructor: { value: Deserializer } });
michael@0 77
michael@0 78 // The Deserializer is stateful, and it's two main alternating states are: *waiting for header* and
michael@0 79 // *waiting for payload*. The state is stored in the boolean property `_waitingForHeader`.
michael@0 80 //
michael@0 81 // When entering a new state, a `_buffer` is created that will hold the accumulated data (header or
michael@0 82 // payload). The `_cursor` is used to track the progress.
michael@0 83 Deserializer.prototype._next = function(size) {
michael@0 84 this._cursor = 0;
michael@0 85 this._buffer = new Buffer(size);
michael@0 86 this._waitingForHeader = !this._waitingForHeader;
michael@0 87 if (this._waitingForHeader) {
michael@0 88 this._frame = {};
michael@0 89 }
michael@0 90 };
michael@0 91
michael@0 92 // Parsing an incoming buffer is an iterative process because it can hold multiple frames if it's
michael@0 93 // large enough. A `cursor` is used to track the progress in parsing the incoming `chunk`.
michael@0 94 Deserializer.prototype._transform = function _transform(chunk, encoding, done) {
michael@0 95 var cursor = 0;
michael@0 96
michael@0 97 if (logData) {
michael@0 98 this._log.trace({ data: chunk }, 'Incoming data');
michael@0 99 }
michael@0 100
michael@0 101 while(cursor < chunk.length) {
michael@0 102 // The content of an incoming buffer is first copied to `_buffer`. If it can't hold the full
michael@0 103 // chunk, then only a part of it is copied.
michael@0 104 var toCopy = Math.min(chunk.length - cursor, this._buffer.length - this._cursor);
michael@0 105 chunk.copy(this._buffer, this._cursor, cursor, cursor + toCopy);
michael@0 106 this._cursor += toCopy;
michael@0 107 cursor += toCopy;
michael@0 108
michael@0 109 // When `_buffer` is full, it's content gets parsed either as header or payload depending on
michael@0 110 // the actual state.
michael@0 111
michael@0 112 // If it's header then the parsed data is stored in a temporary variable and then the
michael@0 113 // deserializer waits for the specified length payload.
michael@0 114 if ((this._cursor === this._buffer.length) && this._waitingForHeader) {
michael@0 115 var payloadSize = Deserializer.commonHeader(this._buffer, this._frame);
michael@0 116 if (payloadSize <= this._sizeLimit) {
michael@0 117 this._next(payloadSize);
michael@0 118 } else {
michael@0 119 this.emit('error', 'FRAME_SIZE_ERROR');
michael@0 120 return;
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 // If it's payload then the the frame object is finalized and then gets pushed out.
michael@0 125 // Unknown frame types are ignored.
michael@0 126 //
michael@0 127 // Note: If we just finished the parsing of a header and the payload length is 0, this branch
michael@0 128 // will also run.
michael@0 129 if ((this._cursor === this._buffer.length) && !this._waitingForHeader) {
michael@0 130 if (this._frame.type) {
michael@0 131 var error = Deserializer[this._frame.type](this._buffer, this._frame, this._role);
michael@0 132 if (error) {
michael@0 133 this._log.error('Incoming frame parsing error: ' + error);
michael@0 134 this.emit('error', 'PROTOCOL_ERROR');
michael@0 135 } else {
michael@0 136 this._log.trace({ frame: this._frame }, 'Incoming frame');
michael@0 137 this.push(this._frame);
michael@0 138 }
michael@0 139 } else {
michael@0 140 this._log.error('Unknown type incoming frame');
michael@0 141 this.emit('error', 'PROTOCOL_ERROR');
michael@0 142 }
michael@0 143 this._next(COMMON_HEADER_SIZE);
michael@0 144 }
michael@0 145 }
michael@0 146
michael@0 147 done();
michael@0 148 };
michael@0 149
michael@0 150 // [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-4.1)
michael@0 151 // --------------------------------------------------------------
michael@0 152 //
michael@0 153 // HTTP/2.0 frames share a common base format consisting of an 8-byte header followed by 0 to 65535
michael@0 154 // bytes of data.
michael@0 155 //
michael@0 156 // Additional size limits can be set by specific application uses. HTTP limits the frame size to
michael@0 157 // 16,383 octets.
michael@0 158 //
michael@0 159 // 0 1 2 3
michael@0 160 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 161 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 162 // | R | Length (14) | Type (8) | Flags (8) |
michael@0 163 // +-+-+---------------------------+---------------+---------------+
michael@0 164 // |R| Stream Identifier (31) |
michael@0 165 // +-+-------------------------------------------------------------+
michael@0 166 // | Frame Data (0...) ...
michael@0 167 // +---------------------------------------------------------------+
michael@0 168 //
michael@0 169 // The fields of the frame header are defined as:
michael@0 170 //
michael@0 171 // * R:
michael@0 172 // A reserved 2-bit field. The semantics of these bits are undefined and the bits MUST remain
michael@0 173 // unset (0) when sending and MUST be ignored when receiving.
michael@0 174 //
michael@0 175 // * Length:
michael@0 176 // The length of the frame data expressed as an unsigned 14-bit integer. The 8 bytes of the frame
michael@0 177 // header are not included in this value.
michael@0 178 //
michael@0 179 // * Type:
michael@0 180 // The 8-bit type of the frame. The frame type determines how the remainder of the frame header
michael@0 181 // and data are interpreted. Implementations MUST ignore unsupported and unrecognized frame types.
michael@0 182 //
michael@0 183 // * Flags:
michael@0 184 // An 8-bit field reserved for frame-type specific boolean flags.
michael@0 185 //
michael@0 186 // Flags are assigned semantics specific to the indicated frame type. Flags that have no defined
michael@0 187 // semantics for a particular frame type MUST be ignored, and MUST be left unset (0) when sending.
michael@0 188 //
michael@0 189 // * R:
michael@0 190 // A reserved 1-bit field. The semantics of this bit are undefined and the bit MUST remain unset
michael@0 191 // (0) when sending and MUST be ignored when receiving.
michael@0 192 //
michael@0 193 // * Stream Identifier:
michael@0 194 // A 31-bit stream identifier. The value 0 is reserved for frames that are associated with the
michael@0 195 // connection as a whole as opposed to an individual stream.
michael@0 196 //
michael@0 197 // The structure and content of the remaining frame data is dependent entirely on the frame type.
michael@0 198
michael@0 199 var COMMON_HEADER_SIZE = 8;
michael@0 200
michael@0 201 var frameTypes = [];
michael@0 202
michael@0 203 var frameFlags = {};
michael@0 204
michael@0 205 var genericAttributes = ['type', 'flags', 'stream'];
michael@0 206
michael@0 207 var typeSpecificAttributes = {};
michael@0 208
michael@0 209 Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
michael@0 210 var headerBuffer = new Buffer(COMMON_HEADER_SIZE);
michael@0 211
michael@0 212 var size = 0;
michael@0 213 for (var i = 0; i < buffers.length; i++) {
michael@0 214 size += buffers[i].length;
michael@0 215 }
michael@0 216 headerBuffer.writeUInt16BE(size, 0);
michael@0 217
michael@0 218 var typeId = frameTypes.indexOf(frame.type); // If we are here then the type is valid for sure
michael@0 219 headerBuffer.writeUInt8(typeId, 2);
michael@0 220
michael@0 221 var flagByte = 0;
michael@0 222 for (var flag in frame.flags) {
michael@0 223 var position = frameFlags[frame.type].indexOf(flag);
michael@0 224 assert(position !== -1, 'Unknown flag for frame type ' + frame.type + ': ' + flag);
michael@0 225 if (frame.flags[flag]) {
michael@0 226 flagByte |= (1 << position);
michael@0 227 }
michael@0 228 }
michael@0 229 headerBuffer.writeUInt8(flagByte, 3);
michael@0 230
michael@0 231 assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream);
michael@0 232 headerBuffer.writeUInt32BE(frame.stream || 0, 4);
michael@0 233
michael@0 234 buffers.unshift(headerBuffer);
michael@0 235 };
michael@0 236
michael@0 237 Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
michael@0 238 var length = buffer.readUInt16BE(0);
michael@0 239
michael@0 240 frame.type = frameTypes[buffer.readUInt8(2)];
michael@0 241
michael@0 242 frame.flags = {};
michael@0 243 var flagByte = buffer.readUInt8(3);
michael@0 244 var definedFlags = frameFlags[frame.type];
michael@0 245 for (var i = 0; i < definedFlags.length; i++) {
michael@0 246 frame.flags[definedFlags[i]] = Boolean(flagByte & (1 << i));
michael@0 247 }
michael@0 248
michael@0 249 frame.stream = buffer.readUInt32BE(4) & 0x7fffffff;
michael@0 250
michael@0 251 return length;
michael@0 252 };
michael@0 253
michael@0 254 // Frame types
michael@0 255 // ===========
michael@0 256
michael@0 257 // Every frame type is registered in the following places:
michael@0 258 //
michael@0 259 // * `frameTypes`: a register of frame type codes (used by `commonHeader()`)
michael@0 260 // * `frameFlags`: a register of valid flags for frame types (used by `commonHeader()`)
michael@0 261 // * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by
michael@0 262 // logging code and also serves as documentation for frame objects)
michael@0 263
michael@0 264 // [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.1)
michael@0 265 // ------------------------------------------------------------
michael@0 266 //
michael@0 267 // DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
michael@0 268 // stream.
michael@0 269 //
michael@0 270 // The DATA frame defines the following flags:
michael@0 271 //
michael@0 272 // * END_STREAM (0x1):
michael@0 273 // Bit 1 being set indicates that this frame is the last that the endpoint will send for the
michael@0 274 // identified stream.
michael@0 275 // * END_SEGMENT (0x2):
michael@0 276 // Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
michael@0 277 // MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
michael@0 278 // forwarding frames.
michael@0 279 // * PAD_LOW (0x10):
michael@0 280 // Bit 5 being set indicates that the Pad Low field is present.
michael@0 281 // * PAD_HIGH (0x20):
michael@0 282 // Bit 6 being set indicates that the Pad High field is present. This bit MUST NOT be set unless
michael@0 283 // the PAD_LOW flag is also set. Endpoints that receive a frame with PAD_HIGH set and PAD_LOW
michael@0 284 // cleared MUST treat this as a connection error of type PROTOCOL_ERROR.
michael@0 285
michael@0 286 frameTypes[0x0] = 'DATA';
michael@0 287
michael@0 288 frameFlags.DATA = ['END_STREAM', 'END_SEGMENT', 'RESERVED4', 'RESERVED8', 'PAD_LOW', 'PAD_HIGH'];
michael@0 289
michael@0 290 typeSpecificAttributes.DATA = ['data'];
michael@0 291
michael@0 292 Serializer.DATA = function writeData(frame, buffers) {
michael@0 293 buffers.push(frame.data);
michael@0 294 };
michael@0 295
michael@0 296 Deserializer.DATA = function readData(buffer, frame) {
michael@0 297 var dataOffset = 0;
michael@0 298 var paddingLength = 0;
michael@0 299 if (frame.flags.PAD_LOW) {
michael@0 300 if (frame.flags.PAD_HIGH) {
michael@0 301 paddingLength = (buffer.readUInt8(dataOffset) & 0xff) * 256;
michael@0 302 dataOffset += 1;
michael@0 303 }
michael@0 304 paddingLength += (buffer.readUInt8(dataOffset) & 0xff);
michael@0 305 dataOffset += 1;
michael@0 306 } else if (frame.flags.PAD_HIGH) {
michael@0 307 return 'DATA frame got PAD_HIGH without PAD_LOW';
michael@0 308 }
michael@0 309 if (paddingLength) {
michael@0 310 frame.data = buffer.slice(dataOffset, -1 * paddingLength);
michael@0 311 } else {
michael@0 312 frame.data = buffer.slice(dataOffset);
michael@0 313 }
michael@0 314 };
michael@0 315
michael@0 316 // [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.2)
michael@0 317 // --------------------------------------------------------------
michael@0 318 //
michael@0 319 // The HEADERS frame (type=0x1) allows the sender to create a stream.
michael@0 320 //
michael@0 321 // The HEADERS frame defines the following flags:
michael@0 322 //
michael@0 323 // * END_STREAM (0x1):
michael@0 324 // Bit 1 being set indicates that this frame is the last that the endpoint will send for the
michael@0 325 // identified stream.
michael@0 326 // * END_SEGMENT (0x2):
michael@0 327 // Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
michael@0 328 // MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
michael@0 329 // forwarding frames.
michael@0 330 // * END_HEADERS (0x4):
michael@0 331 // The END_HEADERS bit indicates that this frame contains the entire payload necessary to provide
michael@0 332 // a complete set of headers.
michael@0 333 // * PRIORITY (0x8):
michael@0 334 // Bit 4 being set indicates that the first four octets of this frame contain a single reserved
michael@0 335 // bit and a 31-bit priority.
michael@0 336 // * PAD_LOW (0x10):
michael@0 337 // Bit 5 being set indicates that the Pad Low field is present.
michael@0 338 // * PAD_HIGH (0x20):
michael@0 339 // Bit 6 being set indicates that the Pad High field is present. This bit MUST NOT be set unless
michael@0 340 // the PAD_LOW flag is also set. Endpoints that receive a frame with PAD_HIGH set and PAD_LOW
michael@0 341 // cleared MUST treat this as a connection error of type PROTOCOL_ERROR.
michael@0 342
michael@0 343 frameTypes[0x1] = 'HEADERS';
michael@0 344
michael@0 345 frameFlags.HEADERS = ['END_STREAM', 'END_SEGMENT', 'END_HEADERS', 'PRIORITY', 'PAD_LOW', 'PAD_HIGH'];
michael@0 346
michael@0 347 typeSpecificAttributes.HEADERS = ['priority', 'headers', 'data'];
michael@0 348
michael@0 349 // 0 1 2 3
michael@0 350 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 351 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 352 // |X| (Optional) Priority (31) |
michael@0 353 // +-+-------------------------------------------------------------+
michael@0 354 // | Header Block (*) ...
michael@0 355 // +---------------------------------------------------------------+
michael@0 356 //
michael@0 357 // The payload of a HEADERS frame contains a Headers Block
michael@0 358
michael@0 359 Serializer.HEADERS = function writeHeadersPriority(frame, buffers) {
michael@0 360 if (frame.flags.PRIORITY) {
michael@0 361 var buffer = new Buffer(4);
michael@0 362 assert((0 <= frame.priority) && (frame.priority <= 0xffffffff), frame.priority);
michael@0 363 buffer.writeUInt32BE(frame.priority, 0);
michael@0 364 buffers.push(buffer);
michael@0 365 }
michael@0 366 buffers.push(frame.data);
michael@0 367 };
michael@0 368
michael@0 369 Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
michael@0 370 var dataOffset = 0;
michael@0 371 var paddingLength = 0;
michael@0 372 if (frame.flags.PAD_LOW) {
michael@0 373 if (frame.flags.PAD_HIGH) {
michael@0 374 paddingLength = (buffer.readUInt8(dataOffset) & 0xff) * 256;
michael@0 375 dataOffset += 1;
michael@0 376 }
michael@0 377 paddingLength += (buffer.readUInt8(dataOffset) & 0xff);
michael@0 378 dataOffset += 1;
michael@0 379 } else if (frame.flags.PAD_HIGH) {
michael@0 380 return 'HEADERS frame got PAD_HIGH without PAD_LOW';
michael@0 381 }
michael@0 382 if (frame.flags.PRIORITY) {
michael@0 383 frame.priority = buffer.readUInt32BE(dataOffset) & 0x7fffffff;
michael@0 384 dataOffset += 4;
michael@0 385 }
michael@0 386 if (paddingLength) {
michael@0 387 frame.data = buffer.slice(dataOffset, -1 * paddingLength);
michael@0 388 } else {
michael@0 389 frame.data = buffer.slice(dataOffset);
michael@0 390 }
michael@0 391 };
michael@0 392
michael@0 393 // [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.3)
michael@0 394 // -------------------------------------------------------
michael@0 395 //
michael@0 396 // The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
michael@0 397 //
michael@0 398 // The PRIORITY frame does not define any flags.
michael@0 399
michael@0 400 frameTypes[0x2] = 'PRIORITY';
michael@0 401
michael@0 402 frameFlags.PRIORITY = [];
michael@0 403
michael@0 404 typeSpecificAttributes.PRIORITY = ['priority'];
michael@0 405
michael@0 406 // 0 1 2 3
michael@0 407 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 408 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 409 // |X| Priority (31) |
michael@0 410 // +-+-------------------------------------------------------------+
michael@0 411 //
michael@0 412 // The payload of a PRIORITY frame contains a single reserved bit and a 31-bit priority.
michael@0 413
michael@0 414 Serializer.PRIORITY = function writePriority(frame, buffers) {
michael@0 415 var buffer = new Buffer(4);
michael@0 416 buffer.writeUInt32BE(frame.priority, 0);
michael@0 417 buffers.push(buffer);
michael@0 418 };
michael@0 419
michael@0 420 Deserializer.PRIORITY = function readPriority(buffer, frame) {
michael@0 421 frame.priority = buffer.readUInt32BE(0);
michael@0 422 };
michael@0 423
michael@0 424 // [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.4)
michael@0 425 // -----------------------------------------------------------
michael@0 426 //
michael@0 427 // The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
michael@0 428 //
michael@0 429 // No type-flags are defined.
michael@0 430
michael@0 431 frameTypes[0x3] = 'RST_STREAM';
michael@0 432
michael@0 433 frameFlags.RST_STREAM = [];
michael@0 434
michael@0 435 typeSpecificAttributes.RST_STREAM = ['error'];
michael@0 436
michael@0 437 // 0 1 2 3
michael@0 438 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 439 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 440 // | Error Code (32) |
michael@0 441 // +---------------------------------------------------------------+
michael@0 442 //
michael@0 443 // The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the error
michael@0 444 // code (see Error Codes). The error code indicates why the stream is being terminated.
michael@0 445
michael@0 446 Serializer.RST_STREAM = function writeRstStream(frame, buffers) {
michael@0 447 var buffer = new Buffer(4);
michael@0 448 var code = errorCodes.indexOf(frame.error);
michael@0 449 assert((0 <= code) && (code <= 0xffffffff), code);
michael@0 450 buffer.writeUInt32BE(code, 0);
michael@0 451 buffers.push(buffer);
michael@0 452 };
michael@0 453
michael@0 454 Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
michael@0 455 frame.error = errorCodes[buffer.readUInt32BE(0)];
michael@0 456 };
michael@0 457
michael@0 458 // [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.5)
michael@0 459 // -------------------------------------------------------
michael@0 460 //
michael@0 461 // The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
michael@0 462 // communicate.
michael@0 463 //
michael@0 464 // The SETTINGS frame defines the following flag:
michael@0 465
michael@0 466 // * ACK (0x1):
michael@0 467 // Bit 1 being set indicates that this frame acknowledges receipt and application of the peer's
michael@0 468 // SETTINGS frame.
michael@0 469 frameTypes[0x4] = 'SETTINGS';
michael@0 470
michael@0 471 frameFlags.SETTINGS = ['ACK'];
michael@0 472
michael@0 473 typeSpecificAttributes.SETTINGS = ['settings'];
michael@0 474
michael@0 475 // The payload of a SETTINGS frame consists of zero or more settings. Each setting consists of an
michael@0 476 // 8-bit identifier, and an unsigned 32-bit value.
michael@0 477 //
michael@0 478 // 0 1 2 3
michael@0 479 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 480 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 481 // | Identifier(8) | Value (32) |
michael@0 482 // +-----------------+---------------------------------------------+
michael@0 483 // ...Value |
michael@0 484 // +-----------------+
michael@0 485 //
michael@0 486 // Each setting in a SETTINGS frame replaces the existing value for that setting. Settings are
michael@0 487 // processed in the order in which they appear, and a receiver of a SETTINGS frame does not need to
michael@0 488 // maintain any state other than the current value of settings. Therefore, the value of a setting
michael@0 489 // is the last value that is seen by a receiver. This permits the inclusion of the same settings
michael@0 490 // multiple times in the same SETTINGS frame, though doing so does nothing other than waste
michael@0 491 // connection capacity.
michael@0 492
michael@0 493 Serializer.SETTINGS = function writeSettings(frame, buffers) {
michael@0 494 var settings = [], settingsLeft = Object.keys(frame.settings);
michael@0 495 definedSettings.forEach(function(setting, id) {
michael@0 496 if (setting.name in frame.settings) {
michael@0 497 settingsLeft.splice(settingsLeft.indexOf(setting.name), 1);
michael@0 498 var value = frame.settings[setting.name];
michael@0 499 settings.push({ id: id, value: setting.flag ? Boolean(value) : value });
michael@0 500 }
michael@0 501 });
michael@0 502 assert(settingsLeft.length === 0, 'Unknown settings: ' + settingsLeft.join(', '));
michael@0 503
michael@0 504 var buffer = new Buffer(settings.length * 5);
michael@0 505 for (var i = 0; i < settings.length; i++) {
michael@0 506 buffer.writeUInt8(settings[i].id & 0xff, i*5);
michael@0 507 buffer.writeUInt32BE(settings[i].value, i*5 + 1);
michael@0 508 }
michael@0 509
michael@0 510 buffers.push(buffer);
michael@0 511 };
michael@0 512
michael@0 513 Deserializer.SETTINGS = function readSettings(buffer, frame, role) {
michael@0 514 frame.settings = {};
michael@0 515
michael@0 516 if (buffer.length % 5 !== 0) {
michael@0 517 return 'Invalid SETTINGS frame';
michael@0 518 }
michael@0 519 for (var i = 0; i < buffer.length / 5; i++) {
michael@0 520 var id = buffer.readUInt8(i*5) & 0xff;
michael@0 521 var setting = definedSettings[id];
michael@0 522 if (setting) {
michael@0 523 if (role == 'CLIENT' && setting.name == 'SETTINGS_ENABLE_PUSH') {
michael@0 524 return 'SETTINGS frame on client got SETTINGS_ENABLE_PUSH';
michael@0 525 }
michael@0 526 var value = buffer.readUInt32BE(i*5 + 1);
michael@0 527 frame.settings[setting.name] = setting.flag ? Boolean(value & 0x1) : value;
michael@0 528 } else {
michael@0 529 /* Unknown setting, protocol error */
michael@0 530 return 'SETTINGS frame got unknown setting type';
michael@0 531 }
michael@0 532 }
michael@0 533 };
michael@0 534
michael@0 535 // The following settings are defined:
michael@0 536 var definedSettings = [];
michael@0 537
michael@0 538 // * SETTINGS_HEADER_TABLE_SIZE (1):
michael@0 539 // Allows the sender to inform the remote endpoint of the size of the header compression table
michael@0 540 // used to decode header blocks.
michael@0 541 definedSettings[1] = { name: 'SETTINGS_HEADER_TABLE_SIZE', flag: false };
michael@0 542
michael@0 543 // * SETTINGS_ENABLE_PUSH (2):
michael@0 544 // This setting can be use to disable server push. An endpoint MUST NOT send a PUSH_PROMISE frame
michael@0 545 // if it receives this setting set to a value of 0. The default value is 1, which indicates that
michael@0 546 // push is permitted.
michael@0 547 definedSettings[2] = { name: 'SETTINGS_ENABLE_PUSH', flag: true };
michael@0 548
michael@0 549 // * SETTINGS_MAX_CONCURRENT_STREAMS (4):
michael@0 550 // indicates the maximum number of concurrent streams that the sender will allow.
michael@0 551 definedSettings[3] = { name: 'SETTINGS_MAX_CONCURRENT_STREAMS', flag: false };
michael@0 552
michael@0 553 // * SETTINGS_INITIAL_WINDOW_SIZE (7):
michael@0 554 // indicates the sender's initial stream window size (in bytes) for new streams.
michael@0 555 definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
michael@0 556
michael@0 557 // [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.6)
michael@0 558 // ---------------------------------------------------------------
michael@0 559 //
michael@0 560 // The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
michael@0 561 // sender intends to initiate.
michael@0 562 //
michael@0 563 // The PUSH_PROMISE frame defines the following flags:
michael@0 564 //
michael@0 565 // * END_PUSH_PROMISE (0x4):
michael@0 566 // The END_PUSH_PROMISE bit indicates that this frame contains the entire payload necessary to
michael@0 567 // provide a complete set of headers.
michael@0 568
michael@0 569 frameTypes[0x5] = 'PUSH_PROMISE';
michael@0 570
michael@0 571 frameFlags.PUSH_PROMISE = ['RESERVED1', 'RESERVED2', 'END_PUSH_PROMISE'];
michael@0 572
michael@0 573 typeSpecificAttributes.PUSH_PROMISE = ['promised_stream', 'headers', 'data'];
michael@0 574
michael@0 575 // 0 1 2 3
michael@0 576 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 577 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 578 // |X| Promised-Stream-ID (31) |
michael@0 579 // +-+-------------------------------------------------------------+
michael@0 580 // | Header Block (*) ...
michael@0 581 // +---------------------------------------------------------------+
michael@0 582 //
michael@0 583 // The PUSH_PROMISE frame includes the unsigned 31-bit identifier of
michael@0 584 // the stream the endpoint plans to create along with a minimal set of headers that provide
michael@0 585 // additional context for the stream.
michael@0 586
michael@0 587 Serializer.PUSH_PROMISE = function writePushPromise(frame, buffers) {
michael@0 588 var buffer = new Buffer(4);
michael@0 589
michael@0 590 var promised_stream = frame.promised_stream;
michael@0 591 assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream);
michael@0 592 buffer.writeUInt32BE(promised_stream, 0);
michael@0 593
michael@0 594 buffers.push(buffer);
michael@0 595 buffers.push(frame.data);
michael@0 596 };
michael@0 597
michael@0 598 Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) {
michael@0 599 frame.promised_stream = buffer.readUInt32BE(0) & 0x7fffffff;
michael@0 600 frame.data = buffer.slice(4);
michael@0 601 };
michael@0 602
michael@0 603 // [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.7)
michael@0 604 // -----------------------------------------------
michael@0 605 //
michael@0 606 // The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
michael@0 607 // sender, as well as determining whether an idle connection is still functional.
michael@0 608 //
michael@0 609 // The PING frame defines one type-specific flag:
michael@0 610 //
michael@0 611 // * ACK (0x1):
michael@0 612 // Bit 1 being set indicates that this PING frame is a PING response.
michael@0 613
michael@0 614 frameTypes[0x6] = 'PING';
michael@0 615
michael@0 616 frameFlags.PING = ['ACK'];
michael@0 617
michael@0 618 typeSpecificAttributes.PING = ['data'];
michael@0 619
michael@0 620 // In addition to the frame header, PING frames MUST contain 8 additional octets of opaque data.
michael@0 621
michael@0 622 Serializer.PING = function writePing(frame, buffers) {
michael@0 623 buffers.push(frame.data);
michael@0 624 };
michael@0 625
michael@0 626 Deserializer.PING = function readPing(buffer, frame) {
michael@0 627 if (buffer.length !== 8) {
michael@0 628 return 'Invalid size PING frame';
michael@0 629 }
michael@0 630 frame.data = buffer;
michael@0 631 };
michael@0 632
michael@0 633 // [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.8)
michael@0 634 // ---------------------------------------------------
michael@0 635 //
michael@0 636 // The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
michael@0 637 //
michael@0 638 // The GOAWAY frame does not define any flags.
michael@0 639
michael@0 640 frameTypes[0x7] = 'GOAWAY';
michael@0 641
michael@0 642 frameFlags.GOAWAY = [];
michael@0 643
michael@0 644 typeSpecificAttributes.GOAWAY = ['last_stream', 'error'];
michael@0 645
michael@0 646 // 0 1 2 3
michael@0 647 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
michael@0 648 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
michael@0 649 // |X| Last-Stream-ID (31) |
michael@0 650 // +-+-------------------------------------------------------------+
michael@0 651 // | Error Code (32) |
michael@0 652 // +---------------------------------------------------------------+
michael@0 653 //
michael@0 654 // The last stream identifier in the GOAWAY frame contains the highest numbered stream identifier
michael@0 655 // for which the sender of the GOAWAY frame has received frames on and might have taken some action
michael@0 656 // on.
michael@0 657 //
michael@0 658 // The GOAWAY frame also contains a 32-bit error code (see Error Codes) that contains the reason for
michael@0 659 // closing the connection.
michael@0 660
michael@0 661 Serializer.GOAWAY = function writeGoaway(frame, buffers) {
michael@0 662 var buffer = new Buffer(8);
michael@0 663
michael@0 664 var last_stream = frame.last_stream;
michael@0 665 assert((0 <= last_stream) && (last_stream <= 0x7fffffff), last_stream);
michael@0 666 buffer.writeUInt32BE(last_stream, 0);
michael@0 667
michael@0 668 var code = errorCodes.indexOf(frame.error);
michael@0 669 assert((0 <= code) && (code <= 0xffffffff), code);
michael@0 670 buffer.writeUInt32BE(code, 4);
michael@0 671
michael@0 672 buffers.push(buffer);
michael@0 673 };
michael@0 674
michael@0 675 Deserializer.GOAWAY = function readGoaway(buffer, frame) {
michael@0 676 frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff;
michael@0 677 frame.error = errorCodes[buffer.readUInt32BE(4)];
michael@0 678 };
michael@0 679
michael@0 680 // [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.9)
michael@0 681 // -----------------------------------------------------------------
michael@0 682 //
michael@0 683 // The WINDOW_UPDATE frame (type=0x8) is used to implement flow control.
michael@0 684 //
michael@0 685 // The WINDOW_UPDATE frame does not define any flags.
michael@0 686
michael@0 687 frameTypes[0x8] = 'WINDOW_UPDATE';
michael@0 688
michael@0 689 frameFlags.WINDOW_UPDATE = [];
michael@0 690
michael@0 691 typeSpecificAttributes.WINDOW_UPDATE = ['window_size'];
michael@0 692
michael@0 693 // The payload of a WINDOW_UPDATE frame is a 32-bit value indicating the additional number of bytes
michael@0 694 // that the sender can transmit in addition to the existing flow control window. The legal range
michael@0 695 // for this field is 1 to 2^31 - 1 (0x7fffffff) bytes; the most significant bit of this value is
michael@0 696 // reserved.
michael@0 697
michael@0 698 Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) {
michael@0 699 var buffer = new Buffer(4);
michael@0 700
michael@0 701 var window_size = frame.window_size;
michael@0 702 assert((0 <= window_size) && (window_size <= 0x7fffffff), window_size);
michael@0 703 buffer.writeUInt32BE(window_size, 0);
michael@0 704
michael@0 705 buffers.push(buffer);
michael@0 706 };
michael@0 707
michael@0 708 Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) {
michael@0 709 frame.window_size = buffer.readUInt32BE(0) & 0x7fffffff;
michael@0 710 };
michael@0 711
michael@0 712 // [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-6.10)
michael@0 713 // ------------------------------------------------------------
michael@0 714 //
michael@0 715 // The CONTINUATION frame (type=0xA) is used to continue a sequence of header block fragments.
michael@0 716 //
michael@0 717 // The CONTINUATION frame defines the following flag:
michael@0 718 //
michael@0 719 // * END_HEADERS (0x4):
michael@0 720 // The END_HEADERS bit indicates that this frame ends the sequence of header block fragments
michael@0 721 // necessary to provide a complete set of headers.
michael@0 722 // * PAD_LOW (0x10):
michael@0 723 // Bit 5 being set indicates that the Pad Low field is present.
michael@0 724 // * PAD_HIGH (0x20):
michael@0 725 // Bit 6 being set indicates that the Pad High field is present. This bit MUST NOT be set unless
michael@0 726 // the PAD_LOW flag is also set. Endpoints that receive a frame with PAD_HIGH set and PAD_LOW
michael@0 727 // cleared MUST treat this as a connection error of type PROTOCOL_ERROR.
michael@0 728
michael@0 729 frameTypes[0x9] = 'CONTINUATION';
michael@0 730
michael@0 731 frameFlags.CONTINUATION = ['RESERVED1', 'RESERVED2', 'END_HEADERS', 'RESERVED8', 'PAD_LOW', 'PAD_HIGH'];
michael@0 732
michael@0 733 typeSpecificAttributes.CONTINUATION = ['headers', 'data'];
michael@0 734
michael@0 735 Serializer.CONTINUATION = function writeContinuation(frame, buffers) {
michael@0 736 buffers.push(frame.data);
michael@0 737 };
michael@0 738
michael@0 739 Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
michael@0 740 var dataOffset = 0;
michael@0 741 var paddingLength = 0;
michael@0 742 if (frame.flags.PAD_LOW) {
michael@0 743 if (frame.flags.PAD_HIGH) {
michael@0 744 paddingLength = (buffer.readUInt8(dataOffset) & 0xff) * 256;
michael@0 745 dataOffset += 1;
michael@0 746 }
michael@0 747 paddingLength += (buffer.readUInt8(dataOffset) & 0xff);
michael@0 748 dataOffset += 1;
michael@0 749 } else if (frame.flags.PAD_HIGH) {
michael@0 750 return 'CONTINUATION frame got PAD_HIGH without PAD_LOW';
michael@0 751 }
michael@0 752 if (paddingLength) {
michael@0 753 frame.data = buffer.slice(dataOffset, -1 * paddingLength);
michael@0 754 } else {
michael@0 755 frame.data = buffer.slice(dataOffset);
michael@0 756 }
michael@0 757 };
michael@0 758
michael@0 759 // [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-7)
michael@0 760 // ------------------------------------------------------------
michael@0 761
michael@0 762 var errorCodes = [
michael@0 763 'NO_ERROR',
michael@0 764 'PROTOCOL_ERROR',
michael@0 765 'INTERNAL_ERROR',
michael@0 766 'FLOW_CONTROL_ERROR',
michael@0 767 'SETTINGS_TIMEOUT',
michael@0 768 'STREAM_CLOSED',
michael@0 769 'FRAME_SIZE_ERROR',
michael@0 770 'REFUSED_STREAM',
michael@0 771 'CANCEL',
michael@0 772 'COMPRESSION_ERROR',
michael@0 773 'CONNECT_ERROR'
michael@0 774 ];
michael@0 775 errorCodes[420] = 'ENHANCE_YOUR_CALM';
michael@0 776
michael@0 777 // Logging
michael@0 778 // -------
michael@0 779
michael@0 780 // [Bunyan serializers](https://github.com/trentm/node-bunyan#serializers) to improve logging output
michael@0 781 // for debug messages emitted in this component.
michael@0 782 exports.serializers = {};
michael@0 783
michael@0 784 // * `frame` serializer: it transforms data attributes from Buffers to hex strings and filters out
michael@0 785 // flags that are not present.
michael@0 786 var frameCounter = 0;
michael@0 787 exports.serializers.frame = function(frame) {
michael@0 788 if (!frame) {
michael@0 789 return null;
michael@0 790 }
michael@0 791
michael@0 792 if ('id' in frame) {
michael@0 793 return frame.id;
michael@0 794 }
michael@0 795
michael@0 796 frame.id = frameCounter;
michael@0 797 frameCounter += 1;
michael@0 798
michael@0 799 var logEntry = { id: frame.id };
michael@0 800 genericAttributes.concat(typeSpecificAttributes[frame.type]).forEach(function(name) {
michael@0 801 logEntry[name] = frame[name];
michael@0 802 });
michael@0 803
michael@0 804 if (frame.data instanceof Buffer) {
michael@0 805 if (logEntry.data.length > 50) {
michael@0 806 logEntry.data = frame.data.slice(0, 47).toString('hex') + '...';
michael@0 807 } else {
michael@0 808 logEntry.data = frame.data.toString('hex');
michael@0 809 }
michael@0 810
michael@0 811 if (!('length' in logEntry)) {
michael@0 812 logEntry.length = frame.data.length;
michael@0 813 }
michael@0 814 }
michael@0 815
michael@0 816 if (frame.promised_stream instanceof Object) {
michael@0 817 logEntry.promised_stream = 'stream-' + frame.promised_stream.id;
michael@0 818 }
michael@0 819
michael@0 820 logEntry.flags = Object.keys(frame.flags || {}).filter(function(name) {
michael@0 821 return frame.flags[name] === true;
michael@0 822 });
michael@0 823
michael@0 824 return logEntry;
michael@0 825 };
michael@0 826
michael@0 827 // * `data` serializer: it simply transforms a buffer to a hex string.
michael@0 828 exports.serializers.data = function(data) {
michael@0 829 return data.toString('hex');
michael@0 830 };

mercurial