/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * RAIL Virtual Channel Plugin
 *
 * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
 *

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <freerdp/types.h>
#include <freerdp/constants.h>

#include <freerdp/freerdp.h>
#include <freerdp/channels/log.h>
#include <freerdp/utils/channel_pdu_tracker.h>

#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>

#include "rail_main.h"

#define TAG CHANNELS_TAG("rail.server")

/**
 * Sends a single rail PDU on the channel
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send(RailServerContext* context, wStream* s, size_t length)
{
	UINT status = CHANNEL_RC_OK;
	ULONG written = 0;

	const ULONG ulen = WINPR_ASSERTING_INT_CAST(ULONG, length);
	WINPR_ASSERT(context);

	if (!WTSVirtualChannelWrite(context->priv->rail_channel, Stream_BufferAs(s, char), ulen,
	                            &written))
	{
		WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
		status = ERROR_INTERNAL_ERROR;
	}

	return status;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_server_send_pdu(RailServerContext* context, wStream* s, UINT16 orderType)
{
	char buffer[128] = WINPR_C_ARRAY_INIT;

	WINPR_ASSERT(context);
	WINPR_ASSERT(s);

	const size_t orderLength = Stream_GetPosition(s);
	Stream_ResetPosition(s);
	if (!rail_write_pdu_header(s, orderType, WINPR_ASSERTING_INT_CAST(UINT16, orderLength)))
		goto fail;
	if (!Stream_SetPosition(s, orderLength))
		goto fail;
	WLog_DBG(TAG, "Sending %s PDU, length: %" PRIuz "",
	         rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
	return rail_send(context, s, orderLength);

fail:
	Stream_Free(s, TRUE);
	return ERROR_INVALID_DATA;
}

static void rail_write_local_move_size_order(wStream* s,
                                             const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(localMoveSize);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 12));

	Stream_Write_UINT32(s, localMoveSize->windowId);                /* WindowId (4 bytes) */
	Stream_Write_UINT16(s, localMoveSize->isMoveSizeStart ? 1 : 0); /* IsMoveSizeStart (2 bytes) */
	Stream_Write_UINT16(s, localMoveSize->moveSizeType);            /* MoveSizeType (2 bytes) */
	Stream_Write_INT16(s, localMoveSize->posX);                     /* PosX (2 bytes) */
	Stream_Write_INT16(s, localMoveSize->posY);                     /* PosY (2 bytes) */
}

static void rail_write_min_max_info_order(wStream* s, const RAIL_MINMAXINFO_ORDER* minMaxInfo)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(minMaxInfo);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 20));

	Stream_Write_UINT32(s, minMaxInfo->windowId);      /* WindowId (4 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxWidth);       /* MaxWidth (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxHeight);      /* MaxHeight (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxPosX);        /* MaxPosX (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxPosY);        /* MaxPosY (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->minTrackWidth);  /* MinTrackWidth (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->minTrackHeight); /* MinTrackHeight (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxTrackWidth);  /* MaxTrackWidth (2 bytes) */
	Stream_Write_INT16(s, minMaxInfo->maxTrackHeight); /* MaxTrackHeight (2 bytes) */
}

static void rail_write_taskbar_info_order(wStream* s, const RAIL_TASKBAR_INFO_ORDER* taskbarInfo)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(taskbarInfo);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 12));

	Stream_Write_UINT32(s, taskbarInfo->TaskbarMessage); /* TaskbarMessage (4 bytes) */
	Stream_Write_UINT32(s, taskbarInfo->WindowIdTab);    /* WindowIdTab (4 bytes) */
	Stream_Write_UINT32(s, taskbarInfo->Body);           /* Body (4 bytes) */
}

static void rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(langbarInfo);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 4));

	Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_write_exec_result_order(wStream* s, const RAIL_EXEC_RESULT_ORDER* execResult)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(execResult);

	if (execResult->exeOrFile.length > 520 || execResult->exeOrFile.length < 1)
		return ERROR_INVALID_DATA;

	Stream_Write_UINT16(s, execResult->flags);            /* Flags (2 bytes) */
	Stream_Write_UINT16(s, execResult->execResult);       /* ExecResult (2 bytes) */
	Stream_Write_UINT32(s, execResult->rawResult);        /* RawResult (4 bytes) */
	Stream_Write_UINT16(s, 0);                            /* Padding (2 bytes) */
	return rail_write_unicode_string(s, &execResult->exeOrFile);
}

static void rail_write_z_order_sync_order(wStream* s, const RAIL_ZORDER_SYNC* zOrderSync)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(zOrderSync);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 4));
	Stream_Write_UINT32(s, zOrderSync->windowIdMarker); /* WindowIdMarker (4 bytes) */
}

static void rail_write_cloak_order(wStream* s, const RAIL_CLOAK* cloak)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(cloak);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 5));

	Stream_Write_UINT32(s, cloak->windowId);     /* WindowId (4 bytes) */
	Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); /* Cloaked (1 byte) */
}

static void
rail_write_power_display_request_order(wStream* s,
                                       const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(powerDisplayRequest);
	WINPR_ASSERT(Stream_EnsureRemainingCapacity(s, 4));

	Stream_Write_UINT32(s, powerDisplayRequest->active ? 1 : 0); /* Active (4 bytes) */
}

WINPR_ATTR_NODISCARD
static UINT rail_write_get_app_id_resp_order(wStream* s,
                                             const RAIL_GET_APPID_RESP_ORDER* getAppidResp)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(getAppidResp);
	if (!Stream_EnsureRemainingCapacity(s, 4ull + ARRAYSIZE(getAppidResp->applicationId)))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(s, getAppidResp->windowId); /* WindowId (4 bytes) */
	if (!Stream_Write_UTF16_String(
	        s, getAppidResp->applicationId,
	        ARRAYSIZE(getAppidResp->applicationId))) /* ApplicationId (512 bytes) */
		return ERROR_INVALID_DATA;
	return ERROR_SUCCESS;
}

WINPR_ATTR_NODISCARD
static UINT rail_write_get_appid_resp_ex_order(wStream* s,
                                               const RAIL_GET_APPID_RESP_EX* getAppidRespEx)
{
	WINPR_ASSERT(s);
	WINPR_ASSERT(getAppidRespEx);

	if (!Stream_EnsureRemainingCapacity(s, (8ull + ARRAYSIZE(getAppidRespEx->applicationID) +
	                                        ARRAYSIZE(getAppidRespEx->processImageName))))
		return ERROR_OUTOFMEMORY;

	Stream_Write_UINT32(s, getAppidRespEx->windowID); /* WindowId (4 bytes) */
	if (!Stream_Write_UTF16_String(
	        s, getAppidRespEx->applicationID,
	        ARRAYSIZE(getAppidRespEx->applicationID))) /* ApplicationId (520 bytes) */
		return ERROR_INVALID_DATA;
	Stream_Write_UINT32(s, getAppidRespEx->processId); /* ProcessId (4 bytes) */
	if (!Stream_Write_UTF16_String(
	        s, getAppidRespEx->processImageName,
	        ARRAYSIZE(getAppidRespEx->processImageName))) /* ProcessImageName (520 bytes) */
		return ERROR_INVALID_DATA;
	return ERROR_SUCCESS;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_handshake(RailServerContext* context,
                                       const RAIL_HANDSHAKE_ORDER* handshake)
{
	if (!context || !handshake)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_handshake_order(s, handshake);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_handshake_ex(RailServerContext* context,
                                          const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
{
	if (!context || !handshakeEx || !context->priv)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_server_set_handshake_ex_flags(context, handshakeEx->railHandshakeFlags);
	rail_write_handshake_ex_order(s, handshakeEx);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE_EX);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_sysparam(RailServerContext* context,
                                      const RAIL_SYSPARAM_ORDER* sysparam)
{
	if (!context || !sysparam)
		return ERROR_INVALID_PARAMETER;

	RailServerPrivate* priv = context->priv;

	if (!priv)
		return ERROR_INVALID_PARAMETER;

	const BOOL extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags);
	wStream* s = rail_pdu_init(RAIL_SYSPARAM_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	const UINT error = rail_write_sysparam_order(s, sysparam, extendedSpiSupported);
	if (error != CHANNEL_RC_OK)
	{
		Stream_Free(s, TRUE);
		return error;
	}
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_SYSPARAM);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_local_move_size(RailServerContext* context,
                                             const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
{
	if (!context || !localMoveSize)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_LOCALMOVESIZE_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_local_move_size_order(s, localMoveSize);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_LOCALMOVESIZE);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_min_max_info(RailServerContext* context,
                                          const RAIL_MINMAXINFO_ORDER* minMaxInfo)
{
	if (!context || !minMaxInfo)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_MINMAXINFO_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_min_max_info_order(s, minMaxInfo);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_MINMAXINFO);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_taskbar_info(RailServerContext* context,
                                          const RAIL_TASKBAR_INFO_ORDER* taskbarInfo)
{
	if (!context || !taskbarInfo)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_TASKBAR_INFO_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_taskbar_info_order(s, taskbarInfo);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_TASKBARINFO);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_langbar_info(RailServerContext* context,
                                          const RAIL_LANGBAR_INFO_ORDER* langbarInfo)
{
	if (!context || !langbarInfo)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_langbar_info_order(s, langbarInfo);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_LANGBARINFO);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_exec_result(RailServerContext* context,
                                         const RAIL_EXEC_RESULT_ORDER* execResult)
{
	if (!context || !execResult)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_EXEC_RESULT_ORDER_LENGTH + execResult->exeOrFile.length);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	const UINT error = rail_write_exec_result_order(s, execResult);
	if (error != CHANNEL_RC_OK)
	{
		Stream_Free(s, TRUE);
		return error;
	}
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_EXEC_RESULT);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_z_order_sync(RailServerContext* context,
                                          const RAIL_ZORDER_SYNC* zOrderSync)
{
	if (!context || !zOrderSync)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_Z_ORDER_SYNC_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_z_order_sync_order(s, zOrderSync);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_ZORDER_SYNC);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_cloak(RailServerContext* context, const RAIL_CLOAK* cloak)
{
	if (!context || !cloak)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_CLOAK_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_cloak_order(s, cloak);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_CLOAK);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT
rail_send_server_power_display_request(RailServerContext* context,
                                       const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest)
{
	if (!context || !powerDisplayRequest)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	rail_write_power_display_request_order(s, powerDisplayRequest);
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_POWER_DISPLAY_REQUEST);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error coie
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_get_app_id_resp(RailServerContext* context,
                                             const RAIL_GET_APPID_RESP_ORDER* getAppidResp)
{
	if (!context || !getAppidResp)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_GET_APPID_RESP_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	const UINT error = rail_write_get_app_id_resp_order(s, getAppidResp);
	if (error != CHANNEL_RC_OK)
	{
		Stream_Free(s, TRUE);
		return error;
	}
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_send_server_get_appid_resp_ex(RailServerContext* context,
                                               const RAIL_GET_APPID_RESP_EX* getAppidRespEx)
{
	if (!context || !getAppidRespEx)
		return ERROR_INVALID_PARAMETER;

	wStream* s = rail_pdu_init(RAIL_GET_APPID_RESP_EX_ORDER_LENGTH);

	if (!s)
	{
		WLog_ERR(TAG, "rail_pdu_init failed!");
		return CHANNEL_RC_NO_MEMORY;
	}

	const UINT error = rail_write_get_appid_resp_ex_order(s, getAppidRespEx);
	if (error != CHANNEL_RC_OK)
	{
		Stream_Free(s, TRUE);
		return error;
	}
	return rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP_EX);
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_client_status_order(wStream* s, RAIL_CLIENT_STATUS_ORDER* clientStatus)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLIENT_STATUS_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, clientStatus->flags); /* Flags (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_exec_order(wStream* s, RAIL_EXEC_ORDER* exec, char* args[])
{
	RAIL_EXEC_ORDER order = WINPR_C_ARRAY_INIT;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_EXEC_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	exec->flags = Stream_Get_UINT16(s);          /* Flags (2 bytes) */
	const UINT16 exeLen = Stream_Get_UINT16(s);  /* ExeOrFileLength (2 bytes) */
	const UINT16 workLen = Stream_Get_UINT16(s); /* WorkingDirLength (2 bytes) */
	const UINT16 argLen = Stream_Get_UINT16(s);  /* ArgumentsLength (2 bytes) */

	if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)exeLen + workLen + argLen))
		return ERROR_INVALID_DATA;

	if (exeLen > 0)
	{
		const size_t len = exeLen / sizeof(WCHAR);
		exec->RemoteApplicationProgram = args[0] =
		    Stream_Read_UTF16_String_As_UTF8(s, len, nullptr);
		if (!exec->RemoteApplicationProgram)
			goto fail;
	}
	if (workLen > 0)
	{
		const size_t len = workLen / sizeof(WCHAR);
		exec->RemoteApplicationWorkingDir = args[1] =
		    Stream_Read_UTF16_String_As_UTF8(s, len, nullptr);
		if (!exec->RemoteApplicationWorkingDir)
			goto fail;
	}
	if (argLen > 0)
	{
		const size_t len = argLen / sizeof(WCHAR);
		exec->RemoteApplicationArguments = args[2] =
		    Stream_Read_UTF16_String_As_UTF8(s, len, nullptr);
		if (!exec->RemoteApplicationArguments)
			goto fail;
	}

	return CHANNEL_RC_OK;
fail:
	free(args[0]);
	free(args[1]);
	free(args[2]);
	*exec = order;
	return ERROR_INTERNAL_ERROR;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_activate_order(wStream* s, RAIL_ACTIVATE_ORDER* activate)
{
	BYTE enabled = 0;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_ACTIVATE_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, activate->windowId); /* WindowId (4 bytes) */
	Stream_Read_UINT8(s, enabled);             /* Enabled (1 byte) */
	activate->enabled = (enabled != 0);
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_sysmenu_order(wStream* s, RAIL_SYSMENU_ORDER* sysmenu)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSMENU_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, sysmenu->windowId); /* WindowId (4 bytes) */
	Stream_Read_INT16(s, sysmenu->left);      /* Left (2 bytes) */
	Stream_Read_INT16(s, sysmenu->top);       /* Top (2 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_syscommand_order(wStream* s, RAIL_SYSCOMMAND_ORDER* syscommand)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSCOMMAND_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, syscommand->windowId); /* WindowId (4 bytes) */
	Stream_Read_UINT16(s, syscommand->command);  /* Command (2 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_notify_event_order(wStream* s, RAIL_NOTIFY_EVENT_ORDER* notifyEvent)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_NOTIFY_EVENT_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, notifyEvent->windowId);     /* WindowId (4 bytes) */
	Stream_Read_UINT32(s, notifyEvent->notifyIconId); /* NotifyIconId (4 bytes) */
	Stream_Read_UINT32(s, notifyEvent->message);      /* Message (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_get_appid_req_order(wStream* s, RAIL_GET_APPID_REQ_ORDER* getAppidReq)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_GET_APPID_REQ_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, getAppidReq->windowId); /* WindowId (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_window_move_order(wStream* s, RAIL_WINDOW_MOVE_ORDER* windowMove)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_WINDOW_MOVE_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, windowMove->windowId); /* WindowId (4 bytes) */
	Stream_Read_INT16(s, windowMove->left);      /* Left (2 bytes) */
	Stream_Read_INT16(s, windowMove->top);       /* Top (2 bytes) */
	Stream_Read_INT16(s, windowMove->right);     /* Right (2 bytes) */
	Stream_Read_INT16(s, windowMove->bottom);    /* Bottom (2 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_snap_arange_order(wStream* s, RAIL_SNAP_ARRANGE* snapArrange)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SNAP_ARRANGE_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, snapArrange->windowId); /* WindowId (4 bytes) */
	Stream_Read_INT16(s, snapArrange->left);      /* Left (2 bytes) */
	Stream_Read_INT16(s, snapArrange->top);       /* Top (2 bytes) */
	Stream_Read_INT16(s, snapArrange->right);     /* Right (2 bytes) */
	Stream_Read_INT16(s, snapArrange->bottom);    /* Bottom (2 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGBAR_INFO_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_language_ime_info_order(wStream* s,
                                              RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGUAGEIME_INFO_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, languageImeInfo->ProfileType); /* ProfileType (4 bytes) */
	Stream_Read_UINT16(s, languageImeInfo->LanguageID);  /* LanguageID (2 bytes) */
	Stream_Read(
	    s, &languageImeInfo->LanguageProfileCLSID,
	    sizeof(languageImeInfo->LanguageProfileCLSID)); /* LanguageProfileCLSID (16 bytes) */
	Stream_Read(s, &languageImeInfo->ProfileGUID,
	            sizeof(languageImeInfo->ProfileGUID));      /* ProfileGUID (16 bytes) */
	Stream_Read_UINT32(s, languageImeInfo->KeyboardLayout); /* KeyboardLayout (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_compartment_info_order(wStream* s,
                                             RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo)
{
	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, compartmentInfo->ImeState);        /* ImeState (4 bytes) */
	Stream_Read_UINT32(s, compartmentInfo->ImeConvMode);     /* ImeConvMode (4 bytes) */
	Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */
	Stream_Read_UINT32(s, compartmentInfo->KanaMode);        /* KANAMode (4 bytes) */
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak)
{
	BYTE cloaked = 0;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLOAK_ORDER_LENGTH))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */
	Stream_Read_UINT8(s, cloaked);          /* Cloaked (1 byte) */
	cloak->cloak = (cloaked != 0);
	return CHANNEL_RC_OK;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_handshake_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_HANDSHAKE_ORDER handshake = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_handshake_order(s, &handshake)))
	{
		WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientHandshake, error, context, &handshake);

	if (error)
		WLog_ERR(TAG, "context.ClientHandshake failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_client_status_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_CLIENT_STATUS_ORDER clientStatus = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_client_status_order(s, &clientStatus)))
	{
		WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientClientStatus, error, context, &clientStatus);

	if (error)
		WLog_ERR(TAG, "context.ClientClientStatus failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_exec_order(RailServerContext* context, wStream* s)
{
	char* args[3] = WINPR_C_ARRAY_INIT;
	RAIL_EXEC_ORDER exec = WINPR_C_ARRAY_INIT;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	UINT error = rail_read_exec_order(s, &exec, args);
	if (error)
	{
		WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientExec, error, context, &exec);

	if (error)
		WLog_ERR(TAG, "context.Exec failed with error %" PRIu32 "", error);

	free(args[0]);
	free(args[1]);
	free(args[2]);
	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_sysparam_order(RailServerContext* context, wStream* s)
{
	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	const BOOL extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags);

	RAIL_SYSPARAM_ORDER sysparam = WINPR_C_ARRAY_INIT;
	UINT error = rail_read_sysparam_order(s, &sysparam, extendedSpiSupported);
	if (error != CHANNEL_RC_OK)
	{
		rail_unicode_string_free(&sysparam.highContrast.colorScheme);
		WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientSysparam, error, context, &sysparam);
	rail_unicode_string_free(&sysparam.highContrast.colorScheme);

	if (error)
		WLog_ERR(TAG, "context.ClientSysparam failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_activate_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_ACTIVATE_ORDER activate = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_activate_order(s, &activate)))
	{
		WLog_ERR(TAG, "rail_read_activate_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientActivate, error, context, &activate);

	if (error)
		WLog_ERR(TAG, "context.ClientActivate failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_sysmenu_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_SYSMENU_ORDER sysmenu = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_sysmenu_order(s, &sysmenu)))
	{
		WLog_ERR(TAG, "rail_read_sysmenu_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientSysmenu, error, context, &sysmenu);

	if (error)
		WLog_ERR(TAG, "context.ClientSysmenu failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_syscommand_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_SYSCOMMAND_ORDER syscommand = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_syscommand_order(s, &syscommand)))
	{
		WLog_ERR(TAG, "rail_read_syscommand_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientSyscommand, error, context, &syscommand);

	if (error)
		WLog_ERR(TAG, "context.ClientSyscommand failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_notify_event_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_NOTIFY_EVENT_ORDER notifyEvent = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_notify_event_order(s, &notifyEvent)))
	{
		WLog_ERR(TAG, "rail_read_notify_event_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientNotifyEvent, error, context, &notifyEvent);

	if (error)
		WLog_ERR(TAG, "context.ClientNotifyEvent failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_window_move_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_WINDOW_MOVE_ORDER windowMove = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_window_move_order(s, &windowMove)))
	{
		WLog_ERR(TAG, "rail_read_window_move_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientWindowMove, error, context, &windowMove);

	if (error)
		WLog_ERR(TAG, "context.ClientWindowMove failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_snap_arrange_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_SNAP_ARRANGE snapArrange = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_snap_arange_order(s, &snapArrange)))
	{
		WLog_ERR(TAG, "rail_read_snap_arange_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientSnapArrange, error, context, &snapArrange);

	if (error)
		WLog_ERR(TAG, "context.ClientSnapArrange failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_get_appid_req_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_GET_APPID_REQ_ORDER getAppidReq = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_get_appid_req_order(s, &getAppidReq)))
	{
		WLog_ERR(TAG, "rail_read_get_appid_req_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientGetAppidReq, error, context, &getAppidReq);

	if (error)
		WLog_ERR(TAG, "context.ClientGetAppidReq failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_langbar_info_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_LANGBAR_INFO_ORDER langbarInfo = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_langbar_info_order(s, &langbarInfo)))
	{
		WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientLangbarInfo, error, context, &langbarInfo);

	if (error)
		WLog_ERR(TAG, "context.ClientLangbarInfo failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_language_ime_info_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_LANGUAGEIME_INFO_ORDER languageImeInfo = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_language_ime_info_order(s, &languageImeInfo)))
	{
		WLog_ERR(TAG, "rail_read_language_ime_info_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientLanguageImeInfo, error, context, &languageImeInfo);

	if (error)
		WLog_ERR(TAG, "context.ClientLanguageImeInfo failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_compartment_info(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_COMPARTMENT_INFO_ORDER compartmentInfo = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_compartment_info_order(s, &compartmentInfo)))
	{
		WLog_ERR(TAG, "rail_read_compartment_info_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientCompartmentInfo, error, context, &compartmentInfo);

	if (error)
		WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error);

	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_recv_client_cloak_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	RAIL_CLOAK cloak = WINPR_C_ARRAY_INIT;
	if ((error = rail_read_cloak_order(s, &cloak)))
	{
		WLog_ERR(TAG, "rail_read_cloak_order failed with error %" PRIu32 "!", error);
		return error;
	}

	IFCALLRET(context->ClientCloak, error, context, &cloak);

	if (error)
		WLog_ERR(TAG, "context.Cloak failed with error %" PRIu32 "", error);

	return error;
}

WINPR_ATTR_NODISCARD
static UINT rail_recv_client_text_scale_order(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;
	UINT32 TextScaleFactor = 0;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, TextScaleFactor);
	IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor);

	if (error)
		WLog_ERR(TAG, "context.TextScale failed with error %" PRIu32 "", error);

	return error;
}

WINPR_ATTR_NODISCARD
static UINT rail_recv_client_caret_blink(RailServerContext* context, wStream* s)
{
	UINT error = CHANNEL_RC_OK;
	UINT32 CaretBlinkRate = 0;

	if (!context || !s)
		return ERROR_INVALID_PARAMETER;

	if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT32(s, CaretBlinkRate);
	IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate);

	if (error)
		WLog_ERR(TAG, "context.CaretBlinkRate failed with error %" PRIu32 "", error);

	return error;
}

WINPR_ATTR_NODISCARD
static DWORD WINAPI rail_server_thread(LPVOID arg)
{
	RailServerContext* context = (RailServerContext*)arg;
	WINPR_ASSERT(context);

	RailServerPrivate* priv = context->priv;
	WINPR_ASSERT(priv);

	DWORD status = 0;
	DWORD nCount = 0;
	HANDLE events[8];
	UINT error = CHANNEL_RC_OK;
	events[nCount++] = priv->channelEvent;
	events[nCount++] = priv->stopEvent;

	while (TRUE)
	{
		status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);

		if (status == WAIT_FAILED)
		{
			error = GetLastError();
			WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
			break;
		}

		status = WaitForSingleObject(context->priv->stopEvent, 0);

		if (status == WAIT_FAILED)
		{
			error = GetLastError();
			WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
			break;
		}

		if (status == WAIT_OBJECT_0)
			break;

		status = WaitForSingleObject(context->priv->channelEvent, 0);

		if (status == WAIT_FAILED)
		{
			error = GetLastError();
			WLog_ERR(
			    TAG,
			    "WaitForSingleObject(context->priv->channelEvent, 0) failed with error %" PRIu32
			    "!",
			    error);
			break;
		}

		if (status == WAIT_OBJECT_0)
		{
			if ((error = rail_server_handle_messages(context)))
			{
				WLog_ERR(TAG, "rail_server_handle_messages failed with error %" PRIu32 "", error);
				break;
			}
		}
	}

	if (error && context->rdpcontext)
		setChannelError(context->rdpcontext, error, "rail_server_thread reported an error");

	ExitThread(error);
	return error;
}

/**
 * Function description
 *
 * @return 0 on success, otherwise a Win32 error code
 */
WINPR_ATTR_NODISCARD
static UINT rail_server_start(RailServerContext* context)
{
	void* buffer = nullptr;
	DWORD bytesReturned = 0;
	UINT error = ERROR_INTERNAL_ERROR;

	WINPR_ASSERT(context);

	RailServerPrivate* priv = context->priv;
	WINPR_ASSERT(priv);

	priv->rail_channel =
	    WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RAIL_SVC_CHANNEL_NAME);

	if (!priv->rail_channel)
	{
		WLog_ERR(TAG, "WTSVirtualChannelOpen failed!");
		return error;
	}

	if (!WTSVirtualChannelQuery(priv->rail_channel, WTSVirtualEventHandle, &buffer,
	                            &bytesReturned) ||
	    (bytesReturned != sizeof(HANDLE)))
	{
		WLog_ERR(TAG,
		         "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned "
		         "size(%" PRIu32 ")",
		         bytesReturned);

		if (buffer)
			WTSFreeMemory(buffer);

		goto out_close;
	}

	priv->channelEvent = *(HANDLE*)buffer;
	WTSFreeMemory(buffer);
	context->priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);

	if (!context->priv->stopEvent)
	{
		WLog_ERR(TAG, "CreateEvent failed!");
		goto out_close;
	}

	context->priv->thread =
	    CreateThread(nullptr, 0, rail_server_thread, (void*)context, 0, nullptr);

	if (!context->priv->thread)
	{
		WLog_ERR(TAG, "CreateThread failed!");
		goto out_stop_event;
	}

	return CHANNEL_RC_OK;
out_stop_event:
	(void)CloseHandle(context->priv->stopEvent);
	context->priv->stopEvent = nullptr;
out_close:
	(void)WTSVirtualChannelClose(context->priv->rail_channel);
	context->priv->rail_channel = nullptr;
	return error;
}

WINPR_ATTR_NODISCARD
static BOOL rail_server_stop(RailServerContext* context)
{
	WINPR_ASSERT(context);
	RailServerPrivate* priv = context->priv;

	if (priv->thread)
	{
		(void)SetEvent(priv->stopEvent);

		if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
		{
			WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", GetLastError());
			return FALSE;
		}

		(void)CloseHandle(priv->thread);
		(void)CloseHandle(priv->stopEvent);
		priv->thread = nullptr;
		priv->stopEvent = nullptr;
	}

	if (priv->rail_channel)
	{
		(void)WTSVirtualChannelClose(priv->rail_channel);
		priv->rail_channel = nullptr;
	}

	priv->channelEvent = nullptr;
	return TRUE;
}

static void rail_server_context_priv_free(RailServerPrivate* priv)
{
	if (!priv)
		return;

	ChannelPduTracker_free(priv->channelPduTracker);
	free(priv);
}

WINPR_ATTR_MALLOC(rail_server_context_priv_free, 1)
static RailServerPrivate* rail_server_context_priv_new(HANDLE vc)
{
	RailServerPrivate* priv = calloc(1, sizeof(RailServerPrivate));
	if (!priv)
		return nullptr;

	priv->channelPduTracker = ChannelPduTracker_new(vc);
	if (!priv->channelPduTracker)
		goto fail;
	return priv;
fail:
	rail_server_context_priv_free(priv);
	return nullptr;
}

RailServerContext* rail_server_context_new(HANDLE vcm)
{
	RailServerContext* context = (RailServerContext*)calloc(1, sizeof(RailServerContext));

	if (!context)
	{
		WLog_ERR(TAG, "calloc failed!");
		return nullptr;
	}

	context->vcm = vcm;
	context->Start = rail_server_start;
	context->Stop = rail_server_stop;
	context->ServerHandshake = rail_send_server_handshake;
	context->ServerHandshakeEx = rail_send_server_handshake_ex;
	context->ServerSysparam = rail_send_server_sysparam;
	context->ServerLocalMoveSize = rail_send_server_local_move_size;
	context->ServerMinMaxInfo = rail_send_server_min_max_info;
	context->ServerTaskbarInfo = rail_send_server_taskbar_info;
	context->ServerLangbarInfo = rail_send_server_langbar_info;
	context->ServerExecResult = rail_send_server_exec_result;
	context->ServerGetAppidResp = rail_send_server_get_app_id_resp;
	context->ServerZOrderSync = rail_send_server_z_order_sync;
	context->ServerCloak = rail_send_server_cloak;
	context->ServerPowerDisplayRequest = rail_send_server_power_display_request;
	context->ServerGetAppidRespEx = rail_send_server_get_appid_resp_ex;
	context->priv = rail_server_context_priv_new(vcm);

	if (!context->priv)
	{
		WLog_ERR(TAG, "calloc failed!");
		goto fail;
	}

	return context;
fail:
	rail_server_context_free(context);
	return nullptr;
}

void rail_server_context_free(RailServerContext* context)
{
	if (!context)
		return;
	rail_server_context_priv_free(context->priv);
	free(context);
}

void rail_server_set_handshake_ex_flags(RailServerContext* context, DWORD flags)
{
	WINPR_ASSERT(context);
	WINPR_ASSERT(context->priv);

	RailServerPrivate* priv = context->priv;
	priv->channelFlags = flags;
}

static UINT rail_server_read_pdu_header(wStream* s, UINT16* pOrderType, UINT16* pOrderLength)
{
	WINPR_ASSERT(pOrderType);
	WINPR_ASSERT(pOrderLength);

	/* Parse header */
	const UINT status = rail_read_pdu_header(s, pOrderType, pOrderLength);
	if (status != CHANNEL_RC_OK)
	{
		WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", status);
		return status;
	}

	if (!Stream_EnsureRemainingCapacity(s, *pOrderLength - RAIL_PDU_HEADER_LENGTH))
	{
		WLog_ERR(TAG,
		         "Stream_EnsureRemainingCapacity failed, orderLength - RAIL_PDU_HEADER_LENGTH");
		return CHANNEL_RC_NO_MEMORY;
	}
	return CHANNEL_RC_OK;
}

static UINT rail_server_handle_message_completed(RailServerContext* context, wStream* s,
                                                 UINT16 orderType, UINT16 orderLength)
{
	{
		char buffer[128] = WINPR_C_ARRAY_INIT;
		WLog_DBG(TAG, "Received %s PDU, length:%" PRIu16 "",
		         rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength);
	}

	switch (orderType)
	{
		case TS_RAIL_ORDER_HANDSHAKE:
			return rail_recv_client_handshake_order(context, s);

		case TS_RAIL_ORDER_CLIENTSTATUS:
			return rail_recv_client_client_status_order(context, s);

		case TS_RAIL_ORDER_EXEC:
			return rail_recv_client_exec_order(context, s);

		case TS_RAIL_ORDER_SYSPARAM:
			return rail_recv_client_sysparam_order(context, s);

		case TS_RAIL_ORDER_ACTIVATE:
			return rail_recv_client_activate_order(context, s);

		case TS_RAIL_ORDER_SYSMENU:
			return rail_recv_client_sysmenu_order(context, s);

		case TS_RAIL_ORDER_SYSCOMMAND:
			return rail_recv_client_syscommand_order(context, s);

		case TS_RAIL_ORDER_NOTIFY_EVENT:
			return rail_recv_client_notify_event_order(context, s);

		case TS_RAIL_ORDER_WINDOWMOVE:
			return rail_recv_client_window_move_order(context, s);

		case TS_RAIL_ORDER_SNAP_ARRANGE:
			return rail_recv_client_snap_arrange_order(context, s);

		case TS_RAIL_ORDER_GET_APPID_REQ:
			return rail_recv_client_get_appid_req_order(context, s);

		case TS_RAIL_ORDER_LANGBARINFO:
			return rail_recv_client_langbar_info_order(context, s);

		case TS_RAIL_ORDER_LANGUAGEIMEINFO:
			return rail_recv_client_language_ime_info_order(context, s);

		case TS_RAIL_ORDER_COMPARTMENTINFO:
			return rail_recv_client_compartment_info(context, s);

		case TS_RAIL_ORDER_CLOAK:
			return rail_recv_client_cloak_order(context, s);

		case TS_RAIL_ORDER_TEXTSCALEINFO:
			return rail_recv_client_text_scale_order(context, s);

		case TS_RAIL_ORDER_CARETBLINKINFO:
			return rail_recv_client_caret_blink(context, s);

		default:
			WLog_ERR(TAG, "Unknown RAIL PDU order received.");
			return ERROR_INVALID_DATA;
	}
}

UINT rail_server_handle_messages(RailServerContext* context)
{
	WINPR_ASSERT(context);

	RailServerPrivate* priv = context->priv;
	WINPR_ASSERT(priv);

	BOOL ok = FALSE;
	wStream* s = ChannelPduTracker_poll(priv->channelPduTracker, &ok);

	if (!ok)
		return ERROR_INVALID_DATA;

	if (!s)
		return CHANNEL_RC_OK;

	UINT16 orderType = 0;
	UINT16 orderLength = 0;
	UINT rc = rail_server_read_pdu_header(s, &orderType, &orderLength);
	if (rc == CHANNEL_RC_OK)
		rc = rail_server_handle_message_completed(context, s, orderType, orderLength);
	Stream_Release(s);
	return rc;
}
