559 lines
17 KiB
Lua
559 lines
17 KiB
Lua
-- hs.socket tests
|
|
hs.socket = require("hs.socket")
|
|
hs.timer = require("hs.timer")
|
|
hs.fnutils = require("hs.fnutils")
|
|
|
|
require "test_udpsocket"
|
|
|
|
-- globals for async TCP tests
|
|
port = 9001
|
|
sockfile = "/tmp/sock"
|
|
|
|
callback = function(data, tag)
|
|
readData = data
|
|
readTag = tag
|
|
end
|
|
|
|
-- constructors
|
|
function testTcpSocketInstanceCreation()
|
|
assertIsUserdataOfType("hs.socket", hs.socket.new())
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpSocketInstanceCreationWithCallback()
|
|
assertIsUserdataOfType("hs.socket", hs.socket.new(print))
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpListenerSocketCreation()
|
|
assertIsUserdataOfType("hs.socket", hs.socket.server(port))
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpListenerSocketCreationWithCallback()
|
|
assertIsUserdataOfType("hs.socket", hs.socket.server(port, print))
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpListenerSocketAttributes()
|
|
local server = hs.socket.server(port, print)
|
|
|
|
local info = server:info()
|
|
|
|
assertIsEqual("SERVER", info.userData)
|
|
assertIsEqual("", info.unixSocketPath)
|
|
assertIsEqual(port, info.localPort)
|
|
assertIsEqual("0.0.0.0", info.localHost)
|
|
assertIsEqual(0, info.connectedPort)
|
|
assertIsEqual("", info.connectedHost)
|
|
assertFalse(info.isDisconnected)
|
|
assertFalse(info.isConnected)
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpUnixListenerSocketAttributes()
|
|
local server = hs.socket.server(sockfile, print)
|
|
|
|
local info = server:info()
|
|
|
|
assertIsEqual("SERVER", info.userData)
|
|
assertIsEqual(sockfile, info.unixSocketPath)
|
|
assertIsEqual(0, info.localPort)
|
|
assertIsEqual("", info.localHost)
|
|
assertIsEqual(0, info.connectedPort)
|
|
assertIsEqual("", info.connectedHost)
|
|
assertFalse(info.isDisconnected)
|
|
assertFalse(info.isConnected)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- reusing client and server sockets
|
|
function testTcpDisconnectAndReuseValues()
|
|
if (type(serverLocalPort) == "number" and serverLocalPort == port and
|
|
type(clientConnectedPort) == "number" and clientConnectedPort == port and
|
|
type(serverDisconnectedPort) == "number" and serverDisconnectedPort == 0 and
|
|
type(clientDisconnectedPort) == "number" and clientDisconnectedPort == 0 and
|
|
type(server2LocalPort) == "number" and server2LocalPort == port and
|
|
type(client2ConnectedPort) == "number" and client2ConnectedPort == port and
|
|
type(server2DisconnectedPort) == "number" and server2DisconnectedPort == 0 and
|
|
type(client2DisconnectedPort) == "number" and client2DisconnectedPort == 0) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpDisconnectAndReuse()
|
|
server = hs.socket.server(port)
|
|
client = hs.socket.new():connect("localhost", port, function()
|
|
serverLocalPort = server:info().localPort
|
|
clientConnectedPort = client:info().connectedPort
|
|
|
|
server:disconnect()
|
|
|
|
hs.timer.doAfter(0.1, function()
|
|
serverDisconnectedPort = server:info().localPort
|
|
clientDisconnectedPort = client:info().connectedPort
|
|
|
|
client:listen(port) -- switch roles
|
|
server:connect("localhost", port, function()
|
|
server2LocalPort = client:info().localPort
|
|
client2ConnectedPort = server:info().connectedPort
|
|
|
|
client:disconnect()
|
|
|
|
hs.timer.doAfter(0.1, function()
|
|
server2DisconnectedPort = client:info().localPort
|
|
client2DisconnectedPort = server:info().connectedPort
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- multiple client connection counts
|
|
function testTcpConnectedValues()
|
|
if (type(serverConnected) == "boolean" and serverConnected == true and
|
|
type(serverConnections) == "number" and serverConnections == 3 and
|
|
type(clientConnected) == "boolean" and clientConnected == true and
|
|
type(clientConnections) == "number" and clientConnections == 1 and
|
|
type(serverConnectedAfterDisconnect) == "boolean" and serverConnectedAfterDisconnect == false and
|
|
type(serverConnectionsAfterDisconnect) == "number" and serverConnectionsAfterDisconnect == 0 and
|
|
type(clientConnectedAfterDisconnect) == "boolean" and clientConnectedAfterDisconnect == false and
|
|
type(clientConnectionsAfterDisconnect) == "number" and clientConnectionsAfterDisconnect == 0) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpConnected()
|
|
local server = hs.socket.server(port)
|
|
local client = hs.socket.new():connect("localhost", port)
|
|
local client2 = hs.socket.new():connect("localhost", port)
|
|
local client3 = hs.socket.new():connect("localhost", port)
|
|
|
|
hs.timer.doAfter(0.2, function()
|
|
serverConnected = server:connected()
|
|
serverConnections = server:connections()
|
|
clientConnected = client:connected()
|
|
clientConnections = client:connections()
|
|
|
|
server:disconnect()
|
|
|
|
hs.timer.doAfter(0.2, function()
|
|
serverConnectedAfterDisconnect = server:connected()
|
|
serverConnectionsAfterDisconnect = server:connections()
|
|
clientConnectedAfterDisconnect = client:connected()
|
|
clientConnectionsAfterDisconnect = client:connections()
|
|
end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- test failure to connect already connected sockets
|
|
function testTcpAlreadyConnectedValues()
|
|
if (type(server1Connected) == "boolean" and server1Connected == true and
|
|
type(server1Userdata) == "string" and server1Userdata == "SERVER" and
|
|
type(server2Connected) == "boolean" and server2Connected == false and
|
|
type(server2Userdata) == "string" and server2Userdata == "" and
|
|
type(clientConnectedPort) == "number" and clientConnectedPort == port and
|
|
type(server3Connected) == "boolean" and server3Connected == true and
|
|
type(server3Userdata) == "string" and server3Userdata == "SERVER" and
|
|
type(server4Connected) == "boolean" and server4Connected == false and
|
|
type(server4Userdata) == "string" and server4Userdata == "" and
|
|
type(clientSockfile) == "string" and clientSockfile == sockfile) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpAlreadyConnected()
|
|
server1 = hs.socket.server(port)
|
|
server2 = hs.socket.server(port)
|
|
client = hs.socket.new():connect("localhost", port, function()
|
|
server1Connected = server1:connected()
|
|
server1Userdata = server1:info().userData
|
|
|
|
-- no listening socket created because local port already in use
|
|
server2Connected = server2:connected()
|
|
server2Userdata = server2:info().userData
|
|
|
|
-- port should not change because already connected
|
|
client:connect("localhost", port + 1)
|
|
hs.timer.doAfter(0.2, function() clientConnectedPort = client:info().connectedPort end)
|
|
end)
|
|
|
|
server3 = hs.socket.server(sockfile)
|
|
server4 = hs.socket.server(sockfile)
|
|
clientUnix = hs.socket.new():connect(sockfile, function()
|
|
server3Connected = server3:connected()
|
|
server3Userdata = server3:info().userData
|
|
|
|
-- no listening socket created because sockfile already bound for listening
|
|
server4Connected = server4:connected()
|
|
server4Userdata = server4:info().userData
|
|
|
|
-- connected sockfile not change because already connected
|
|
clientUnix:connect(sockfile.."2")
|
|
hs.timer.doAfter(0.2, function() clientSockfile = clientUnix:info().unixSocketPath end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- client and server userdata strings
|
|
function testTcpUserdataStringsValues()
|
|
local serverHostPort = serverLocalHost..":"..serverLocalPort
|
|
local clientHostPort = clientConnectedHost..":"..clientConnectedPort
|
|
|
|
local serverUserdataHostPort = hs.fnutils.split(serverUserdataString, " ")[2]
|
|
local clientUserdataHostPort = hs.fnutils.split(clientUserdataString, " ")[2]
|
|
|
|
if (serverHostPort == serverUserdataHostPort and clientHostPort == clientUserdataHostPort) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpUserdataStrings()
|
|
server = hs.socket.server(port)
|
|
client = hs.socket.new():connect("localhost", port, function()
|
|
serverUserdataString = tostring(server)
|
|
serverLocalHost = server:info().localHost
|
|
serverLocalPort = server:info().localPort
|
|
|
|
clientUserdataString = tostring(client)
|
|
clientConnectedHost = client:info().connectedHost
|
|
clientConnectedPort = client:info().connectedPort
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- reading and writing data
|
|
function testTcpClientServerReadWriteDelimiterValues()
|
|
if (type(serverReadData) == "string" and serverReadData == "Hi from client\n" and
|
|
type(clientReadData) == "string" and clientReadData == "Hello from server\n") then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpClientServerReadWriteDelimiter()
|
|
server = hs.socket.server(port, print)
|
|
client = hs.socket.new(print):connect("localhost", port, function()
|
|
-- clear and set new callbacks
|
|
local function serverCallback(data) serverReadData = data end
|
|
local function clientCallback(data) clientReadData = data end
|
|
server:setCallback(nil)
|
|
client:setCallback(nil)
|
|
server:setCallback(serverCallback)
|
|
client:setCallback(clientCallback)
|
|
|
|
client:write("Hi from client\n", function()
|
|
assertIsUserdataOfType("hs.socket", server:read("\n"))
|
|
server:write("Hello from server\n", function()
|
|
assertIsUserdataOfType("hs.socket", client:read("\n"))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpClientServerReadWriteBytesValues()
|
|
if (type(serverReadData) == "string" and serverReadData == "Hi fr" and
|
|
type(clientReadData) == "string" and clientReadData == "Hello") then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpClientServerReadWriteBytes()
|
|
local function serverCallback(data) serverReadData = data end
|
|
local function clientCallback(data) clientReadData = data end
|
|
|
|
server = hs.socket.server(port, serverCallback)
|
|
client = hs.socket.new(clientCallback):connect("localhost", port, function()
|
|
local tag = 10
|
|
client:write("Hi from client\n", tag, function(writeTag)
|
|
assertIsEqual(tag, writeTag)
|
|
server:read(5)
|
|
server:write("Hello from server\n", function(writeTag)
|
|
assertIsEqual(-1, writeTag)
|
|
client:read(5)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpUnixClientServerReadWriteBytesValues()
|
|
print(serverReadData)
|
|
print(clientReadData)
|
|
if (type(serverReadData) == "string" and serverReadData == "Hi fr" and
|
|
type(clientReadData) == "string" and clientReadData == "Hello") then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpUnixClientServerReadWriteBytes()
|
|
local function serverCallback(data) serverReadData = data end
|
|
local function clientCallback(data) clientReadData = data end
|
|
|
|
server = hs.socket.server(sockfile, serverCallback)
|
|
client = hs.socket.new(clientCallback):connect(sockfile, function()
|
|
local tag = 10
|
|
client:write("Hi from client\n", tag, function(writeTag)
|
|
assertIsEqual(tag, writeTag)
|
|
server:read(5)
|
|
server:write("Hello from server\n", function(writeTag)
|
|
assertIsEqual(-1, writeTag)
|
|
client:read(5)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- tagging
|
|
function testTcpTaggingValues()
|
|
if (type(readData) == "string" and readData:sub(1,6) == "<HTML>" and
|
|
type(clientConnected) == "boolean" and clientConnected == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTagging()
|
|
local TAG_HTTP_HEADER = 1
|
|
local TAG_HTTP_CONTENT = 2
|
|
|
|
local function httpCallback(data, tag)
|
|
if tag == TAG_HTTP_HEADER then
|
|
local _, _, contentLength = data:find("\r\nContent%-Length: (%d+)\r\n");
|
|
client:read(tonumber(contentLength), TAG_HTTP_CONTENT)
|
|
elseif tag == TAG_HTTP_CONTENT then
|
|
readData = data
|
|
end
|
|
end
|
|
|
|
client = hs.socket.new(httpCallback):connect("google.com", 80, function()
|
|
client:write("GET /index.html HTTP/1.0\r\nHost: google.com\r\n\r\n")
|
|
client:read("\r\n\r\n", TAG_HTTP_HEADER)
|
|
hs.timer.doAfter(2, function() clientConnected = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- timeout
|
|
function testTcpClientServerTimeoutValues()
|
|
if (type(clientConnected) == "boolean" and clientConnected == true and
|
|
type(clientConnectedAfterTimeout) == "boolean" and clientConnectedAfterTimeout == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpClientServerTimeout()
|
|
local server = hs.socket.server(port, print)
|
|
client = hs.socket.new(print):connect("localhost", port, function()
|
|
clientConnected = client:connected()
|
|
client:setTimeout(1)
|
|
client:read("\n") -- waiting for server to send data ending in '\n' which will time out
|
|
hs.timer.doAfter(2, function() clientConnectedAfterTimeout = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- TLS
|
|
function testTcpTlsValues()
|
|
if (type(readData) == "string" and readData:sub(1,15) == "HTTP/1.1 200 OK" and
|
|
type(clientConnected) == "boolean" and clientConnected == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTls()
|
|
client = hs.socket.new(callback):connect("github.com", 443, function()
|
|
client:startTLS()
|
|
client:write("HEAD / HTTP/1.0\r\nHost: github.com\r\nConnection: Close\r\n\r\n");
|
|
client:read("\r\n\r\n");
|
|
hs.timer.doAfter(2, function() clientConnected = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- make sure github disconnects us if operations attempted on unsecured socket
|
|
function testTcpTlsRequiredByServerValues()
|
|
if (type(readData) == "nil" and readData == nil and
|
|
type(clientConnected) == "boolean" and clientConnected == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTlsRequiredByServer()
|
|
client = hs.socket.new(callback):connect("github.com", 443, function()
|
|
client:write("HEAD / HTTP/1.0\r\nHost: github.com\r\nConnection: Close\r\n\r\n");
|
|
client:read("\r\n\r\n");
|
|
hs.timer.doAfter(2, function() clientConnected = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- verify peer name
|
|
function testTcpTlsVerifyPeerValues()
|
|
if (type(readData) == "string" and readData:sub(1,15) == "HTTP/1.1 200 OK") then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTlsVerifyPeer()
|
|
client = hs.socket.new(callback):connect("github.com", 443, function()
|
|
client:startTLS("github.com")
|
|
client:write("HEAD / HTTP/1.0\r\nHost: github.com\r\nConnection: Close\r\n\r\n");
|
|
client:read("\r\n\r\n");
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- make sure TLS handshake fails on bad peer
|
|
function testTcpTlsVerifyBadPeerFailsValues()
|
|
if (type(readData) == "nil" and readData == nil and
|
|
type(clientConnected) == "boolean" and clientConnected == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTlsVerifyBadPeerFails()
|
|
client = hs.socket.new(callback):connect("github.com", 443, function()
|
|
client:startTLS("bitbucket.org")
|
|
client:write("HEAD / HTTP/1.0\r\nHost: github.com\r\nConnection: Close\r\n\r\n");
|
|
client:read("\r\n\r\n");
|
|
hs.timer.doAfter(2, function() clientConnected = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- no verification should work fine
|
|
function testTcpTlsNoVerifyValues()
|
|
if (type(readData) == "string" and readData:sub(1,15) == "HTTP/1.1 200 OK" and
|
|
type(clientConnected) == "boolean" and clientConnected == false) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpTlsNoVerify()
|
|
client = hs.socket.new(callback):connect("github.com", 443, function()
|
|
client:startTLS(false)
|
|
client:write("HEAD / HTTP/1.0\r\nHost: github.com\r\nConnection: Close\r\n\r\n");
|
|
client:read("\r\n\r\n");
|
|
hs.timer.doAfter(2, function() clientConnected = client:connected() end)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- no callback should fail on read attempt
|
|
function testTcpNoCallbackReadValues()
|
|
if (type(result) == "nil" and result == nil) then
|
|
return success()
|
|
else
|
|
return "Waiting for success..."
|
|
end
|
|
end
|
|
|
|
function testTcpNoCallbackRead()
|
|
local server = hs.socket.server(port)
|
|
client = hs.socket.new():connect("localhost", port, function()
|
|
result = client:read(1)
|
|
end)
|
|
|
|
return success()
|
|
end
|
|
|
|
-- address parsing
|
|
function testTcpParseAddress()
|
|
-- sockaddr structure:
|
|
-- 1 byte size
|
|
-- 1 byte family
|
|
-- 2 bytes port
|
|
-- IPv4
|
|
-- 4 bytes IPv4 address
|
|
-- 8 bytes zeros - (total 16 bytes)
|
|
-- IPv6
|
|
-- 4 bytes flowinfo
|
|
-- 16 bytes IPv6 address
|
|
-- 4 bytes scope ID - (total 28 bytes)
|
|
|
|
-- ipv4.google.com
|
|
host_IPv4 = "216.58.218.110"
|
|
port_IPv4 = 80
|
|
AF_IPv4 = 2 -- AF_INET
|
|
-- IPv4 80 216.58.218.110
|
|
addr_IPv4 = string.char(16,02,0,80,216,58,218,110,0,0,0,0,0,0,0,0)
|
|
|
|
-- ipv6.google.com
|
|
host_IPv6 = "2607:f8b0:4000:800::200e"
|
|
port_IPv6 = 80
|
|
AF_IPv6 = 30 -- AF_INET6
|
|
-- IPv6 80 26 07 : f8 b0 : 40 00 : 08 00 : : 20 0e
|
|
addr_IPv6 = string.char(28,30,0,80,0,0,0,0,0x26,0x07,0xf8,0xb0,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x0e,0,0,0,0)
|
|
|
|
|
|
local addr4 = hs.socket.parseAddress(addr_IPv4)
|
|
local addr6 = hs.socket.parseAddress(addr_IPv6)
|
|
|
|
assertIsEqual(host_IPv4, addr4.host)
|
|
assertIsEqual(port_IPv4, addr4.port)
|
|
assertIsEqual(AF_IPv4, addr4.addressFamily)
|
|
|
|
assertIsEqual(host_IPv6, addr6.host)
|
|
assertIsEqual(port_IPv6, addr6.port)
|
|
assertIsEqual(AF_IPv6, addr6.addressFamily)
|
|
|
|
return success()
|
|
end
|
|
|
|
function testTcpParseBadAddress()
|
|
assertIsNil(hs.socket.parseAddress("nonsense"))
|
|
|
|
return success()
|
|
end
|