///
import peerconnection = require('../../../../third_party/uproxy-lib/webrtc/peerconnection');
import ProxyConfig = require('../../rtc-to-net/proxyconfig');
import rtc_to_net = require('../../rtc-to-net/rtc-to-net');
import socks_to_rtc = require('../../socks-to-rtc/socks-to-rtc');
import net = require('../../net/net.types');
import tcp = require('../../net/tcp');
import socks = require('../../socks-common/socks-headers');
import proxyintegrationtesttypes = require('./proxy-integration-test.types');
import ProxyIntegrationTester = proxyintegrationtesttypes.ProxyIntegrationTester;
import ReceivedDataEvent = proxyintegrationtesttypes.ReceivedDataEvent;
import arraybuffers = require('../../../../third_party/uproxy-lib/arraybuffers/arraybuffers');
// This abstract class is converted into a real class by Freedom, which
// fills in the unimplemented on(...) method in the process of
// constructing a module.
class AbstractProxyIntegrationTest implements ProxyIntegrationTester {
private socksToRtc_ :socks_to_rtc.SocksToRtc;
private rtcToNet_ :rtc_to_net.RtcToNet;
private socksEndpoint_ : Promise;
private echoServers_ :tcp.Server[] = [];
private connections_ :{ [index:string]: tcp.Connection; } = {};
private localhost_ :string = '127.0.0.1';
private repeat_ :number = 1;
constructor(private dispatchEvent_:(name:string, args:any) => void,
denyLocalhost?:boolean,
obfuscate?:boolean) {
this.socksEndpoint_ = this.startSocksPair_(denyLocalhost, obfuscate);
}
public startEchoServer = () : Promise => {
var server = new tcp.Server({
address: this.localhost_,
port: 0
});
server.connectionsQueue.setSyncHandler((tcpConnection:tcp.Connection) => {
tcpConnection.dataFromSocketQueue.setSyncHandler((buffer:ArrayBuffer) => {
var multiBuffer :ArrayBuffer[] = []
for (var i = 0; i < this.repeat_; ++i) {
multiBuffer.push(buffer);
}
var concatenated = arraybuffers.concat(multiBuffer);
tcpConnection.send(concatenated);
});
});
// Discard endpoint info; we'll get it again later via .onceListening().
this.echoServers_.push(server);
return server.listen().then((endpoint:net.Endpoint) => { return endpoint.port; });
}
public setRepeat = (repeat:number) : Promise => {
this.repeat_ = repeat;
return Promise.resolve();
};
private startSocksPair_ = (denyLocalhost?:boolean, obfuscate?:boolean) : Promise => {
var socksToRtcEndpoint :net.Endpoint = {
address: this.localhost_,
port: 0
};
var rtcPcConfig :freedom_RTCPeerConnection.RTCConfiguration = {
iceServers: [],
};
var rtcToNetProxyConfig :ProxyConfig = {
allowNonUnicast: !denyLocalhost // Allow RtcToNet to contact the localhost server.
};
this.socksToRtc_ = new socks_to_rtc.SocksToRtc();
this.rtcToNet_ = new rtc_to_net.RtcToNet();
this.rtcToNet_.startFromConfig(rtcToNetProxyConfig,rtcPcConfig,obfuscate);
this.rtcToNet_.signalsForPeer.setSyncHandler(this.socksToRtc_.handleSignalFromPeer);
this.socksToRtc_.on('signalForPeer', this.rtcToNet_.handleSignalFromPeer);
return this.socksToRtc_.startFromConfig(socksToRtcEndpoint, rtcPcConfig, obfuscate);
}
// Assumes webEndpoint is IPv4.
private connectThroughSocks_ = (socksEndpoint:net.Endpoint, webEndpoint:net.Endpoint) : Promise => {
var connection = new tcp.Connection({endpoint: socksEndpoint});
var authRequest = socks.composeAuthHandshakeBuffer([socks.Auth.NOAUTH]);
connection.send(authRequest);
var connected = new Promise((F, R) => {
connection.onceConnected.then(F);
connection.onceClosed.then(R);
});
var firstBufferPromise :Promise = connection.receiveNext();
return connected.then((i:tcp.ConnectionInfo) => {
return firstBufferPromise;
}).then((buffer:ArrayBuffer) : Promise => {
var auth = socks.interpretAuthResponse(buffer);
if (auth != socks.Auth.NOAUTH) {
throw new Error('SOCKS server returned unexpected AUTH response. ' +
'Expected NOAUTH (' + socks.Auth.NOAUTH + ') but got ' + auth);
}
var request :socks.Request = {
command: socks.Command.TCP_CONNECT,
endpoint: webEndpoint,
};
connection.send(socks.composeRequestBuffer(request));
return connection.receiveNext();
}).then((buffer:ArrayBuffer) : Promise => {
var response = socks.interpretResponseBuffer(buffer);
if (response.reply != socks.Reply.SUCCEEDED) {
// TODO: Fix bad style: reject should only and always be an error.
// We should be resolving with result status.
return Promise.reject(response);
}
return Promise.resolve(connection);
});
}
public connect = (port:number, address?:string) : Promise => {
try {
return this.socksEndpoint_.then((socksEndpoint:net.Endpoint) : Promise => {
var echoEndpoint :net.Endpoint = {
address: address || this.localhost_,
port: port
};
return this.connectThroughSocks_(socksEndpoint, echoEndpoint);
}).then((connection:tcp.Connection) => {
this.connections_[connection.connectionId] = connection;
return connection.connectionId;
});
} catch (e) {
return Promise.reject(e);
}
}
public echo = (connectionId:string, content:ArrayBuffer) : Promise => {
return this.echoMultiple(connectionId, [content])
.then((responses:ArrayBuffer[]) : ArrayBuffer => {
return arraybuffers.concat(responses);
});
}
public echoMultiple = (connectionId:string, contents:ArrayBuffer[]) : Promise => {
try {
var connection = this.connections_[connectionId];
contents.forEach(connection.send);
var received :ArrayBuffer[] = [];
var bytesReceived :number = 0;
var bytesToSend :number = 0;
contents.forEach((content:ArrayBuffer) => {
bytesToSend += content.byteLength;
});
return new Promise((F, R) => {
connection.dataFromSocketQueue.setSyncHandler((echo:ArrayBuffer) => {
received.push(echo);
bytesReceived += echo.byteLength;
if (bytesReceived == bytesToSend) {
F(received);
}
});
});
} catch (e) {
return Promise.reject(e);
}
}
public sendData = (connectionId:string, content:ArrayBuffer) : Promise => {
try {
var connection = this.connections_[connectionId];
connection.send(content);
connection.dataFromSocketQueue.setSyncHandler((response:ArrayBuffer) => {
this.dispatchEvent_('receivedData', {
connectionId: connectionId,
response: response
});
});
return Promise.resolve();
} catch (e) {
return Promise.reject(e);
}
}
public on = (name:string, listener:(event:any) => void) : void => {
throw new Error('Placeholder function to keep Typescript happy');
}
}
export = AbstractProxyIntegrationTest;