190 lines
4.8 KiB
JavaScript
190 lines
4.8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
var parser = exports;
|
|
|
|
var spdy = require('../spdy'),
|
|
util = require('util'),
|
|
stream = require('stream'),
|
|
Buffer = require('buffer').Buffer;
|
|
|
|
//
|
|
// ### function Parser (connection, deflate, inflate)
|
|
// #### @connection {spdy.Connection} connection
|
|
// #### @deflate {zlib.Deflate} Deflate stream
|
|
// #### @inflate {zlib.Inflate} Inflate stream
|
|
// SPDY protocol frames parser's @constructor
|
|
//
|
|
function Parser(connection, deflate, inflate) {
|
|
stream.Stream.call(this);
|
|
|
|
this.drained = true;
|
|
this.paused = false;
|
|
this.buffer = [];
|
|
this.buffered = 0;
|
|
this.waiting = 8;
|
|
|
|
this.state = { type: 'frame-head' };
|
|
this.deflate = deflate;
|
|
this.inflate = inflate;
|
|
this.framer = null;
|
|
|
|
this.connection = connection;
|
|
|
|
this.readable = this.writable = true;
|
|
}
|
|
util.inherits(Parser, stream.Stream);
|
|
|
|
//
|
|
// ### function create (connection, deflate, inflate)
|
|
// #### @connection {spdy.Connection} connection
|
|
// #### @deflate {zlib.Deflate} Deflate stream
|
|
// #### @inflate {zlib.Inflate} Inflate stream
|
|
// @constructor wrapper
|
|
//
|
|
parser.create = function create(connection, deflate, inflate) {
|
|
return new Parser(connection, deflate, inflate);
|
|
};
|
|
|
|
//
|
|
// ### function write (data)
|
|
// #### @data {Buffer} chunk of data
|
|
// Writes or buffers data to parser
|
|
//
|
|
Parser.prototype.write = function write(data) {
|
|
if (data !== undefined) {
|
|
// Buffer data
|
|
this.buffer.push(data);
|
|
this.buffered += data.length;
|
|
}
|
|
|
|
// Notify caller about state (for piping)
|
|
if (this.paused) return false;
|
|
|
|
// We shall not do anything until we get all expected data
|
|
if (this.buffered < this.waiting) return;
|
|
|
|
// Mark parser as not drained
|
|
if (data !== undefined) this.drained = false;
|
|
|
|
var self = this,
|
|
buffer = new Buffer(this.waiting),
|
|
sliced = 0,
|
|
offset = 0;
|
|
|
|
while (this.waiting > offset && sliced < this.buffer.length) {
|
|
var chunk = this.buffer[sliced++],
|
|
overmatched = false;
|
|
|
|
// Copy chunk into `buffer`
|
|
if (chunk.length > this.waiting - offset) {
|
|
chunk.copy(buffer, offset, 0, this.waiting - offset);
|
|
|
|
this.buffer[--sliced] = chunk.slice(this.waiting - offset);
|
|
this.buffered += this.buffer[sliced].length;
|
|
|
|
overmatched = true;
|
|
} else {
|
|
chunk.copy(buffer, offset);
|
|
}
|
|
|
|
// Move offset and decrease amount of buffered data
|
|
offset += chunk.length;
|
|
this.buffered -= chunk.length;
|
|
|
|
if (overmatched) break;
|
|
}
|
|
|
|
// Remove used buffers
|
|
this.buffer = this.buffer.slice(sliced);
|
|
|
|
// Executed parser for buffered data
|
|
this.paused = true;
|
|
this.execute(this.state, buffer, function (err, waiting) {
|
|
// And unpause once execution finished
|
|
self.paused = false;
|
|
|
|
// Propagate errors
|
|
if (err) return self.emit('error', err);
|
|
|
|
// Set new `waiting`
|
|
self.waiting = waiting;
|
|
|
|
if (self.waiting <= self.buffered) {
|
|
self.write();
|
|
} else {
|
|
process.nextTick(function() {
|
|
if (self.drained) return;
|
|
|
|
// Mark parser as drained
|
|
self.drained = true;
|
|
self.emit('drain');
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
//
|
|
// ### function end ()
|
|
// Stream's end() implementation
|
|
//
|
|
Parser.prototype.end = function end() {
|
|
this.emit('end');
|
|
};
|
|
|
|
//
|
|
// ### function execute (state, data, callback)
|
|
// #### @state {Object} Parser's state
|
|
// #### @data {Buffer} Incoming data
|
|
// #### @callback {Function} continuation callback
|
|
// Parse buffered data
|
|
//
|
|
Parser.prototype.execute = function execute(state, data, callback) {
|
|
if (state.type === 'frame-head') {
|
|
var header = state.header = spdy.protocol.generic.parseHeader(data);
|
|
|
|
// Lazily create framer
|
|
if (!this.framer && header.control) {
|
|
if (spdy.protocol[header.version]) {
|
|
this.framer = new spdy.protocol[header.version].Framer(
|
|
spdy.utils.zwrap(this.deflate),
|
|
spdy.utils.zwrap(this.inflate)
|
|
);
|
|
|
|
// Propagate framer to connection
|
|
this.connection.framer = this.framer;
|
|
this.emit('_framer', this.framer);
|
|
}
|
|
}
|
|
|
|
state.type = 'frame-body';
|
|
callback(null, header.length);
|
|
} else if (state.type === 'frame-body') {
|
|
var self = this;
|
|
|
|
// Data frame
|
|
if (!state.header.control) {
|
|
return onFrame(null, {
|
|
type: 'DATA',
|
|
id: state.header.id,
|
|
fin: (state.header.flags & 0x01) === 0x01,
|
|
compressed: (state.header.flags & 0x02) === 0x02,
|
|
data: data
|
|
});
|
|
} else {
|
|
// Control frame
|
|
this.framer.execute(state.header, data, onFrame);
|
|
}
|
|
|
|
function onFrame(err, frame) {
|
|
if (err) return callback(err);
|
|
|
|
self.emit('frame', frame);
|
|
|
|
state.type = 'frame-head';
|
|
callback(null, 8);
|
|
};
|
|
}
|
|
};
|