Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
1 var framer = exports;
3 var spdy = require('../../../spdy'),
4 Buffer = require('buffer').Buffer,
5 protocol = require('./');
7 //
8 // ### function Framer (deflate, inflate)
9 // #### @deflate {zlib.Deflate} Deflate stream
10 // #### @inflate {zlib.Inflate} Inflate stream
11 // Framer constructor
12 //
13 function Framer(deflate, inflate) {
14 this.version = 2;
15 this.deflate = deflate;
16 this.inflate = inflate;
17 }
18 exports.Framer = Framer;
21 //
22 // ### function execute (header, body, callback)
23 // #### @header {Object} Frame headers
24 // #### @body {Buffer} Frame's body
25 // #### @callback {Function} Continuation callback
26 // Parse frame (decompress data and create streams)
27 //
28 Framer.prototype.execute = function execute(header, body, callback) {
29 // SYN_STREAM or SYN_REPLY
30 if (header.type === 0x01 || header.type === 0x02) {
31 var frame = protocol.parseSynHead(header.type, header.flags, body);
33 body = body.slice(frame._offset);
35 this.inflate(body, function(err, chunks, length) {
36 if (err) return callback(err);
38 var pairs = new Buffer(length);
39 for (var i = 0, offset = 0; i < chunks.length; i++) {
40 chunks[i].copy(pairs, offset);
41 offset += chunks[i].length;
42 }
44 frame.headers = protocol.parseHeaders(pairs);
45 frame.url = frame.headers.url || '';
47 callback(null, frame);
48 });
49 // RST_STREAM
50 } else if (header.type === 0x03) {
51 callback(null, protocol.parseRst(body));
52 // SETTINGS
53 } else if (header.type === 0x04) {
54 callback(null, { type: 'SETTINGS' });
55 } else if (header.type === 0x05) {
56 callback(null, { type: 'NOOP' });
57 // PING
58 } else if (header.type === 0x06) {
59 callback(null, { type: 'PING', pingId: body });
60 // GOAWAY
61 } else if (header.type === 0x07) {
62 callback(null, protocol.parseGoaway(body));
63 } else {
64 callback(null, { type: 'unknown: ' + header.type, body: body });
65 }
66 };
68 //
69 // internal, converts object into spdy dictionary
70 //
71 function headersToDict(headers, preprocess) {
72 function stringify(value) {
73 if (value !== undefined) {
74 if (Array.isArray(value)) {
75 return value.join('\x00');
76 } else if (typeof value === 'string') {
77 return value;
78 } else {
79 return value.toString();
80 }
81 } else {
82 return '';
83 }
84 }
86 // Lower case of all headers keys
87 var loweredHeaders = {};
88 Object.keys(headers || {}).map(function(key) {
89 loweredHeaders[key.toLowerCase()] = headers[key];
90 });
92 // Allow outer code to add custom headers or remove something
93 if (preprocess) preprocess(loweredHeaders);
95 // Transform object into kv pairs
96 var len = 2,
97 pairs = Object.keys(loweredHeaders).filter(function(key) {
98 var lkey = key.toLowerCase();
99 return lkey !== 'connection' && lkey !== 'keep-alive' &&
100 lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
101 }).map(function(key) {
102 var klen = Buffer.byteLength(key),
103 value = stringify(loweredHeaders[key]),
104 vlen = Buffer.byteLength(value);
106 len += 4 + klen + vlen;
107 return [klen, key, vlen, value];
108 }),
109 result = new Buffer(len);
111 result.writeUInt16BE(pairs.length, 0, true);
113 var offset = 2;
114 pairs.forEach(function(pair) {
115 // Write key length
116 result.writeUInt16BE(pair[0], offset, true);
117 // Write key
118 result.write(pair[1], offset + 2);
120 offset += pair[0] + 2;
122 // Write value length
123 result.writeUInt16BE(pair[2], offset, true);
124 // Write value
125 result.write(pair[3], offset + 2);
127 offset += pair[2] + 2;
128 });
130 return result;
131 };
133 Framer.prototype._synFrame = function _synFrame(type, id, assoc, priority, dict,
134 callback) {
135 // Compress headers
136 this.deflate(dict, function (err, chunks, size) {
137 if (err) return callback(err);
139 var offset = type === 'SYN_STREAM' ? 18 : 14,
140 total = (type === 'SYN_STREAM' ? 10 : 6) + size,
141 frame = new Buffer(offset + size);;
143 frame.writeUInt16BE(0x8002, 0, true); // Control + Version
144 frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
145 frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
146 frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
148 if (type === 'SYN_STREAM') {
149 frame[4] = 2;
150 frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
151 }
153 frame.writeUInt8(priority & 0x3, 16, true); // Priority
155 for (var i = 0; i < chunks.length; i++) {
156 chunks[i].copy(frame, offset);
157 offset += chunks[i].length;
158 }
160 callback(null, frame);
161 });
162 };
164 //
165 // ### function replyFrame (id, code, reason, headers, callback)
166 // #### @id {Number} Stream ID
167 // #### @code {Number} HTTP Status Code
168 // #### @reason {String} (optional)
169 // #### @headers {Object|Array} (optional) HTTP headers
170 // #### @callback {Function} Continuation function
171 // Sends SYN_REPLY frame
172 //
173 Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
174 callback) {
175 var dict = headersToDict(headers, function(headers) {
176 headers.status = code + ' ' + reason;
177 headers.version = 'HTTP/1.1';
178 });
180 this._synFrame('SYN_REPLY', id, null, 0, dict, callback);
181 };
183 //
184 // ### function streamFrame (id, assoc, headers, callback)
185 // #### @id {Number} stream id
186 // #### @assoc {Number} associated stream id
187 // #### @meta {Object} meta headers ( method, scheme, url, version )
188 // #### @headers {Object} stream headers
189 // #### @callback {Function} continuation callback
190 // Create SYN_STREAM frame
191 // (needed for server push and testing)
192 //
193 Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
194 callback) {
195 var dict = headersToDict(headers, function(headers) {
196 headers.status = 200;
197 headers.version = 'HTTP/1.1';
198 headers.url = meta.url;
199 });
201 this._synFrame('SYN_STREAM', id, assoc, meta.priority, dict, callback);
202 };
204 //
205 // ### function dataFrame (id, fin, data)
206 // #### @id {Number} Stream id
207 // #### @fin {Bool} Is this data frame last frame
208 // #### @data {Buffer} Response data
209 // Sends DATA frame
210 //
211 Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
212 if (!fin && !data.length) return [];
214 var frame = new Buffer(8 + data.length);
216 frame.writeUInt32BE(id & 0x7fffffff, 0, true);
217 frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
218 frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
220 if (data.length) data.copy(frame, 8);
222 return frame;
223 };
225 //
226 // ### function pingFrame (id)
227 // #### @id {Buffer} Ping ID
228 // Sends PING frame
229 //
230 Framer.prototype.pingFrame = function pingFrame(id) {
231 var header = new Buffer(12);
233 header.writeUInt32BE(0x80020006, 0, true); // Version and type
234 header.writeUInt32BE(0x00000004, 4, true); // Length
235 id.copy(header, 8, 0, 4); // ID
237 return header;
238 };
240 //
241 // ### function rstFrame (id, code)
242 // #### @id {Number} Stream ID
243 // #### @code {NUmber} RST Code
244 // Sends PING frame
245 //
246 Framer.prototype.rstFrame = function rstFrame(id, code) {
247 var header;
249 if (!(header = Framer.rstCache[code])) {
250 header = new Buffer(16);
252 header.writeUInt32BE(0x80020003, 0, true); // Version and type
253 header.writeUInt32BE(0x00000008, 4, true); // Length
254 header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
255 header.writeUInt32BE(code, 12, true); // Status Code
257 Framer.rstCache[code] = header;
258 }
260 return header;
261 };
262 Framer.rstCache = {};
264 //
265 // ### function settingsFrame (options)
266 // #### @options {Object} settings frame options
267 // Sends SETTINGS frame with MAX_CONCURRENT_STREAMS
268 //
269 Framer.prototype.settingsFrame = function settingsFrame(options) {
270 var settings;
272 if (!(settings = Framer.settingsCache[options.maxStreams])) {
273 settings = new Buffer(20);
275 settings.writeUInt32BE(0x80020004, 0, true); // Version and type
276 settings.writeUInt32BE(0x0000000C, 4, true); // length
277 settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
278 settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag
279 settings.writeUInt32BE(options.maxStreams, 16, true);
281 Framer.settingsCache[options.maxStreams] = settings;
282 }
284 return settings;
285 };
286 Framer.settingsCache = {};