project(lws-api-test-lwsws-uv C)
cmake_minimum_required(VERSION 3.10)
find_package(libwebsockets CONFIG REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR})
include(CheckCSourceCompiles)
include(LwsCheckRequirements)

set(SAMP lws-api-test-lwsws-uv)
set(SRCS main.c)

#
# This api-test drives a real lwsws, running on libuv with dlopened protocol
# plugins out of this build tree, exactly like a production deployment.  So it
# needs the whole lwsws stack present, not just the library.
#
set(requirements 1)
require_lws_config(LWS_WITH_LWSWS 1 requirements)
require_lws_config(LWS_WITH_LIBUV 1 requirements)
require_lws_config(LWS_WITH_PLUGINS 1 requirements)
require_lws_config(LWS_WITH_PLUGINS_BUILTIN 0 requirements)
require_lws_config(LWS_WITH_SHARED 1 requirements)
require_lws_config(LWS_WITH_CLIENT 1 requirements)
require_lws_config(LWS_WITH_SERVER 1 requirements)
require_lws_config(LWS_WITH_TLS 1 requirements)
require_lws_config(LWS_WITH_JOSE 1 requirements)
require_lws_config(LWS_ROLE_H1 1 requirements)

# the h3 cases need the QUIC role both ends
set(has_h3 1)
require_lws_config(LWS_WITH_HTTP3 1 has_h3)
require_lws_config(LWS_ROLE_QUIC 1 has_h3)

# the proxy cases need the http proxy support in the library
set(has_proxy 1)
require_lws_config(LWS_WITH_HTTP_PROXY 1 has_proxy)

if (requirements)
	add_executable(${SAMP} ${SRCS})

	if (websockets_shared)
		target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS})
		add_dependencies(${SAMP} websockets_shared)
	else()
		target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS})
	endif()

	if (NOT WIN32 AND LWS_WITH_MINIMAL_EXAMPLES)

		#
		# Two adjacent ports: PORT_LWSWS is the tls listener (h1 + h2
		# over TCP, and h3 over UDP on the same number), PORT_LWSWS + 1
		# is the cleartext listener the pipelining case uses and the
		# tls vhost's /proxy mount proxies back to.
		#
		lws_get_free_ports(PORT_LWSWS 2)
		math(EXPR PORT_LWSWS_PLAIN "${PORT_LWSWS} + 1")

		set(LWSWS_UV_DIR	   ${CMAKE_CURRENT_BINARY_DIR})
		set(LWSWS_UV_CONF_DIR	   ${LWSWS_UV_DIR}/lwsws-conf)
		set(LWSWS_UV_FILES_DIR	   ${LWSWS_UV_DIR}/files)
		set(LWSWS_UV_DEADDROP_UPLOAD_DIR ${LWSWS_UV_DIR}/deaddrop-upload)

		set(LWSWS_UV_VHOST_NAME	   ${LWS_CTEST_SERVER_RESOLVE})
		set(LWSWS_UV_PORT_TLS	   ${PORT_LWSWS})
		set(LWSWS_UV_PORT_PLAIN	   ${PORT_LWSWS_PLAIN})
		set(LWSWS_UV_PLUGIN_DIR	   ${CMAKE_BINARY_DIR}/lib)
		# ...CMAKE_BINARY_DIR, not PROJECT_BINARY_DIR: this file calls
		# project() itself, so PROJECT_BINARY_DIR is this subdir
		set(LWSWS_UV_CERT	   ${CMAKE_BINARY_DIR}/libwebsockets-test-server.pem)
		set(LWSWS_UV_KEY	   ${CMAKE_BINARY_DIR}/libwebsockets-test-server.key.pem)
		set(LWSWS_UV_GATED_DIR	   ${CMAKE_CURRENT_SOURCE_DIR}/gated-origin)
		set(LWSWS_UV_CAPTCHA_ASSETS_DIR ${CMAKE_SOURCE_DIR}/plugins/protocol_lws_captcha_ratelimit/captcha-assets)
		set(LWSWS_UV_DEADDROP_ASSETS_DIR ${CMAKE_SOURCE_DIR}/plugins/protocol_deaddrop/assets)

		#
		# Throwaway HS256 key (32 bytes 0x00..0x1f): it only signs the
		# captcha visit / pass cookies inside this test's own lwsws.
		# deaddrop is given the same key, as a deployment sharing one
		# key might, and main.c holds a copy to mint session tokens.
		# The quotes are backslash-escaped because the whole JWK is one
		# JSON string value inside the lwsws config.
		#
		set(LWSWS_UV_JWK "{\\\"kty\\\":\\\"oct\\\",\\\"k\\\":\\\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8\\\"}")

		#
		# Short enough that the whole captcha handshake fits in a ctest,
		# but nonzero, so the delays themselves are still exercised.
		# pre-delay-ms 1000 makes the interceptor's minimum age for the
		# visit cookie 0s (interceptor.c: pre_delay_sec - 1), so the
		# POST does not have to sit out a whole second first.
		#
		set(LWSWS_UV_PRE_DELAY_MS  1000)
		set(LWSWS_UV_POST_DELAY_MS 500)

		# short, so the h1 idle-close path is reached inside the test
		set(LWSWS_UV_KEEPALIVE	   5)

		configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.in
			       ${LWSWS_UV_CONF_DIR}/conf @ONLY)
		configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/apitest.json.in
			       ${LWSWS_UV_CONF_DIR}/conf.d/apitest.json @ONLY)

		file(MAKE_DIRECTORY ${LWSWS_UV_FILES_DIR})
		file(MAKE_DIRECTORY ${LWSWS_UV_DEADDROP_UPLOAD_DIR})

		#
		# The small file is written at configure time; the several-MB
		# one is generated by a build step, so no blob lives in git.
		#
		file(WRITE ${LWSWS_UV_FILES_DIR}/small.txt
		     "LWSWS-APITEST-SMALL-FILE-CONTENT\n")

		add_custom_command(OUTPUT ${LWSWS_UV_FILES_DIR}/big.bin
			COMMAND ${CMAKE_COMMAND}
				-DOUT=${LWSWS_UV_FILES_DIR}/big.bin
				-DSIZE_MB=4
				-P ${CMAKE_CURRENT_SOURCE_DIR}/gen-big-file.cmake
			DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen-big-file.cmake
			COMMENT "Generating api-test-lwsws-uv 4MB test file")

		add_custom_target(lws-api-test-lwsws-uv-bigfile ALL
				  DEPENDS ${LWSWS_UV_FILES_DIR}/big.bin)
		add_dependencies(${SAMP} lws-api-test-lwsws-uv-bigfile)

		#
		# The fixture: the real lwsws, on its own libuv loop, with the
		# plugins dlopened from this build tree's lib/.
		#
		#  -n  don't also scan the compiled-in install-time plugin dir
		#  -c  our generated config dir
		#
		# ctest-background.sh appends -d1039, which is what makes lwsws
		# install its crash handler, so a SIGSEGV in the worker prints a
		# backtrace into the fixture log.
		#
		add_test(NAME st_lwsws_uv COMMAND
			${CMAKE_SOURCE_DIR}/scripts/ctest-background.sh
					lwsws_uv
					$<TARGET_FILE:lwsws>
					-n -c ${LWSWS_UV_CONF_DIR})
		add_test(NAME ki_lwsws_uv COMMAND
			${CMAKE_SOURCE_DIR}/scripts/ctest-background-kill.sh
					lwsws_uv $<TARGET_FILE_NAME:lwsws>)

		set_tests_properties(st_lwsws_uv PROPERTIES
				     WORKING_DIRECTORY ${LWSWS_UV_DIR}
				     ENVIRONMENT "SAI_LIST_PORT=${PORT_LWSWS}"
				     FIXTURES_SETUP lwsws_uv
				     TIMEOUT 120)
		set_tests_properties(ki_lwsws_uv PROPERTIES
				     FIXTURES_CLEANUP lwsws_uv)

		set(ARGS_COMMON --server ${LWS_CTEST_SERVER_RESOLVE}
				-p ${PORT_LWSWS} --plain-port ${PORT_LWSWS_PLAIN}
				--plugin-dir ${LWSWS_UV_PLUGIN_DIR})

		set(mytests "")

		# (a) the captcha interceptor challenge / POST / pass flow.
		# The interceptor decision is made per request on whatever http
		# role the peer arrived as, so the same sequence runs once as
		# h1 and once as h2.
		add_test(NAME lwsws-uv-captcha
			 COMMAND ${SAMP} -t captcha ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-captcha)

		#
		# The h2 case is also the regression test for C-474: the lws h2
		# client re-sent the request body on the writeable callback a
		# WINDOW_UPDATE re-armed after END_STREAM, and the server's
		# GOAWAY killed the connection before any response.
		#
		if (LWS_WITH_HTTP2)
			add_test(NAME lwsws-uv-captcha-h2
				 COMMAND ${SAMP} -t captcha-h2 ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-captcha-h2)
		endif()

		# the same interceptor flow driven by a client on libuv
		add_test(NAME lwsws-uv-captcha-uv
			 COMMAND ${SAMP} -t captcha --evlib uv ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-captcha-uv)

		# G-174: HEAD of the interceptor's generated config js is
		# headers only, so a GET pipelined behind it still parses
		add_test(NAME lwsws-uv-captcha-head
			 COMMAND ${SAMP} -t captcha-head ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-captcha-head)

		# G-188: deaddrop shares the interceptor's jwk here, and only
		# an "sso" session token may name a deaddrop user
		add_test(NAME lwsws-uv-deaddrop-jwt
			 COMMAND ${SAMP} -t deaddrop ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-deaddrop-jwt)

		# (b) h1 pipelining during a transfer, on the cleartext listener
		add_test(NAME lwsws-uv-pipeline
			 COMMAND ${SAMP} -t pipeline ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-pipeline)

		add_test(NAME lwsws-uv-pipeline-uv
			 COMMAND ${SAMP} -t pipeline --evlib uv ${ARGS_COMMON})
		list(APPEND mytests lwsws-uv-pipeline-uv)

		# the proxy mount, which forwards back into the same lwsws:
		# an http:// mount origin has no dispatch at all without the
		# proxy support, so these cases need it in the build
		if (has_proxy)
			add_test(NAME lwsws-uv-proxy
				 COMMAND ${SAMP} -t proxy ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-proxy)

			add_test(NAME lwsws-uv-proxy-uv
				 COMMAND ${SAMP} -t proxy --evlib uv ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-proxy-uv)
		endif()

		if (has_h3)
			# (c) C-473: h3 wins the race, the TCP racer is parked
			# and torn down, and the client must survive past the
			# server's idle close of it.  Under libuv, which is
			# where the poll watcher outlived its wsi...
			add_test(NAME lwsws-uv-h3race-uv
				 COMMAND ${SAMP} -t h3race --evlib uv ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-h3race-uv)

			# ...and on the poll loop, as the control
			add_test(NAME lwsws-uv-h3race-poll
				 COMMAND ${SAMP} -t h3race --evlib poll ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-h3race-poll)

			# (d) C-471: a QUIC client that goes away during /
			# right after its handshake, then the server must still
			# answer normal h1
			add_test(NAME lwsws-uv-quic-abort-uv
				 COMMAND ${SAMP} -t quicabort --evlib uv ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-quic-abort-uv)

			add_test(NAME lwsws-uv-quic-abort-poll
				 COMMAND ${SAMP} -t quicabort --evlib poll ${ARGS_COMMON})
			list(APPEND mytests lwsws-uv-quic-abort-poll)
		endif()

		foreach(N IN LISTS mytests)
			set_tests_properties(${N} PROPERTIES
					     WORKING_DIRECTORY ${LWSWS_UV_DIR}
					     FIXTURES_REQUIRED "lwsws_uv"
					     TIMEOUT 60)
		endforeach()

	endif()
endif()
