// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TCP tests use an HTTP server configured to echo back the request body
// as the response body.
const tcpRequest = "POST /echo HTTP/1.1\r\n" +
"Content-Length: 19\r\n\r\n" +
"0100000005320000005";
const tcpExpectedResponsePattern = /\n0100000005320000005$/;
// UDP tests use a server that just echoes back the request.
const udpRequest = "0100000005320000005";
const udpExpectedResponsePattern = /^0100000005320000005$/;
const socket = chrome.socket;
var address;
var bytesWritten = 0;
var dataAsString;
var dataRead = [];
var port = -1;
var protocol = "none";
var socketId = 0;
var succeeded = false;
var waitCount = 0;
var request = "<this should be set based on protocol>";
var expectedResponsePattern = "<this, too>";
// Many thanks to Dennis for his 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 assertDataMatch(expecterDataPattern, data) {
var match = !!data.match(expecterDataPattern);
chrome.test.assertTrue(match, "Received data does not match. " +
"Expected pattern: \"" + expecterDataPattern + "\" - " +
"Data received: \"" + data + "\".");
}
var testSocketCreation = function() {
function onCreate(socketInfo) {
function onGetInfo(info) {
chrome.test.assertEq(info.socketType, protocol);
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');
}
socket.destroy(socketInfo.socketId);
socket.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.
socket.getInfo(socketInfo.socketId, onGetInfo);
}
socket.create(protocol, {}, onCreate);
};
var testGetInfo = function() {
};
function onDataRead(readInfo) {
if (readInfo.resultCode > 0 || readInfo.data.byteLength > 0) {
chrome.test.assertEq(readInfo.resultCode, readInfo.data.byteLength);
}
arrayBuffer2String(readInfo.data, function(s) {
dataAsString = s; // save this for error reporting
assertDataMatch(expectedResponsePattern, dataAsString);
succeeded = true;
chrome.test.succeed();
});
}
function onWriteOrSendToComplete(writeInfo) {
bytesWritten += writeInfo.bytesWritten;
if (bytesWritten == request.length) {
if (protocol == "tcp")
socket.read(socketId, onDataRead);
else
socket.recvFrom(socketId, onDataRead);
}
}
function onSetKeepAlive(result) {
if (protocol == "tcp")
chrome.test.assertTrue(result, "setKeepAlive failed for TCP.");
else
chrome.test.assertFalse(result, "setKeepAlive did not fail for UDP.");
string2ArrayBuffer(request, function(arrayBuffer) {
if (protocol == "tcp")
socket.write(socketId, arrayBuffer, onWriteOrSendToComplete);
else
socket.sendTo(socketId, arrayBuffer, address, port,
onWriteOrSendToComplete);
});
}
function onSetNoDelay(result) {
if (protocol == "tcp")
chrome.test.assertTrue(result, "setNoDelay failed for TCP.");
else
chrome.test.assertFalse(result, "setNoDelay did not fail for UDP.");
socket.setKeepAlive(socketId, true, 1000, onSetKeepAlive);
}
function onGetInfo(result) {
chrome.test.assertTrue(!!result.localAddress,
"Bound socket should always have local address");
chrome.test.assertTrue(!!result.localPort,
"Bound socket should always have local port");
chrome.test.assertEq(result.socketType, protocol, "Unexpected socketType");
if (protocol == "tcp") {
// 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, port,
"Peer port should be the listen server");
chrome.test.assertTrue(result.connected, "Socket should be connected");
} else {
chrome.test.assertFalse(result.connected, "UDP socket was not connected");
chrome.test.assertTrue(!result.peerAddress,
"Unconnected UDP socket should not have peer address");
chrome.test.assertTrue(!result.peerPort,
"Unconnected UDP socket should not have peer port");
}
socket.setNoDelay(socketId, true, onSetNoDelay);
}
function onConnectOrBindComplete(result) {
chrome.test.assertEq(0, result,
"Connect or bind failed with error " + result);
if (result == 0) {
socket.getInfo(socketId, onGetInfo);
}
}
function onCreate(socketInfo) {
socketId = socketInfo.socketId;
chrome.test.assertTrue(socketId > 0, "failed to create socket");
if (protocol == "tcp")
socket.connect(socketId, address, port, onConnectOrBindComplete);
else
socket.bind(socketId, "0.0.0.0", 0, onConnectOrBindComplete);
}
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 testSending = function() {
dataRead = "";
succeeded = false;
waitCount = 0;
setTimeout(waitForBlockingOperation, 1000);
socket.create(protocol, {}, onCreate);
};
// Tests listening on a socket and sending/receiving from accepted sockets.
var testSocketListening = function() {
var tmpSocketId = 0;
function onServerSocketAccept(acceptInfo) {
chrome.test.assertEq(0, acceptInfo.resultCode);
var acceptedSocketId = acceptInfo.socketId;
socket.read(acceptedSocketId, function(readInfo) {
arrayBuffer2String(readInfo.data, function (s) {
assertDataMatch(request, s);
// Rather than using a timeout, use another read to detect the peer
// termination.
socket.read(acceptedSocketId, function(readInfo2) {
chrome.test.assertEq(0, readInfo2.resultCode);
socket.getInfo(acceptedSocketId, function(info) {
chrome.test.assertFalse(info.connected);
// Use a third read to make sure net::ERR_SOCKET_NOT_CONNECTED (-15)
// is received for subsequent reads.
socket.read(acceptedSocketId, function(readInfo3) {
chrome.test.assertEq(-15, readInfo3.resultCode);
socket.destroy(socketId);
chrome.test.succeed();
});
});
});
});
});
}
function onListen(result) {
chrome.test.assertEq(0, result, "Listen failed.");
socket.accept(socketId, onServerSocketAccept);
// Trying to schedule a second accept callback should fail.
socket.accept(socketId, function(acceptInfo) {
chrome.test.assertEq(-2, acceptInfo.resultCode);
});
// Create a new socket to connect to the TCP server.
socket.create('tcp', {}, function(socketInfo) {
tmpSocketId = socketInfo.socketId;
socket.connect(tmpSocketId, address, port,
function(result) {
chrome.test.assertEq(0, result, "Connect failed");
// Write.
string2ArrayBuffer(request, function(buf) {
socket.write(tmpSocketId, buf, function() {
socket.disconnect(tmpSocketId);
});
});
});
});
}
function onServerSocketCreate(socketInfo) {
socketId = socketInfo.socketId;
socket.listen(socketId, address, port, onListen);
}
socket.create('tcp', {}, onServerSocketCreate);
};
// Tests creation of a TCP listening socket on a port that is already in use.
var testSocketListenInUse = function() {
var tmpSocketId;
function onAccept(result) {
chrome.test.assertNoLastError();
chrome.test.assertEq(-2, result.resultCode);
socket.destroy(socketId);
socket.destroy(tmpSocketId);
chrome.test.succeed();
}
function onSecondSocketListen(result) {
chrome.test.assertLastError("Could not listen on the specified port.");
chrome.test.assertEq(-147, result);
// Calling accept on this socket should fail since it isn't listening.
socket.accept(tmpSocketId, onAccept);
}
function onSecondSocketCreate(socketInfo) {
chrome.test.assertNoLastError();
tmpSocketId = socketInfo.socketId;
socket.listen(tmpSocketId, address, port, onSecondSocketListen);
}
function onFirstSocketListen(result) {
chrome.test.assertNoLastError();
chrome.test.assertEq(0, result);
socket.create('tcp', {}, onSecondSocketCreate);
}
function onFirstSocketCreate(socketInfo) {
chrome.test.assertNoLastError();
socketId = socketInfo.socketId;
socket.listen(socketId, address, port, onFirstSocketListen);
}
socket.create('tcp', {}, onFirstSocketCreate);
};
var testPendingCallback = function() {
dataRead = "";
succeeded = false;
waitCount = 0;
console.log("calling create");
chrome.socket.create(protocol, null, onCreate);
function onCreate(createInfo) {
chrome.test.assertTrue(createInfo.socketId > 0, "failed to create socket");
socketId = createInfo.socketId;
console.log("calling connect");
if (protocol == "tcp")
chrome.socket.connect(socketId, address, port, onConnect1);
else
chrome.socket.bind(socketId, "0.0.0.0", 0, onConnect1);
}
function onConnect1(result) {
chrome.test.assertEq(0, result, "failed to connect");
console.log("Socket connect: result=" + result, chrome.runtime.lastError);
console.log("calling read with readCB1 callback");
if (protocol == "tcp")
chrome.socket.read(socketId, readCB1);
else
chrome.socket.recvFrom(socketId, readCB1);
console.log("calling disconnect");
chrome.socket.disconnect(socketId);
console.log("calling connect");
if (protocol == "tcp")
chrome.socket.connect(socketId, address, port, onConnect2);
else
chrome.socket.bind(socketId, "0.0.0.0", 0, onConnect2);
}
function onConnect2(result) {
chrome.test.assertEq(0, result, "failed to connect");
console.log("Socket connect: result=" + result, chrome.runtime.lastError);
console.log("calling read with readCB2 callback");
if (protocol == "tcp")
chrome.socket.read(socketId, readCB2);
else
chrome.socket.recvFrom(socketId, readCB2);
string2ArrayBuffer(request, function (arrayBuffer) {
if (protocol == "tcp")
chrome.socket.write(socketId, arrayBuffer, onWriteComplete);
else
chrome.socket.sendTo(
socketId, arrayBuffer, address, port, onWriteComplete);
});
}
function onWriteComplete(res) {
console.log("write callback: bytesWritten=" + res.bytesWritten);
}
// Callback 1 for initial read call
function readCB1(readInfo) {
console.log("Socket read CB1: result=" + readInfo.resultCode,
chrome.runtime.lastError);
// We disconnect the socket right after calling read(), so behavior here
// is undefined.
// TODO(devlin): Why do we do that? What are we trying to do?
}
// Second callback, for read call after re-connect
function readCB2(readInfo) {
console.log("Socket read CB2: result=" + readInfo.resultCode,
chrome.runtime.lastError);
if (readInfo.resultCode === -1) {
chrome.test.fail("Unable to register a read 2nd callback on the socket!");
} else if (readInfo.resultCode < 0) {
chrome.test.fail("Error reading from socket: " + readInfo.resultCode);
}
else {
arrayBuffer2String(readInfo.data, function (s) {
assertDataMatch(expectedResponsePattern, s);
console.log("Success!");
succeeded = true;
chrome.test.succeed();
});
}
}
}
// See http://crbug.com/418229.
var testUsingTCPSocketOnUDPMethods = function() {
if (protocol == "udp") {
socket.create("tcp", function(createInfo) {
socket.recvFrom(createInfo.socketId, 256, function(recvFromInfo) {
chrome.test.assertTrue(recvFromInfo.resultCode < 0);
chrome.test.succeed();
});
});
function onSendToComplete(writeInfo) {
chrome.test.assertTrue(writeInfo.bytesWritten < 0);
chrome.test.succeed();
}
string2ArrayBuffer(request, function(arrayBuffer) {
socket.create("tcp", function(createInfo) {
socket.sendTo(createInfo.socketId, arrayBuffer, address, port,
onSendToComplete);
});
});
} else {
// We only run this test when the protocol is UDP to
// avoid running it multiple times unnecessarily.
chrome.test.succeed();
}
};
var onMessageReply = function(message) {
var parts = message.split(":");
var test_type = parts[0];
address = parts[1];
port = parseInt(parts[2]);
console.log("Running tests, protocol " + test_type + ", echo server " +
address + ":" + port);
if (test_type == 'tcp_server') {
chrome.test.runTests([
testSocketListening,
testSocketListenInUse
]);
} else if (test_type == 'multicast') {
console.log("Running multicast tests");
chrome.test.runTests([ testMulticast ]);
} else {
protocol = test_type;
if (protocol == "udp") {
request = udpRequest;
expectedResponsePattern = udpExpectedResponsePattern;
} else {
request = tcpRequest;
expectedResponsePattern = tcpExpectedResponsePattern;
}
chrome.test.runTests([
testSocketCreation,
testSending,
testPendingCallback,
testUsingTCPSocketOnUDPMethods]);
}
};
// 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);