// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Use an HTTP server configured to echo back the request body as the response
// body.
const httpPost =
"POST /echo HTTP/1.1\r\n" +
"Content-Length: 19\r\n\r\n" +
"0100000005320000005";
var expectedResponsePattern = /\n0100000005320000005$/;
// The echo server can send back the response in multiple chunks. We must wait
// for at least `minExpectedResponseLength` bytes to be received before matching
// the response with `expectedResponsePattern`.
var minExpectedResponseLength = 102;
var tcp_address;
var https_address;
var bytesSent = 0;
var dataAsString = "";
var dataRead = [];
var tcp_port = -1;
var https_port = -1;
var protocol = "none";
var tcp_socketId = 0;
var https_socketId = 0;
var echoDataSent = false;
var waitCount = 0;
// Keys are socketIds. Values are inner dictionaries with two keys: onReceive,
// onReceiveError. Both are functions.
var receive_dispatcher = {}
// Many thanks to Dennis for the StackOverflow answer: http://goo.gl/UDanx
// Since amended to handle BlobBuilder deprecation.
function string2ArrayBuffer(string, callback) {
var blob = new Blob([string]);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
};
f.readAsArrayBuffer(blob);
}
function arrayBuffer2String(buf, callback) {
var blob = new Blob([new Uint8Array(buf)]);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
};
f.readAsText(blob);
}
function dispatchSocketReceive(receiveInfo) {
if (receive_dispatcher[receiveInfo.socketId] !== undefined) {
receive_dispatcher[receiveInfo.socketId].onReceive(receiveInfo);
} else {
console.log("dispatchSocketReceive: No handler for socket " +
receiveInfo.socketId);
}
}
function dispatchSocketReceiveError(receiveErrorInfo) {
if (receive_dispatcher[receiveErrorInfo.socketId] !== undefined) {
receive_dispatcher[receiveErrorInfo.socketId].onReceiveError(
receiveErrorInfo);
} else {
console.log("dispatchSocketReceiveError: No handler for socket " +
receiveErrorInfo.socketId);
}
}
var testSocketCreation = function() {
function onCreate(socketInfo) {
function onGetInfo(info) {
chrome.test.assertFalse(info.connected);
if (info.peerAddress || info.peerPort) {
chrome.test.fail('Unconnected socket should not have peer');
}
if (info.localAddress || info.localPort) {
chrome.test.fail('Unconnected socket should not have local binding');
}
chrome.sockets.tcp.close(socketInfo.socketId, function() {
chrome.sockets.tcp.getInfo(socketInfo.socketId, function(info) {
chrome.test.assertEq(undefined, info);
chrome.test.succeed();
});
});
}
chrome.test.assertTrue(socketInfo.socketId > 0);
// Obtaining socket information before a connect() call should be safe, but
// return empty values.
chrome.sockets.tcp.getInfo(socketInfo.socketId, onGetInfo);
}
chrome.sockets.tcp.create({}, onCreate);
};
var testSending = function() {
dataRead = "";
echoDataSent = false;
waitCount = 0;
setTimeout(waitForBlockingOperation, 1000);
createSocket();
function createSocket() {
chrome.sockets.tcp.create({
"name": "test",
"persistent": true,
"bufferSize": 104
}, onCreateComplete);
}
function onCreateComplete(socketInfo) {
console.log("onCreateComplete");
tcp_socketId = socketInfo.socketId;
chrome.test.assertTrue(tcp_socketId > 0, "failed to create socket");
console.log("add event listeners");
receive_dispatcher[tcp_socketId] = {
onReceive: onSocketReceive,
onReceiveError: onSocketReceiveError
};
chrome.sockets.tcp.getInfo(tcp_socketId, onGetInfoAfterCreateComplete);
}
function onGetInfoAfterCreateComplete(result) {
console.log("onGetInfoAfterCreateComplete");
chrome.test.assertTrue(!result.localAddress,
"Socket should not have local address");
chrome.test.assertTrue(!result.localPort,
"Socket should not have local port");
chrome.test.assertTrue(!result.peerAddress,
"Socket should not have peer address");
chrome.test.assertTrue(!result.peerPort,
"Socket should not have peer port");
chrome.test.assertFalse(result.connected, "Socket should not be connected");
chrome.test.assertEq("test", result.name, "Socket name did not persist");
chrome.test.assertTrue(result.persistent,
"Socket should be persistent");
chrome.test.assertEq(104, result.bufferSize, "Buffer size did not persist");
chrome.test.assertFalse(result.paused, "Socket should not be paused");
chrome.sockets.tcp.update(tcp_socketId, {
"name": "test2",
"persistent": false,
bufferSize: 2048
}, onUpdateComplete);
}
function onUpdateComplete() {
console.log("onUpdateComplete");
chrome.sockets.tcp.getInfo(tcp_socketId, onGetInfoAfterUpdateComplete);
}
function onGetInfoAfterUpdateComplete(result) {
console.log("onGetInfoAfterUpdateComplete");
chrome.test.assertTrue(!result.localAddress,
"Socket should not have local address");
chrome.test.assertTrue(!result.localPort,
"Socket should not have local port");
chrome.test.assertTrue(!result.peerAddress,
"Socket should not have peer address");
chrome.test.assertTrue(!result.peerPort,
"Socket should not have peer port");
chrome.test.assertFalse(result.connected, "Socket should not be connected");
chrome.test.assertEq("test2", result.name, "Socket name did not persist");
chrome.test.assertFalse(result.persistent,
"Socket should not be persistent");
chrome.test.assertEq(2048, result.bufferSize,
"Buffer size did not persist");
chrome.test.assertFalse(result.paused, "Socket should not be paused");
chrome.sockets.tcp.connect(tcp_socketId, tcp_address, tcp_port,
onConnectComplete);
}
function onConnectComplete(result) {
console.log("onConnectComplete");
chrome.test.assertEq(0, result,
"Connect failed with error " + result);
chrome.sockets.tcp.getInfo(tcp_socketId, onGetInfoAfterConnectComplete);
}
function onGetInfoAfterConnectComplete(result) {
console.log("onGetInfoAfterConnectComplete");
chrome.test.assertTrue(!!result.localAddress,
"Bound socket should always have local address");
chrome.test.assertTrue(!!result.localPort,
"Bound socket should always have local port");
// NOTE: We're always called with 'localhost', but getInfo will only return
// IPs, not names.
chrome.test.assertEq(result.peerAddress, "127.0.0.1",
"Peer addresss should be the listen server");
chrome.test.assertEq(result.peerPort, tcp_port,
"Peer port should be the listen server");
chrome.test.assertTrue(result.connected, "Socket should be connected");
chrome.sockets.tcp.setPaused(tcp_socketId, true, onSetPausedComplete);
}
function onSetPausedComplete() {
console.log("onSetPausedComplete");
chrome.sockets.tcp.getInfo(tcp_socketId, onGetInfoAfterSetPausedComplete);
}
function onGetInfoAfterSetPausedComplete(result) {
console.log("onGetInfoAfterSetPausedComplete");
chrome.test.assertTrue(result.paused, "Socket should be paused");
chrome.sockets.tcp.setPaused(tcp_socketId, false, onUnpauseComplete);
}
function onUnpauseComplete() {
console.log("onUnpauseComplete");
chrome.sockets.tcp.getInfo(tcp_socketId, onGetInfoAfterUnpauseComplete);
}
function onGetInfoAfterUnpauseComplete(result) {
console.log("onGetInfoAfterUnpauseComplete");
chrome.test.assertFalse(result.paused, "Socket should not be paused");
chrome.sockets.tcp.setNoDelay(tcp_socketId, true, onSetNoDelayComplete);
}
function onSetNoDelayComplete(result) {
console.log("onSetNoDelayComplete");
if (result != 0) {
chrome.test.fail("setNoDelay failed for TCP: " +
"result=" + result + ", " +
"lastError=" + chrome.runtime.lastError.message);
}
chrome.sockets.tcp.setKeepAlive(
tcp_socketId, true, 1000, onSetKeepAliveComplete);
}
function onSetKeepAliveComplete(result) {
console.log("onSetKeepAliveComplete");
if (result != 0) {
chrome.test.fail("setKeepAlive failed for TCP: " +
"result=" + result + ", " +
"lastError=" + chrome.runtime.lastError.message);
}
string2ArrayBuffer(httpPost, function(arrayBuffer) {
echoDataSent = true;
chrome.sockets.tcp.send(tcp_socketId, arrayBuffer, onSendComplete);
});
}
function onSendComplete(sendInfo) {
console.log("onSendComplete: " + sendInfo.bytesSent + " bytes.");
chrome.test.assertEq(0, sendInfo.resultCode, "Send failed.");
chrome.test.assertTrue(sendInfo.bytesSent > 0,
"Send didn't write bytes.");
bytesSent += sendInfo.bytesSent;
}
function onSocketReceive(receiveInfo) {
console.log("onSocketReceive");
chrome.test.assertEq(tcp_socketId, receiveInfo.socketId);
arrayBuffer2String(receiveInfo.data, function(s) {
// We may receive the response in multiple chunks. Keep appending the
// response to dataAsString until we reach minExpectedResponseLength.
dataAsString += s;
if (dataAsString.length >= minExpectedResponseLength) {
var match = !!dataAsString.match(expectedResponsePattern);
chrome.test.assertTrue(match, "Received data does not match.");
console.log("echo data received, closing socket");
chrome.sockets.tcp.close(tcp_socketId, onCloseComplete);
}
});
}
function onSocketReceiveError(receiveErrorInfo) {
// Note: Once we have sent the echo message, the server sends back
// the echo response and closes the connection right away. This means
// we get a "connection closed" error very quickly after sending our
// message. This is why we ignore errors from that point on.
if (echoDataSent)
return;
console.log("onSocketReceiveError");
chrome.test.fail("Receive error on socket " + receiveErrorInfo.socketId +
": result code=" + receiveErrorInfo.resultCode);
}
function onCloseComplete() {
console.log("onCloseComplete");
chrome.test.succeed();
}
}; // testSending()
var testSecure = function () {
var request_sent = false;
dataAsString = "";
setTimeout(waitForBlockingOperation, 1000);
// Run the test a few times. First with misuse_testing=MISUSE_NONE,
// to test that the API works correctly when used properly. Then
// with different values of misuse_mode, test that the API does
// not malfunction when used improperly. Upon success, each misuse
// must close the socket and call onCloseComplete().
var MISUSE_NONE = 0;
var MISUSE_SECURE_PENDING_READ = 1;
var MISUSE_LAST = MISUSE_SECURE_PENDING_READ;
var misuse_mode = MISUSE_NONE;
chrome.sockets.tcp.create({}, onCreateComplete);
function onCreateComplete(socketInfo) {
https_socketId = socketInfo.socketId;
receive_dispatcher[https_socketId] = {
onReceive: onSocketReceive,
onReceiveError: onSocketReceiveError
};
chrome.test.assertTrue(https_socketId > 0, "failed to create socket");
if (misuse_mode == MISUSE_SECURE_PENDING_READ) {
// Don't pause the socket. This will let the sockets runtime
// keep a pending read on it.
console.log("HTTPS onCreateComplete: in MISUSE_SECURE_PENDING_READ " +
"mode, skipping setPaused(false).");
onPausedComplete();
} else {
chrome.sockets.tcp.setPaused(https_socketId, true, onPausedComplete);
}
}
function onPausedComplete() {
console.log("HTTPS onPausedComplete. Connecting to " + https_address + ":" +
https_port);
chrome.sockets.tcp.connect(https_socketId, https_address, https_port,
onConnectComplete);
}
function onConnectComplete(result) {
console.log("HTTPS onConnectComplete");
chrome.test.assertEq(0, result,
"Connect failed with error " + result);
chrome.sockets.tcp.secure(https_socketId, null, onSecureComplete);
}
function onSecureComplete(result) {
console.log("HTTPS onSecureComplete(" + result + ")");
if (misuse_mode == MISUSE_SECURE_PENDING_READ) {
chrome.test.assertFalse(result == 0,
"Secure should have failed when a read " +
"was pending (" + result + ")");
chrome.sockets.tcp.close(https_socketId, onCloseComplete);
} else {
chrome.test.assertEq(0, result,
"Secure failed with error " + result);
chrome.sockets.tcp.setPaused(https_socketId, false, onUnpauseComplete);
}
}
function onUnpauseComplete() {
console.log("HTTPS onUnpauseComplete");
string2ArrayBuffer(httpPost, function(arrayBuffer) {
request_sent = true;
chrome.sockets.tcp.send(https_socketId, arrayBuffer, onSendComplete);
});
}
function onSendComplete(sendInfo) {
console.log("HTTPS onSendComplete: " + sendInfo.bytesSent + " bytes.");
chrome.test.assertEq(0, sendInfo.resultCode, "Send failed.");
chrome.test.assertTrue(sendInfo.bytesSent > 0,
"Send didn't write bytes.");
bytesSent += sendInfo.bytesSent;
}
function onSocketReceive(receiveInfo) {
console.log("HTTPS onSocketReceive");
chrome.test.assertEq(https_socketId, receiveInfo.socketId);
arrayBuffer2String(receiveInfo.data, function(s) {
// we will get more data than we care about. We only care about the
// first segment of data (the HTTP 200 code). Ignore the rest, which
// won't match the pattern.
dataAsString += s;
if (dataAsString.length >= minExpectedResponseLength) {
console.log("HTTPS receive: got " + s);
var match = !!dataAsString.match(expectedResponsePattern);
chrome.test.assertTrue(match, "Received data does not match.");
console.log("HTTPS response received, closing socket.");
chrome.sockets.tcp.close(https_socketId, onCloseComplete);
}
});
}
function onSocketReceiveError(receiveErrorInfo) {
console.log("HTTPS onSocketReceiveError");
if (request_sent) {
return;
}
chrome.test.fail("Receive error on socket " + receiveErrorInfo.socketId +
": result code=" + receiveErrorInfo.resultCode);
}
function onCloseComplete() {
console.log("HTTPS Test Succeeded");
if (misuse_mode == MISUSE_LAST) {
// The test has run in all misuse modes.
chrome.test.succeed();
} else {
// Run the test again in the next misuse mode.
misuse_mode += 1;
chrome.sockets.tcp.create({}, onCreateComplete);
}
}
}; // testSecure()
function waitForBlockingOperation() {
if (++waitCount < 10) {
setTimeout(waitForBlockingOperation, 1000);
} else {
// We weren't able to succeed in the given time.
chrome.test.fail("Operations didn't complete after " + waitCount + " " +
"seconds. Response so far was <" + dataAsString + ">.");
}
}
var onMessageReply = function(message) {
var components = message.split(',');
var tests = [];
for (var i = 0; i < components.length; ++i) {
var parts = components[i].split(":");
var test_type = parts[0];
if (test_type == 'tcp') {
tcp_address = parts[1];
tcp_port = parseInt(parts[2]);
console.log("Running tests for TCP, server " +
tcp_address + ":" + tcp_port);
tests = tests.concat([testSocketCreation, testSending]);
} else if (test_type == 'https') {
https_address = parts[1];
https_port = parseInt(parts[2]);
console.log("Running tests for HTTPS, server " +
https_address + ":" + https_port);
tests = tests.concat([testSecure]);
} else {
chrome.test.fail("Invalid test type: " + test_type);
}
}
// Setup the suite-wide event listeners.
chrome.sockets.tcp.onReceive.addListener(dispatchSocketReceive);
chrome.sockets.tcp.onReceiveError.addListener(dispatchSocketReceiveError);
chrome.test.runTests(tests);
};
// Find out which protocol we're supposed to test, and which echo server we
// should be using, then kick off the tests.
chrome.test.sendMessage("info_please", onMessageReply);