/// /// /// import freedomMocker = require('../../../third_party/uproxy-lib/freedom/mocks/mock-freedom-in-module-env'); freedom = freedomMocker.makeMockFreedomInModuleEnv(); import arraybuffers = require('../../../third_party/uproxy-lib/arraybuffers/arraybuffers'); import peerconnection = require('../../../third_party/uproxy-lib/webrtc/peerconnection'); import signals = require('../../../third_party/uproxy-lib/webrtc/signals'); import handler = require('../../../third_party/uproxy-lib/handler/queue'); import rtc_to_net = require('./rtc-to-net'); import net = require('../net/net.types'); import tcp = require('../net/tcp'); import socks = require('../socks-common/socks-headers'); import logging = require('../../../third_party/uproxy-lib/logging/logging'); import ProxyConfig = require('./proxyconfig'); var log :logging.Log = new logging.Log('socks-to-rtc spec'); var mockBoundEndpoint :net.Endpoint = { address: '127.0.0.1', port: 1234 }; var voidPromise = Promise.resolve(); var mockProxyConfig :ProxyConfig = { allowNonUnicast: false }; var mockRemoteEndpoint :net.Endpoint = { // This address and port are both reserved for testing. address: '192.0.2.111', port: 1023 }; var mockConnectionInfo :tcp.ConnectionInfo = { bound: mockBoundEndpoint, remote: mockRemoteEndpoint }; // Neither fulfills nor rejects. // Useful in a bunch of tests where a promise must be returned // for chaining purposes. var noopPromise = new Promise((F, R) => {}); describe('RtcToNet', function() { var server :rtc_to_net.RtcToNet; var mockPeerconnection :peerconnection.PeerConnection; beforeEach(function() { server = new rtc_to_net.RtcToNet(); mockPeerconnection = { dataChannels: {}, negotiateConnection: jasmine.createSpy('negotiateConnection'), onceConnecting: noopPromise, onceConnected: noopPromise, onceDisconnected: noopPromise, peerOpenedChannelQueue: new handler.Queue(), close: jasmine.createSpy('close') }; }); it('onceReady fulfills on peerconnection success', (done) => { mockPeerconnection.onceConnected = voidPromise; // We're not testing termination. server.start(mockProxyConfig, mockPeerconnection).then(done); }); it('onceReady rejects on peerconnection setup failure', (done) => { mockPeerconnection.onceConnected = Promise.reject(new Error('failed to establish connection')); // We're not testing termination. server.start(mockProxyConfig, mockPeerconnection).catch(done); }); it('onceStopped fulfills on peerconnection termination', (done) => { mockPeerconnection.onceConnected = voidPromise; mockPeerconnection.onceDisconnected = Promise.resolve(); server.start(mockProxyConfig, mockPeerconnection) .then(() => { return server.onceStopped; }) .then(done); }); it('onceStopped fulfills on call to stop', (done) => { mockPeerconnection.onceConnected = voidPromise; // Calling stop() alone should be sufficient to initiate shutdown. server.start(mockProxyConfig, mockPeerconnection) .then(server.stop) .then(() => { return server.onceStopped; }) .then(done); }); }); describe("RtcToNet session", function() { var session :rtc_to_net.Session; var mockTcpConnection :tcp.Connection; var mockDataChannel :peerconnection.DataChannel; var mockDataFromPeerQueue :handler.Queue; var mockBytesReceived :handler.Queue; var mockBytesSent :handler.Queue; beforeEach(function() { mockTcpConnection = jasmine.createSpyObj('tcp connection', [ 'onceConnected', 'onceClosed', 'isClosed', 'close', 'send', 'pause', 'resume' ]); (mockTcpConnection.send).and.returnValue(Promise.resolve({ bytesWritten: 1 })); mockTcpConnection.dataFromSocketQueue = new handler.Queue(); mockDataFromPeerQueue = new handler.Queue(); mockDataChannel = { close: jasmine.createSpy('close'), closeDataChannel: noopPromise, dataFromPeerQueue: mockDataFromPeerQueue, getLabel: jasmine.createSpy('getLabel'), onceClosed: noopPromise, send: jasmine.createSpy('send'), isInOverflow: jasmine.createSpy('isInOverflow').and.returnValue(false), setOverflowListener: jasmine.createSpy('setOverflowListener') }; (mockDataChannel.send).and.returnValue(voidPromise); mockBytesReceived = new handler.Queue(); mockBytesSent = new handler.Queue(); session = new rtc_to_net.Session( mockDataChannel, mockProxyConfig, mockBytesReceived, mockBytesSent); }); it('onceReady fulfills with listening endpoint on successful negotiation', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; session.start().then(done); }); it('onceReady rejects and onceStopped fulfills on unsuccessful endpoint negotiation', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.reject(new Error('bad format'))); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; session.start().catch(session.onceStopped).then(done); }); it('onceStopped fulfills on datachannel termination', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; mockDataChannel.onceClosed = Promise.resolve(); session.start().then(session.onceStopped).then(done); }); it('onceStopped fulfills on TCP connection termination', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = Promise.resolve(tcp.SocketCloseKind.WE_CLOSED_IT); session.start().then(session.onceStopped).then(done); }); it('onceStopped fulfills on call to stop', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; session.start().then(session.stop).then(session.onceStopped).then(done); }); it('bytes sent counter', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; var buffer = new Uint8Array([1,2,3]).buffer; session.start().then(() => { mockTcpConnection.dataFromSocketQueue.handle(buffer); }); mockBytesSent.setSyncNextHandler((numBytes:number) => { expect(numBytes).toEqual(buffer.byteLength); done(); }); }); it('bytes received counter', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; var message :peerconnection.Data = { buffer: new Uint8Array([1,2,3]).buffer }; session.start().then(() => { mockDataFromPeerQueue.handle(message); }); mockBytesReceived.setSyncNextHandler((numBytes:number) => { expect(numBytes).toEqual(message.buffer.byteLength); done(); }); }); it('channel queue drains before termination', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; // The data channel is closed before the session starts. mockDataChannel.onceClosed = voidPromise; var message :peerconnection.Data = { buffer: new Uint8Array([1,2,3]).buffer }; var onceMessageHandled = mockDataFromPeerQueue.handle(message); session.start().then(session.onceStopped).then(() => { return onceMessageHandled; }).then(() => { expect(mockDataChannel.dataFromPeerQueue.getLength()).toEqual(0); done(); }); }); it('socket queue drains before termination', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); // The TCP connection is closed before the session starts. mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = Promise.resolve(tcp.SocketCloseKind.WE_CLOSED_IT); (mockTcpConnection.isClosed).and.returnValue(true); var buffer = new Uint8Array([1,2,3]).buffer; var onceMessageHandled = mockTcpConnection.dataFromSocketQueue.handle(buffer); session.start().then(session.onceStopped).then(() => { return onceMessageHandled; }).then(() => { expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(0); done(); }); }); it('backpressure', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; var overflowListener :(overflow:boolean) => void; mockDataChannel.setOverflowListener = (listener) => { overflowListener = listener; }; var buffer = new Uint8Array([1,2,3]).buffer; // Messages received before start sit in the TCP receive queue. mockTcpConnection.dataFromSocketQueue.handle(buffer); expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(1); expect(mockDataChannel.send).not.toHaveBeenCalled(); session.start().then(() => { // After start, the TCP queue should be drained into the datachannel. expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(0); expect(mockDataChannel.send).toHaveBeenCalled(); // After draining the queue, the TCP connection should be resumed. expect(mockTcpConnection.pause).not.toHaveBeenCalled(); expect(mockTcpConnection.resume).toHaveBeenCalled(); // Enter overflow state. This should trigger a call to pause. overflowListener(true); expect(mockTcpConnection.pause).toHaveBeenCalled(); // In the paused state, messages are still forwarded mockTcpConnection.dataFromSocketQueue.handle(buffer); expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(0); expect((mockDataChannel.send).calls.count()).toEqual(2); // Exit overflow state. This should trigger a call to resume. overflowListener(false); expect((mockTcpConnection.resume).calls.count()).toEqual(2); done(); }); }); it('backpressure with early flood', (done) => { spyOn(session, 'receiveEndpointFromPeer_').and.returnValue(Promise.resolve(mockRemoteEndpoint)); spyOn(session, 'replyToPeer_').and.returnValue(Promise.resolve()); spyOn(session, 'getTcpConnection_').and.returnValue(Promise.resolve(mockTcpConnection)); mockTcpConnection.onceConnected = Promise.resolve(mockConnectionInfo); mockTcpConnection.onceClosed = noopPromise; var overflowListener :(overflow:boolean) => void; mockDataChannel.setOverflowListener = (listener) => { overflowListener = listener; }; mockDataChannel.isInOverflow = jasmine.createSpy('isInOverflow').and.returnValue(true); var buffer = new Uint8Array([1,2,3]).buffer; // Messages received before start sit in the TCP receive queue. mockTcpConnection.dataFromSocketQueue.handle(buffer); expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(1); expect(mockDataChannel.send).not.toHaveBeenCalled(); session.start().then(() => { // After start, the TCP queue should be drained into the datachannel. expect(mockTcpConnection.dataFromSocketQueue.getLength()).toEqual(0); expect(mockDataChannel.send).toHaveBeenCalled(); // If the initial queue is enough to trigger overflow, then the // socket should not be resumed. expect(mockTcpConnection.pause).not.toHaveBeenCalled(); expect(mockTcpConnection.resume).not.toHaveBeenCalled(); // Exit overflow state. This should trigger a call to resume. overflowListener(false); expect(mockTcpConnection.resume).toHaveBeenCalled(); done(); }); }); });