var expect = require('chai').expect; var util = require('./util'); var framer = require('../lib/protocol/framer'); var Serializer = framer.Serializer; var Deserializer = framer.Deserializer; var frame_types = { DATA: ['data'], HEADERS: ['priority_information', 'data'], PRIORITY: ['priority_information'], RST_STREAM: ['error'], SETTINGS: ['settings'], PUSH_PROMISE: ['promised_stream', 'data'], PING: ['data'], GOAWAY: ['last_stream', 'error'], WINDOW_UPDATE: ['window_size'], CONTINUATION: ['data'], ALTSVC: ['protocolID', 'host', 'port', 'origin', 'maxAge'] }; var test_frames = [{ frame: { type: 'DATA', flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false, PADDED: false }, stream: 10, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + content buffer: Buffer.from('000004' + '00' + '00' + '0000000A' + '12345678', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: false, RESERVED5: false, PRIORITY: false }, stream: 15, data: Buffer.from('12345678', 'hex') }, buffer: Buffer.from('000004' + '01' + '00' + '0000000F' + '12345678', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: false, RESERVED5: false, PRIORITY: true }, stream: 15, priorityDependency: 10, priorityWeight: 5, exclusiveDependency: false, data: Buffer.from('12345678', 'hex') }, buffer: Buffer.from('000009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: false, RESERVED5: false, PRIORITY: true }, stream: 15, priorityDependency: 10, priorityWeight: 5, exclusiveDependency: true, data: Buffer.from('12345678', 'hex') }, buffer: Buffer.from('000009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex') }, { frame: { type: 'PRIORITY', flags: { }, stream: 10, priorityDependency: 9, priorityWeight: 5, exclusiveDependency: false }, buffer: Buffer.from('000005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex') }, { frame: { type: 'PRIORITY', flags: { }, stream: 10, priorityDependency: 9, priorityWeight: 5, exclusiveDependency: true }, buffer: Buffer.from('000005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex') }, { frame: { type: 'RST_STREAM', flags: { }, stream: 10, error: 'INTERNAL_ERROR' }, buffer: Buffer.from('000004' + '03' + '00' + '0000000A' + '00000002', 'hex') }, { frame: { type: 'SETTINGS', flags: { ACK: false }, stream: 10, settings: { SETTINGS_HEADER_TABLE_SIZE: 0x12345678, SETTINGS_ENABLE_PUSH: true, SETTINGS_MAX_CONCURRENT_STREAMS: 0x01234567, SETTINGS_INITIAL_WINDOW_SIZE: 0x89ABCDEF, SETTINGS_MAX_FRAME_SIZE: 0x00010000 } }, buffer: Buffer.from('00001E' + '04' + '00' + '0000000A' + '0001' + '12345678' + '0002' + '00000001' + '0003' + '01234567' + '0004' + '89ABCDEF' + '0005' + '00010000', 'hex') }, { frame: { type: 'PUSH_PROMISE', flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false, PADDED: false }, stream: 15, promised_stream: 3, data: Buffer.from('12345678', 'hex') }, buffer: Buffer.from('000008' + '05' + '00' + '0000000F' + '00000003' + '12345678', 'hex') }, { frame: { type: 'PING', flags: { ACK: false }, stream: 15, data: Buffer.from('1234567887654321', 'hex') }, buffer: Buffer.from('000008' + '06' + '00' + '0000000F' + '1234567887654321', 'hex') }, { frame: { type: 'GOAWAY', flags: { }, stream: 10, last_stream: 0x12345678, error: 'PROTOCOL_ERROR' }, buffer: Buffer.from('000008' + '07' + '00' + '0000000A' + '12345678' + '00000001', 'hex') }, { frame: { type: 'WINDOW_UPDATE', flags: { }, stream: 10, window_size: 0x12345678 }, buffer: Buffer.from('000004' + '08' + '00' + '0000000A' + '12345678', 'hex') }, { frame: { type: 'CONTINUATION', flags: { RESERVED1: false, RESERVED2: false, END_HEADERS: true }, stream: 10, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + content buffer: Buffer.from('000004' + '09' + '04' + '0000000A' + '12345678', 'hex') }, { frame: { type: 'ALTSVC', flags: { }, stream: 0, maxAge: 31536000, port: 4443, protocolID: "h2", host: "altsvc.example.com", origin: "" }, buffer: Buffer.from(Buffer.from('00002B' + '0A' + '00' + '00000000' + '0000', 'hex') + Buffer.from('h2="altsvc.example.com:4443"; ma=31536000', 'ascii')) }, { frame: { type: 'ALTSVC', flags: { }, stream: 0, maxAge: 31536000, port: 4443, protocolID: "h2", host: "altsvc.example.com", origin: "https://onlyme.example.com" }, buffer: Buffer.from(Buffer.from('000045' + '0A' + '00' + '00000000' + '001A', 'hex') + Buffer.from('https://onlyme.example.comh2="altsvc.example.com:4443"; ma=31536000', 'ascii')) }, { frame: { type: 'BLOCKED', flags: { }, stream: 10 }, buffer: Buffer.from('000000' + '0B' + '00' + '0000000A', 'hex') }]; var deserializer_test_frames = test_frames.slice(0); var padded_test_frames = [{ frame: { type: 'DATA', flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false, PADDED: true }, stream: 10, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + pad length + content + padding buffer: Buffer.from('00000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: true, RESERVED5: false, PRIORITY: false }, stream: 15, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + pad length + data + padding buffer: Buffer.from('00000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: true, RESERVED5: false, PRIORITY: true }, stream: 15, priorityDependency: 10, priorityWeight: 5, exclusiveDependency: false, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding buffer: Buffer.from('000010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex') }, { frame: { type: 'HEADERS', flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false, PADDED: true, RESERVED5: false, PRIORITY: true }, stream: 15, priorityDependency: 10, priorityWeight: 5, exclusiveDependency: true, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding buffer: Buffer.from('000010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex') }, { frame: { type: 'PUSH_PROMISE', flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false, PADDED: true }, stream: 15, promised_stream: 3, data: Buffer.from('12345678', 'hex') }, // length + type + flags + stream + pad length + promised stream + data + padding buffer: Buffer.from('00000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex') }]; for (var idx = 0; idx < padded_test_frames.length; idx++) { deserializer_test_frames.push(padded_test_frames[idx]); } describe('framer.js', function() { describe('Serializer', function() { describe('static method .commonHeader({ type, flags, stream }, buffer_array)', function() { it('should add the appropriate 9 byte header buffer in front of the others', function() { for (var i = 0; i < test_frames.length; i++) { var test = test_frames[i]; var buffers = [test.buffer.slice(9)]; var header_buffer = test.buffer.slice(0,9); Serializer.commonHeader(test.frame, buffers); expect(buffers[0]).to.deep.equal(header_buffer); } }); }); Object.keys(frame_types).forEach(function(type) { var tests = test_frames.filter(function(test) { return test.frame.type === type; }); var frame_shape = '{ ' + frame_types[type].join(', ') + ' }'; describe('static method .' + type + '(' + frame_shape + ', buffer_array)', function() { it('should push buffers to the array that make up a ' + type + ' type payload', function() { for (var i = 0; i < tests.length; i++) { var test = tests[i]; var buffers = []; Serializer[type](test.frame, buffers); expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(9)); } }); }); }); describe('transform stream', function() { it('should transform frame objects to appropriate buffers', function() { var stream = new Serializer(util.log); for (var i = 0; i < test_frames.length; i++) { var test = test_frames[i]; stream.write(test.frame); var chunk, buffer = Buffer.alloc(0); while (chunk = stream.read()) { buffer = util.concat([buffer, chunk]); } expect(buffer).to.be.deep.equal(test.buffer); } }); }); }); describe('Deserializer', function() { describe('static method .commonHeader(header_buffer, frame)', function() { it('should augment the frame object with these properties: { type, flags, stream })', function() { for (var i = 0; i < deserializer_test_frames.length; i++) { var test = deserializer_test_frames[i], frame = {}; Deserializer.commonHeader(test.buffer.slice(0,9), frame); expect(frame).to.deep.equal({ type: test.frame.type, flags: test.frame.flags, stream: test.frame.stream }); } }); }); Object.keys(frame_types).forEach(function(type) { var tests = deserializer_test_frames.filter(function(test) { return test.frame.type === type; }); var frame_shape = '{ ' + frame_types[type].join(', ') + ' }'; describe('static method .' + type + '(payload_buffer, frame)', function() { it('should augment the frame object with these properties: ' + frame_shape, function() { for (var i = 0; i < tests.length; i++) { var test = tests[i]; var frame = { type: test.frame.type, flags: test.frame.flags, stream: test.frame.stream }; Deserializer[type](test.buffer.slice(9), frame); expect(frame).to.deep.equal(test.frame); } }); }); }); describe('transform stream', function() { it('should transform buffers to appropriate frame object', function() { var stream = new Deserializer(util.log); var shuffled = util.shuffleBuffers(deserializer_test_frames.map(function(test) { return test.buffer; })); shuffled.forEach(stream.write.bind(stream)); for (var j = 0; j < deserializer_test_frames.length; j++) { expect(stream.read()).to.be.deep.equal(deserializer_test_frames[j].frame); } }); }); }); describe('bunyan formatter', function() { describe('`frame`', function() { var format = framer.serializers.frame; it('should assign a unique ID to each frame', function() { var frame1 = { type: 'DATA', data: Buffer.alloc(10) }; var frame2 = { type: 'PRIORITY', priority: 1 }; expect(format(frame1).id).to.be.equal(format(frame1)); expect(format(frame2).id).to.be.equal(format(frame2)); expect(format(frame1)).to.not.be.equal(format(frame2)); }); }); }); });