# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua::Stream;

repeat_each(2);

plan tests => repeat_each() * (3 * blocks() + 14);

our $HtmlDir = html_dir;

$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';

#log_level 'warn';

no_long_string();
#no_diff();
#no_shuffle();
check_accum_error_log();
run_tests();

__DATA__

=== TEST 1: sanity
--- stream_server_config

    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        local port = $TEST_NGINX_MEMCACHED_PORT
        udp:settimeout(1000) -- 1 sec

        local ok, err = udp:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected")

        local req = "\0\1\0\0\0\1\0\0flush_all\r\n"
        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("received ", #data, " bytes: ", data)
    }

--- config
    server_tokens off;
--- stream_response eval
"connected\nreceived 12 bytes: \x{00}\x{01}\x{00}\x{00}\x{00}\x{01}\x{00}\x{00}OK\x{0d}\x{0a}"
--- no_error_log
[error]
--- log_level: debug
--- error_log
lua udp socket receive buffer size: 8192



=== TEST 2: multiple parallel queries
--- stream_server_config

    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        local port = $TEST_NGINX_MEMCACHED_PORT
        udp:settimeout(1000) -- 1 sec

        local ok, err = udp:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected")

        local req = "\0\1\0\0\0\1\0\0flush_all\r\n"
        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        req = "\0\2\0\0\0\1\0\0flush_all\r\n"
        ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        ngx.sleep(0.05)

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("1: received ", #data, " bytes: ", data)

        data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("2: received ", #data, " bytes: ", data)
    }

--- config
    server_tokens off;
--- stream_response_like eval
"^connected\n"
."1: received 12 bytes: "
."\x{00}[\1\2]\x{00}\x{00}\x{00}\x{01}\x{00}\x{00}OK\x{0d}\x{0a}"
."2: received 12 bytes: "
."\x{00}[\1\2]\x{00}\x{00}\x{00}\x{01}\x{00}\x{00}OK\x{0d}\x{0a}\$"
--- no_error_log
[error]



=== TEST 3: access a TCP interface
--- stream_server_config

    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        local port = $TEST_NGINX_SERVER_PORT
        udp:settimeout(1000) -- 1 sec

        local ok, err = udp:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected")

        local req = "\0\1\0\0\0\1\0\0flush_all\r\n"
        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("received ", #data, " bytes: ", data)
    }

--- config
    server_tokens off;
--- stream_response
connected
failed to receive data: connection refused
--- error_log eval
qr/recv\(\) failed \(\d+: Connection refused\)/



=== TEST 4: access conflicts of connect() on shared udp objects
--- stream_config
    lua_package_path '$prefix/html/?.lua;;';
--- stream_server_config
    content_by_lua_block {
        local function f()
            local port = $TEST_NGINX_MEMCACHED_PORT
            local foo = require "foo"
            local udp = foo.get_udp()

            udp:settimeout(100) -- 100 ms

            local ok, err = udp:setpeername("127.0.0.1", port)
            if not ok then
                ngx.log(ngx.ERR, "failed to connect: ", err)
                return
            end

            print("test: connected")

            local data, err = udp:receive()
            if not data then
                ngx.say("failed to receive data: ", err)
                return
            end
            print("test: received ", #data, " bytes: ", data)
        end
        for i = 1, 170 do
            assert(ngx.timer.at(0, f))
        end
    }

--- user_files
>>> foo.lua
module("foo", package.seeall)

local udp

function get_udp()
    if not udp then
        udp = ngx.socket.udp()
    end

    return udp
end

--- stap2
M(http-lua-info) {
    printf("udp resume: %p\n", $coctx)
    print_ubacktrace()
}

--- stream_response
--- error_log eval
qr/content_by_lua\(nginx\.conf:\d+\):9: bad request/



=== TEST 5: access conflicts of receive() on shared udp objects
--- stream_config
    lua_package_path '$prefix/html/?.lua;;';
--- stream_server_config
    content_by_lua_block {
        function f()
            local port = $TEST_NGINX_MEMCACHED_PORT
            local foo = require "foo"
            local udp = foo.get_udp(port)

            local data, err = udp:receive()
            if not data then
                ngx.log(ngx.ERR, "failed to receive data: ", err)
                return ngx.exit(500)
            end
            ngx.print("received ", #data, " bytes: ", data)
        end

        for i = 1, 170 do
            assert(ngx.timer.at(0, f))
        end
    }

--- user_files
>>> foo.lua
module("foo", package.seeall)

local udp

function get_udp(port)
    if not udp then
        udp = ngx.socket.udp()

        udp:settimeout(100) -- 100ms

        local ok, err = udp:setpeername("127.0.0.1", port)
        if not ok then
            ngx.log(ngx.ERR, "failed to connect: ", err)
            return ngx.exit(500)
        end
    end

    return udp
end
--- stream_response
--- error_log eval
qr/content_by_lua\(nginx\.conf:\d+\):7: bad request/



=== TEST 6: connect again immediately
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.udp()
        local port = $TEST_NGINX_MEMCACHED_PORT

        local ok, err = sock:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        ok, err = sock:setpeername("127.0.0.1", port)
          if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected again: ", ok)

        local req = "\0\1\0\0\0\1\0\0flush_all\r\n"
        local ok, err = sock:send(req)
        if not ok then
            ngx.say("failed to send request: ", err)
            return
        end
        ngx.say("request sent: ", ok)

        local line, err = sock:receive()
        if line then
            ngx.say("received: ", line)

        else
            ngx.say("failed to receive: ", err)
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- stream_response eval
"connected: 1
connected again: 1
request sent: 1
received: \0\1\0\0\0\1\0\0OK\r\n
close: 1 nil
"
--- no_error_log
[error]
--- error_log eval
["lua reuse socket upstream", "lua udp socket reconnect without shutting down"]
--- log_level: debug



=== TEST 7: recv timeout
--- stream_server_config

    content_by_lua_block {
        local port = $TEST_NGINX_MEMCACHED_PORT

        local sock = ngx.socket.udp()
        sock:settimeout(100) -- 100 ms

        local ok, err = sock:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local line, err = sock:receive()
        if line then
            ngx.say("received: ", line)

        else
            ngx.say("failed to receive: ", err)
        end

        -- ok, err = sock:close()
        -- ngx.say("close: ", ok, " ", err)
    }

--- stream_response
connected: 1
failed to receive: timeout
--- error_log
lua udp socket read timed out



=== TEST 8: with an explicit receive buffer size argument
--- stream_server_config

    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        local port = $TEST_NGINX_MEMCACHED_PORT
        udp:settimeout(1000) -- 1 sec

        local ok, err = udp:setpeername("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected")

        local req = "\0\1\0\0\0\1\0\0flush_all\r\n"
        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive(1400)
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("received ", #data, " bytes: ", data)
    }

--- stream_response eval
"connected\nreceived 12 bytes: \x{00}\x{01}\x{00}\x{00}\x{00}\x{01}\x{00}\x{00}OK\x{0d}\x{0a}"
--- no_error_log
[error]
--- log_level: debug
--- error_log
lua udp socket receive buffer size: 1400



=== TEST 9: read timeout and re-receive
--- stream_server_config
    content_by_lua_block {
        local udp = ngx.socket.udp()
        udp:settimeout(30)
        local ok, err = udp:setpeername("127.0.0.1", 19232)
        if not ok then
            ngx.say("failed to setpeername: ", err)
            return
        end
        local ok, err = udp:send("blah")
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end
        for i = 1, 2 do
            local data, err = udp:receive()
            if err == "timeout" then
                -- continue
            else
                if not data then
                    ngx.say("failed to receive: ", err)
                    return
                end
                ngx.say("received: ", data)
                return
            end
        end

        ngx.say("timed out")
    }

--- udp_listen: 19232
--- udp_reply: hello world
--- udp_reply_delay: 45ms
--- stream_response
received: hello world
--- error_log
lua udp socket read timed out



=== TEST 10: access the google DNS server (using IP addr)
--- stream_server_config
    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        udp:settimeout(2000) -- 2 sec

        local ok, err = udp:setpeername("$TEST_NGINX_RESOLVER", 53)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local req = "\0}\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1"

        -- ngx.print(req)
        -- do return end

        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end

        if string.match(data, "\3www\6google\3com") then
            ngx.say("received a good response.")
        else
            ngx.say("received a bad response: ", #data, " bytes: ", data)
        end
    }

--- stream_response
received a good response.
--- no_error_log
[error]
--- log_level: debug
--- error_log
lua udp socket receive buffer size: 8192



=== TEST 11: access the google DNS server (using domain names)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        -- avoid flushing google in "check leak" testing mode:
        local counter = package.loaded.counter
        if not counter then
            counter = 1
        elseif counter >= 2 then
            return ngx.exit(503)
        else
            counter = counter + 1
        end
        package.loaded.counter = counter

        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        udp:settimeout(2000) -- 2 sec

        local ok, err = udp:setpeername("google-public-dns-a.google.com", 53)
        --local ok, err = udp:setpeername("127.0.1.1", 53)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local req = "\0}\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1"

        -- ngx.print(req)
        -- do return end

        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end

        if string.match(data, "\3www\6google\3com") then
            ngx.say("received a good response.")
        else
            ngx.say("received a bad response: ", #data, " bytes: ", data)
        end
    }

--- stream_response
received a good response.
--- no_error_log
[error]
--- log_level: debug
--- error_log
lua udp socket receive buffer size: 8192



=== TEST 12: datagram unix domain socket
--- stream_server_config

    content_by_lua_block {
        local socket = ngx.socket
        -- local socket = require "socket"

        local udp = socket.udp()

        udp:settimeout(2000) -- 1 sec

        local ok, err = udp:setpeername("unix:a.sock")
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected")

        local req = "hello,\nserver"
        local ok, err = udp:send(req)
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end

        local data, err = udp:receive()
        if not data then
            ngx.say("failed to receive data: ", err)
            return
        end
        ngx.print("received ", #data, " bytes: ", data)
    }

--- udp_listen: a.sock
--- udp_reply
hello,
client

--- stream_response
connected
received 14 bytes: hello,
client

--- stap2
probe syscall.socket, syscall.connect {
    print(name, "(", argstr, ")")
}

probe syscall.socket.return, syscall.connect.return {
    println(" = ", retstr)
}
--- no_error_log
[error]
[crit]
--- skip_eval: 3: $^O ne 'linux'



=== TEST 13: bad request tries to setpeer
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config

    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to set peer: ", err)
        else
            ngx.say("peer set")
        end
        function f()
            local sock = test.get_sock()
            sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.udp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
peer set

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 14: bad request tries to send
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config

    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to set peer: ", err)
        else
            ngx.say("peer set")
        end
        function f()
            local sock = test.get_sock()
            sock:send("a")
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.udp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
peer set

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 15: bad request tries to receive
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config

    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to set peer: ", err)
        else
            ngx.say("peer set")
        end

        local function f()
            local sock = test.get_sock()
            sock:receive()
        end

        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }

--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.udp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
peer set

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):13: bad request/

--- no_error_log
[alert]



=== TEST 16: bad request tries to close
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to set peer: ", err)
        else
            ngx.say("peer set")
        end
        local function f()
            local sock = test.get_sock()
            sock:close()
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.udp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
peer set

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 17: the upper bound of port range should be 2^16 - 1
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.udp()
        local ok, err = sock:setpeername("127.0.0.1", 65536)
        if not ok then
            ngx.say("failed to connect: ", err)
        end
    }
--- stream_response
failed to connect: bad port number: 65536
--- no_error_log
[error]



=== TEST 18: send boolean and nil
--- stream_server_config
    content_by_lua_block {
        local socket = ngx.socket
        local udp = socket.udp()
        local port = ngx.var.port
        udp:settimeout(1000) -- 1 sec

        local ok, err = udp:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local function send(data)
            local bytes, err = udp:send(data)
            if not bytes then
                ngx.say("failed to send: ", err)
                return
            end
            ngx.say("sent ok")
        end

        send(true)
        send(false)
        send(nil)
    }
--- stream_response
sent ok
sent ok
sent ok
--- no_error_log
[error]
--- grep_error_log eval
qr/sendto: fd:\d+ \d+ of \d+/
--- grep_error_log_out eval
qr/sendto: fd:\d+ 4 of 4
sendto: fd:\d+ 5 of 5
sendto: fd:\d+ 3 of 3/
--- log_level: debug



=== TEST 19: UDP socket GC'ed in preread phase without Lua content phase
--- stream_server_config
    preread_by_lua_block {
        do
            local udpsock = ngx.socket.udp()

            local res, err = udpsock:setpeername("127.0.0.1", 1234)
            if not res then
                ngx.log(ngx.ERR, err)
            end
        end

        ngx.timer.at(0, function()
            collectgarbage()
            ngx.log(ngx.WARN, "GC cycle done")
        end)
    }

    return 1;

--- stream_response chomp
1
--- no_error_log
[error]
--- error_log
cleanup lua udp socket upstream request
GC cycle done
