| // 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 TCP_REQUEST = 'POST /echo HTTP/1.1\r\n' + |
| 'Content-Length: 19\r\n\r\n' + |
| '0100000005320000005'; |
| const TCP_EXPECTED_RESPONSE_PATTERN = /\n0100000005320000005$/; |
| |
| // UDP tests use a server that just echoes back the request. |
| const UDP_REQUEST = '0100000005320000005'; |
| const UDP_EXPECTED_RESPONSE_PATTERN = /^0100000005320000005$/; |
| |
| const socket = chrome.socket; |
| let address; |
| let bytesWritten = 0; |
| let dataAsString; |
| let dataRead = []; |
| let port = -1; |
| let protocol = 'none'; |
| let socketId = 0; |
| let succeeded = false; |
| let waitCount = 0; |
| let request = '<this should be set based on protocol>'; |
| let expectedResponsePattern = '<this, too>'; |
| |
| function string2ArrayBuffer(string, callback) { |
| const blob = new Blob([string]); |
| const f = new FileReader(); |
| f.onload = function(e) { |
| callback(e.target.result); |
| }; |
| f.readAsArrayBuffer(blob); |
| } |
| |
| function arrayBuffer2String(buf, callback) { |
| const blob = new Blob([new Uint8Array(buf)]); |
| const f = new FileReader(); |
| f.onload = function(e) { |
| callback(e.target.result); |
| }; |
| f.readAsText(blob); |
| } |
| |
| function assertDataMatch(expecterDataPattern, data) { |
| const match = !!data.match(expecterDataPattern); |
| chrome.test.assertTrue( |
| match, |
| 'Received data does not match. Expected pattern: ' + |
| `"${expecterDataPattern}" - Data received: "${data}".`); |
| } |
| |
| const 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); |
| }; |
| |
| |
| const 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}>.`); |
| } |
| } |
| |
| const 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. |
| const testSocketListening = function() { |
| let tmpSocketId = 0; |
| |
| function onServerSocketAccept(acceptInfo) { |
| chrome.test.assertEq(0, acceptInfo.resultCode); |
| const 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. |
| const testSocketListenInUse = function() { |
| let 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); |
| }; |
| |
| const testPendingCallback = function() { |
| dataRead = ''; |
| succeeded = false; |
| waitCount = 0; |
| |
| console.info('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.info('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.info(`Socket connect: result=${result}`, chrome.runtime.lastError); |
| |
| console.info('calling read with readCB1 callback'); |
| if (protocol === 'tcp') { |
| chrome.socket.read(socketId, readCB1); |
| } else { |
| chrome.socket.recvFrom(socketId, readCB1); |
| } |
| |
| console.info('calling disconnect'); |
| chrome.socket.disconnect(socketId); |
| |
| console.info('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.info(`Socket connect: result=${result}`, chrome.runtime.lastError); |
| |
| console.info('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.info(`write callback: bytesWritten=${res.bytesWritten}`); |
| } |
| |
| // Callback 1 for initial read call |
| function readCB1(readInfo) { |
| console.info( |
| `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.info( |
| `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.info('Success!'); |
| succeeded = true; |
| chrome.test.succeed(); |
| }); |
| } |
| } |
| }; |
| |
| // See http://crbug.com/41134291. |
| const 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(); |
| } |
| }; |
| |
| const testWriteQuota = function() { |
| console.info('calling create, protoocol=', protocol); |
| chrome.socket.create(protocol, {}, onCreate); |
| |
| function onCreate(createInfo) { |
| chrome.test.assertTrue(createInfo.socketId > 0, 'failed to create socket'); |
| socketId = createInfo.socketId; |
| if (protocol === 'tcp') { |
| console.info('calling connect'); |
| chrome.socket.connect(socketId, address, port, onConnectOrBindComplete); |
| } else { |
| console.info('calling bind'); |
| socket.bind(socketId, '0.0.0.0', 0, onConnectOrBindComplete); |
| } |
| } |
| |
| function onConnectOrBindComplete(result) { |
| chrome.test.assertEq(0, result, 'failed to connect'); |
| console.info(`Socket connect: result=${result}`, chrome.runtime.lastError); |
| |
| string2ArrayBuffer(request, function(arrayBuffer) { |
| if (protocol === 'tcp') { |
| chrome.socket.write(socketId, arrayBuffer, onComplete); |
| } else { |
| socket.sendTo(socketId, arrayBuffer, address, port, onComplete); |
| } |
| }); |
| } |
| |
| function onComplete(result) { |
| console.info(`onComplete: result=${result}`, chrome.runtime.lastError); |
| if (chrome.runtime.lastError && |
| chrome.runtime.lastError.message === 'Exceeded write quota.') { |
| chrome.test.succeed(); |
| return; |
| } |
| |
| chrome.test.fail('Write quota not enforced'); |
| } |
| }; |
| |
| const onMessageReply = function(message) { |
| const parts = message.split(':'); |
| const testType = parts[0]; |
| address = parts[1]; |
| port = parseInt(parts[2]); |
| console.info( |
| `Running tests, protocol ${testType}, echo server ` + |
| `${address}:${port}`); |
| if (testType === 'tcp_server') { |
| chrome.test.runTests([ |
| testSocketListening, |
| testSocketListenInUse, |
| ]); |
| } else if (testType === 'multicast') { |
| console.info('Running multicast tests'); |
| chrome.test.runTests([testMulticast]); |
| } else if (testType === 'tcp_write_quota') { |
| console.info('Running TCP write quota tests'); |
| protocol = 'tcp'; |
| chrome.test.runTests([testWriteQuota]); |
| } else if (testType === 'udp_sendTo_quota') { |
| console.info('Running UDP sendTo write quota tests'); |
| protocol = 'udp'; |
| chrome.test.runTests([testWriteQuota]); |
| } else { |
| protocol = testType; |
| if (protocol === 'udp') { |
| request = UDP_REQUEST; |
| expectedResponsePattern = UDP_EXPECTED_RESPONSE_PATTERN; |
| } else { |
| request = TCP_REQUEST; |
| expectedResponsePattern = TCP_EXPECTED_RESPONSE_PATTERN; |
| } |
| 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); |