#include "ds-rt-config.h"

#ifdef ENABLE_PERFTRACING
#if !defined(DS_INCLUDE_SOURCE_FILES) || defined(DS_FORCE_INCLUDE_SOURCE_FILES)

#define DS_IMPL_PROCESS_PROTOCOL_GETTER_SETTER
#include "ds-protocol.h"
#include "ds-process-protocol.h"
#include "ds-server.h"
#include "ep.h"
#include "ds-rt.h"
#include "ep-event-source.h"

/*
 * Forward declares of all static functions.
 */

static
uint16_t
process_info_payload_get_size (DiagnosticsProcessInfoPayload *payload);

static
bool
process_info_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size);

static
uint16_t
env_info_payload_get_size (DiagnosticsEnvironmentInfoPayload *payload);

static
uint32_t
env_info_env_block_get_size (DiagnosticsEnvironmentInfoPayload *payload);

static
bool
env_info_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size);

static
bool
env_info_stream_env_block (
	DiagnosticsEnvironmentInfoPayload *payload,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_get_process_info (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_get_process_info_2 (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_get_process_info_3 (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_get_process_env (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_resume_runtime_startup (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_set_environment_variable (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_enable_perfmap (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_disable_perfmap (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_apply_startup_hook (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

static
bool
process_protocol_helper_unknown_command (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream);

/*
 * DiagnosticsProcessInfoPayload.
 */

static
uint16_t
process_info_payload_get_size (DiagnosticsProcessInfoPayload *payload)
{
	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	// uint64_t ProcessId;  -> 8 bytes
	// GUID RuntimeCookie;  -> 16 bytes
	// LPCWSTR CommandLine; -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR OS;          -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR Arch;        -> 4 bytes + strlen * sizeof(WCHAR)

	EP_ASSERT (payload != NULL);

	size_t size = 0;
	size += sizeof(payload->process_id);
	size += sizeof(payload->runtime_cookie);

	size += sizeof(uint32_t);
	size += (payload->command_line != NULL) ?
		(ep_rt_utf16_string_len (payload->command_line) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->os != NULL) ?
		(ep_rt_utf16_string_len (payload->os) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->arch != NULL) ?
		(ep_rt_utf16_string_len (payload->arch) + 1) * sizeof(ep_char16_t) : 0;

	EP_ASSERT (size <= UINT16_MAX);
	return (uint16_t)size;
}

static
bool
process_info_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size)
{
	DiagnosticsProcessInfoPayload *process_info = (DiagnosticsProcessInfoPayload*)payload;

	EP_ASSERT (payload != NULL);
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (*buffer != NULL);
	EP_ASSERT (size != NULL);
	EP_ASSERT (process_info_payload_get_size (process_info) == *size);

	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	bool success = true;

	// uint64_t ProcessId;
	memcpy (*buffer, &process_info->process_id, sizeof (process_info->process_id));
	*buffer += sizeof (process_info->process_id);
	*size -= sizeof (process_info->process_id);

	// GUID RuntimeCookie;
	memcpy(*buffer, &process_info->runtime_cookie, sizeof (process_info->runtime_cookie));
	*buffer += sizeof (process_info->runtime_cookie);
	*size -= sizeof (process_info->runtime_cookie);

	// LPCWSTR CommandLine;
	success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->command_line);

	// LPCWSTR OS;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->os);

	// LPCWSTR Arch;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->arch);

	// Assert we've used the whole buffer we were given
	EP_ASSERT(*size == 0);

	return success;
}

DiagnosticsProcessInfoPayload *
ds_process_info_payload_init (
	DiagnosticsProcessInfoPayload *payload,
	const ep_char16_t *command_line,
	const ep_char16_t *os,
	const ep_char16_t *arch,
	uint32_t process_id,
	const uint8_t *runtime_cookie)
{
	ep_return_null_if_nok (payload != NULL);

	payload->command_line = command_line;
	payload->os = os;
	payload->arch = arch;
	payload->process_id = process_id;

	if (runtime_cookie)
		memcpy (&payload->runtime_cookie, runtime_cookie, EP_GUID_SIZE);

	return payload;
}

void
ds_process_info_payload_fini (DiagnosticsProcessInfoPayload *payload)
{
	;
}

/*
 * DiagnosticsProcessInfo2Payload.
 */

static
uint16_t
process_info_2_payload_get_size (DiagnosticsProcessInfo2Payload *payload)
{
	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	// uint64_t ProcessId;  -> 8 bytes
	// GUID RuntimeCookie;  -> 16 bytes
	// LPCWSTR CommandLine; -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR OS;          -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR Arch;        -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR managed_entrypoint_assembly_name;	-> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR clr_product_version; 				-> 4 bytes + strlen * sizeof(WCHAR)

	EP_ASSERT (payload != NULL);

	size_t size = 0;
	size += sizeof(payload->process_id);
	size += sizeof(payload->runtime_cookie);

	size += sizeof(uint32_t);
	size += (payload->command_line != NULL) ?
		(ep_rt_utf16_string_len (payload->command_line) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->os != NULL) ?
		(ep_rt_utf16_string_len (payload->os) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->arch != NULL) ?
		(ep_rt_utf16_string_len (payload->arch) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->managed_entrypoint_assembly_name != NULL) ?
		(ep_rt_utf16_string_len (payload->managed_entrypoint_assembly_name) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->clr_product_version != NULL) ?
		(ep_rt_utf16_string_len (payload->clr_product_version) + 1) * sizeof(ep_char16_t) : 0;

	EP_ASSERT (size <= UINT16_MAX);
	return (uint16_t)size;
}

static
bool
process_info_2_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size)
{
	DiagnosticsProcessInfo2Payload *process_info = (DiagnosticsProcessInfo2Payload*)payload;

	EP_ASSERT (payload != NULL);
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (*buffer != NULL);
	EP_ASSERT (size != NULL);
	EP_ASSERT (process_info_2_payload_get_size (process_info) == *size);

	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	bool success = true;

	// uint64_t ProcessId;
	memcpy (*buffer, &process_info->process_id, sizeof (process_info->process_id));
	*buffer += sizeof (process_info->process_id);
	*size -= sizeof (process_info->process_id);

	// GUID RuntimeCookie;
	memcpy(*buffer, &process_info->runtime_cookie, sizeof (process_info->runtime_cookie));
	*buffer += sizeof (process_info->runtime_cookie);
	*size -= sizeof (process_info->runtime_cookie);

	// LPCWSTR CommandLine;
	success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->command_line);

	// LPCWSTR OS;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->os);

	// LPCWSTR Arch;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->arch);

	// LPCWSTR managed_entrypoint_assembly_name;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->managed_entrypoint_assembly_name);

	// LPCWSTR clr_product_version;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->clr_product_version);

	// Assert we've used the whole buffer we were given
	EP_ASSERT(*size == 0);

	return success;
}

DiagnosticsProcessInfo2Payload *
ds_process_info_2_payload_init (
	DiagnosticsProcessInfo2Payload *payload,
	const ep_char16_t *command_line,
	const ep_char16_t *os,
	const ep_char16_t *arch,
	uint32_t process_id,
	const uint8_t *runtime_cookie,
	const ep_char16_t *managed_entrypoint_assembly_name,
	const ep_char16_t *clr_product_version)
{
	ep_return_null_if_nok (payload != NULL);

	payload->command_line = command_line;
	payload->os = os;
	payload->arch = arch;
	payload->process_id = process_id;
	payload->managed_entrypoint_assembly_name = managed_entrypoint_assembly_name;
	payload->clr_product_version = clr_product_version;

	if (runtime_cookie)
		memcpy (&payload->runtime_cookie, runtime_cookie, EP_GUID_SIZE);

	return payload;
}

void
ds_process_info_2_payload_fini (DiagnosticsProcessInfo2Payload *payload)
{
	;
}


/*
 * DiagnosticsProcessInfo3Payload.
 */

static
uint16_t
process_info_3_payload_get_size (DiagnosticsProcessInfo3Payload *payload)
{
	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	// uint32_t Version		-> 4 bytes
	// uint64_t ProcessId;  -> 8 bytes
	// GUID RuntimeCookie;  -> 16 bytes
	// LPCWSTR CommandLine; -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR OS;          -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR Arch;        -> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR managed_entrypoint_assembly_name;	-> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR clr_product_version; 				-> 4 bytes + strlen * sizeof(WCHAR)
	// LPCWSTR portable_rid;						-> 4 bytes + strlen * sizeof(WCHAR)

	EP_ASSERT (payload != NULL);

	size_t size = 0;
	size += sizeof(payload->version);
	size += sizeof(payload->process_id);
	size += sizeof(payload->runtime_cookie);

	size += sizeof(uint32_t);
	size += (payload->command_line != NULL) ?
		(ep_rt_utf16_string_len (payload->command_line) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->os != NULL) ?
		(ep_rt_utf16_string_len (payload->os) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->arch != NULL) ?
		(ep_rt_utf16_string_len (payload->arch) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->managed_entrypoint_assembly_name != NULL) ?
		(ep_rt_utf16_string_len (payload->managed_entrypoint_assembly_name) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->clr_product_version != NULL) ?
		(ep_rt_utf16_string_len (payload->clr_product_version) + 1) * sizeof(ep_char16_t) : 0;

	size += sizeof(uint32_t);
	size += (payload->portable_rid != NULL) ?
		(ep_rt_utf16_string_len (payload->portable_rid) + 1) * sizeof(ep_char16_t) : 0;

	EP_ASSERT (size <= UINT16_MAX);
	return (uint16_t)size;
}

static
bool
process_info_3_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size)
{
	DiagnosticsProcessInfo3Payload *process_info = (DiagnosticsProcessInfo3Payload*)payload;

	EP_ASSERT (payload != NULL);
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (*buffer != NULL);
	EP_ASSERT (size != NULL);
	EP_ASSERT (process_info_3_payload_get_size (process_info) == *size);

	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	bool success = true;

	// uint32_t Version;
	memcpy (*buffer, &process_info->version, sizeof(process_info->version));
	*buffer += sizeof (process_info->version);
	*size -= sizeof (process_info->version);

	// uint64_t ProcessId;
	memcpy (*buffer, &process_info->process_id, sizeof (process_info->process_id));
	*buffer += sizeof (process_info->process_id);
	*size -= sizeof (process_info->process_id);

	// GUID RuntimeCookie;
	memcpy(*buffer, &process_info->runtime_cookie, sizeof (process_info->runtime_cookie));
	*buffer += sizeof (process_info->runtime_cookie);
	*size -= sizeof (process_info->runtime_cookie);

	// LPCWSTR CommandLine;
	success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->command_line);

	// LPCWSTR OS;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->os);

	// LPCWSTR Arch;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->arch);

	// LPCWSTR managed_entrypoint_assembly_name;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->managed_entrypoint_assembly_name);

	// LPCWSTR clr_product_version;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->clr_product_version);

	// LPCWSTR portable_rid;
	if (success)
		success &= ds_ipc_message_try_write_string_utf16_t (buffer, size, process_info->portable_rid);

	// Assert we've used the whole buffer we were given
	EP_ASSERT(*size == 0);

	return success;
}

DiagnosticsProcessInfo3Payload *
ds_process_info_3_payload_init (
	DiagnosticsProcessInfo3Payload *payload,
	const ep_char16_t *command_line,
	const ep_char16_t *os,
	const ep_char16_t *arch,
	uint32_t process_id,
	const uint8_t *runtime_cookie,
	const ep_char16_t *managed_entrypoint_assembly_name,
	const ep_char16_t *clr_product_version,
	const ep_char16_t *portable_rid)
{
	ep_return_null_if_nok (payload != NULL);

	// Starting with ProcessInfo3, instead of creating new ProcessInfo# structures,
	// add new fields and do not remove or change existing field; whenever a new field
	// is added, the version must be incremented to allow the client to understand
	// which fields to expect for the given version.
	payload->version = 1;
	payload->command_line = command_line;
	payload->os = os;
	payload->arch = arch;
	payload->process_id = process_id;
	payload->managed_entrypoint_assembly_name = managed_entrypoint_assembly_name;
	payload->clr_product_version = clr_product_version;
	payload->portable_rid = portable_rid;

	if (runtime_cookie)
		memcpy (&payload->runtime_cookie, runtime_cookie, EP_GUID_SIZE);

	return payload;
}

void
ds_process_info_3_payload_fini (DiagnosticsProcessInfo3Payload *payload)
{
	;
}


/*
 * DiagnosticsEnvironmentInfoPayload.
 */

static
uint16_t
env_info_payload_get_size (DiagnosticsEnvironmentInfoPayload *payload)
{
	EP_ASSERT (payload != NULL);

	size_t size = 0;
	size += sizeof (payload->incoming_bytes);
	size += sizeof (payload->future);

	EP_ASSERT (size <= UINT16_MAX);
	return (uint16_t)size;
}

static
uint32_t
env_info_env_block_get_size (DiagnosticsEnvironmentInfoPayload *payload)
{
	EP_ASSERT (payload != NULL);

	size_t size = 0;

	size += sizeof (uint32_t);
	size += (sizeof (uint32_t) * dn_vector_ptr_size (payload->env_array));

	DN_VECTOR_PTR_FOREACH_BEGIN (ep_char16_t *, env_value, payload->env_array) {
		size += ((ep_rt_utf16_string_len (env_value) + 1) * sizeof (ep_char16_t));
	} DN_VECTOR_PTR_FOREACH_END;

	EP_ASSERT (size <= UINT32_MAX);
	return (uint32_t)size;
}

static
bool
env_info_payload_flatten (
	void *payload,
	uint8_t **buffer,
	uint16_t *size)
{
	DiagnosticsEnvironmentInfoPayload *env_info = (DiagnosticsEnvironmentInfoPayload*)payload;

	EP_ASSERT (payload != NULL);
	EP_ASSERT (buffer != NULL);
	EP_ASSERT (*buffer != NULL);
	EP_ASSERT (size != NULL);
	EP_ASSERT (env_info_payload_get_size (env_info) == *size);

	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	bool success = true;

	// uint32_t incoming_bytes;
	memcpy (*buffer, &env_info->incoming_bytes, sizeof (env_info->incoming_bytes));
	*buffer += sizeof (env_info->incoming_bytes);
	*size -= sizeof (env_info->incoming_bytes);

	// uint16_t future;
	memcpy(*buffer, &env_info->future, sizeof (env_info->future));
	*buffer += sizeof (env_info->future);
	*size -= sizeof (env_info->future);

	// Assert we've used the whole buffer we were given
	EP_ASSERT(*size == 0);

	return success;
}

static
bool
env_info_stream_env_block (
	DiagnosticsEnvironmentInfoPayload *payload,
	DiagnosticsIpcStream *stream)
{
	DiagnosticsEnvironmentInfoPayload *env_info = (DiagnosticsEnvironmentInfoPayload*)payload;

	EP_ASSERT (payload != NULL);
	EP_ASSERT (stream != NULL);

	// see IPC spec @ https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
	// for definition of serialization format

	bool success = true;
	uint32_t bytes_written = 0;

	// Array<Array<WCHAR>>
	uint32_t env_len = dn_vector_ptr_size (env_info->env_array);
	env_len = ep_rt_val_uint32_t (env_len);
	success &= ds_ipc_stream_write (stream, (const uint8_t *)&env_len, sizeof (env_len), &bytes_written, EP_INFINITE_WAIT);

	DN_VECTOR_PTR_FOREACH_BEGIN (ep_char16_t *, env_value, env_info->env_array) {
		success &= ds_ipc_message_try_write_string_utf16_t_to_stream (stream, env_value);
	} DN_VECTOR_PTR_FOREACH_END;

	return success;
}

DiagnosticsEnvironmentInfoPayload *
ds_env_info_payload_init (DiagnosticsEnvironmentInfoPayload *payload)
{
	ep_return_null_if_nok (payload != NULL);

	payload->env_array = dn_vector_ptr_alloc ();
	ep_return_null_if_nok (payload->env_array);

	ep_rt_os_environment_get_utf16 (payload->env_array);

	payload->incoming_bytes = env_info_env_block_get_size (payload);
	payload->future = 0;

	return payload;
}

void
ds_env_info_payload_fini (DiagnosticsEnvironmentInfoPayload *payload)
{
	DN_VECTOR_PTR_FOREACH_BEGIN (ep_char16_t *, env_value, payload->env_array) {
		ep_rt_utf16_string_free (env_value);
	} DN_VECTOR_PTR_FOREACH_END;

	dn_vector_ptr_free (payload->env_array);
	payload->env_array = NULL;
}

/*
 * DiagnosticsProcessProtocolHelper.
 */

static
bool
process_protocol_helper_get_process_info (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;
	ep_char16_t *command_line = NULL;
	ep_char16_t *os_info = NULL;
	ep_char16_t *arch_info = NULL;
	DiagnosticsProcessInfoPayload payload;
	DiagnosticsProcessInfoPayload *process_info_payload = NULL;

	command_line = ep_rt_utf8_to_utf16le_string (ep_rt_diagnostics_command_line_get ());
	ep_raise_error_if_nok (command_line != NULL);

	os_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_os_info ());
	ep_raise_error_if_nok (os_info != NULL);

	arch_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_arch_info ());
	ep_raise_error_if_nok (arch_info != NULL);

	process_info_payload = ds_process_info_payload_init (
		&payload,
		command_line,
		os_info,
		arch_info,
		ep_rt_current_process_get_id (),
		ds_ipc_advertise_cookie_v1_get ());
	ep_raise_error_if_nok (process_info_payload != NULL);

	ep_raise_error_if_nok (ds_ipc_message_initialize_buffer (
		message,
		ds_ipc_header_get_generic_success (),
		(void *)process_info_payload,
		process_info_payload_get_size (process_info_payload),
		process_info_payload_flatten));

	ep_raise_error_if_nok (ds_ipc_message_send (message, stream));

	result = true;

ep_on_exit:
	ds_process_info_payload_fini (process_info_payload);
	ep_rt_utf16_string_free (arch_info);
	ep_rt_utf16_string_free (os_info);
	ep_rt_utf16_string_free (command_line);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
	DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_get_process_info_2 (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;
	ep_char16_t *command_line = NULL;
	ep_char16_t *os_info = NULL;
	ep_char16_t *arch_info = NULL;
	ep_char16_t *managed_entrypoint_assembly_name = NULL;
	ep_char16_t *clr_product_version = NULL;
	DiagnosticsProcessInfo2Payload payload;
	DiagnosticsProcessInfo2Payload *process_info_2_payload = NULL;

	command_line = ep_rt_utf8_to_utf16le_string (ep_rt_diagnostics_command_line_get ());
	ep_raise_error_if_nok (command_line != NULL);

	os_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_os_info ());
	ep_raise_error_if_nok (os_info != NULL);

	arch_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_arch_info ());
	ep_raise_error_if_nok (arch_info != NULL);

	managed_entrypoint_assembly_name = ep_rt_utf8_to_utf16le_string (ep_rt_entrypoint_assembly_name_get_utf8 ());
	ep_raise_error_if_nok (managed_entrypoint_assembly_name != NULL);

	clr_product_version = ep_rt_utf8_to_utf16le_string (ep_rt_runtime_version_get_utf8 ());
	ep_raise_error_if_nok (clr_product_version != NULL);

	process_info_2_payload = ds_process_info_2_payload_init (
		&payload,
		command_line,
		os_info,
		arch_info,
		ep_rt_current_process_get_id (),
		ds_ipc_advertise_cookie_v1_get (),
		managed_entrypoint_assembly_name,
		clr_product_version);
	ep_raise_error_if_nok (process_info_2_payload != NULL);

	ep_raise_error_if_nok (ds_ipc_message_initialize_buffer (
		message,
		ds_ipc_header_get_generic_success (),
		(void *)process_info_2_payload,
		process_info_2_payload_get_size (process_info_2_payload),
		process_info_2_payload_flatten));

	ep_raise_error_if_nok (ds_ipc_message_send (message, stream));

	result = true;

ep_on_exit:
	ds_process_info_2_payload_fini (process_info_2_payload);
	ep_rt_utf16_string_free (arch_info);
	ep_rt_utf16_string_free (os_info);
	ep_rt_utf16_string_free (command_line);
	ep_rt_utf16_string_free (managed_entrypoint_assembly_name);
	ep_rt_utf16_string_free (clr_product_version);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
	DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_get_process_info_3 (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;
	ep_char16_t *command_line = NULL;
	ep_char16_t *os_info = NULL;
	ep_char16_t *arch_info = NULL;
	ep_char16_t *managed_entrypoint_assembly_name = NULL;
	ep_char16_t *clr_product_version = NULL;
	ep_char16_t *portable_rid = NULL;
	DiagnosticsProcessInfo3Payload payload;
	DiagnosticsProcessInfo3Payload *process_info_3_payload = NULL;

	command_line = ep_rt_utf8_to_utf16le_string (ep_rt_diagnostics_command_line_get ());
	ep_raise_error_if_nok (command_line != NULL);

	os_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_os_info ());
	ep_raise_error_if_nok (os_info != NULL);

	arch_info = ep_rt_utf8_to_utf16le_string (ep_event_source_get_arch_info ());
	ep_raise_error_if_nok (arch_info != NULL);

	managed_entrypoint_assembly_name = ep_rt_utf8_to_utf16le_string (ep_rt_entrypoint_assembly_name_get_utf8 ());
	ep_raise_error_if_nok (managed_entrypoint_assembly_name != NULL);

	clr_product_version = ep_rt_utf8_to_utf16le_string (ep_rt_runtime_version_get_utf8 ());
	ep_raise_error_if_nok (clr_product_version != NULL);

	portable_rid = ep_rt_utf8_to_utf16le_string (ds_rt_get_portable_rid ());
	ep_raise_error_if_nok (portable_rid != NULL);

	process_info_3_payload = ds_process_info_3_payload_init (
		&payload,
		command_line,
		os_info,
		arch_info,
		ep_rt_current_process_get_id (),
		ds_ipc_advertise_cookie_v1_get (),
		managed_entrypoint_assembly_name,
		clr_product_version,
		portable_rid);
	ep_raise_error_if_nok (process_info_3_payload != NULL);

	ep_raise_error_if_nok (ds_ipc_message_initialize_buffer (
		message,
		ds_ipc_header_get_generic_success (),
		(void *)process_info_3_payload,
		process_info_3_payload_get_size (process_info_3_payload),
		process_info_3_payload_flatten));

	ep_raise_error_if_nok (ds_ipc_message_send (message, stream));

	result = true;

ep_on_exit:
	ds_process_info_3_payload_fini (process_info_3_payload);
	ep_rt_utf16_string_free (arch_info);
	ep_rt_utf16_string_free (os_info);
	ep_rt_utf16_string_free (command_line);
	ep_rt_utf16_string_free (managed_entrypoint_assembly_name);
	ep_rt_utf16_string_free (clr_product_version);
	ep_rt_utf16_string_free (portable_rid);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
	DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_get_process_env (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;
	DiagnosticsEnvironmentInfoPayload payload;
	DiagnosticsEnvironmentInfoPayload *env_info_payload;

	env_info_payload = ds_env_info_payload_init (&payload);
	ep_raise_error_if_nok (env_info_payload);

	ep_raise_error_if_nok (ds_ipc_message_initialize_buffer (
		message,
		ds_ipc_header_get_generic_success (),
		(void *)env_info_payload,
		env_info_payload_get_size (env_info_payload),
		env_info_payload_flatten));

	ep_raise_error_if_nok (ds_ipc_message_send (message, stream));
	ep_raise_error_if_nok (env_info_stream_env_block (env_info_payload, stream));

	result = true;

ep_on_exit:
	ds_env_info_payload_fini (env_info_payload);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
	DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_resume_runtime_startup (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;

	// no payload
	ds_server_resume_runtime_startup ();
	result = ds_ipc_message_send_success (stream, DS_IPC_S_OK);
	if (!result) {
		ds_ipc_message_send_error (stream, DS_IPC_E_FAIL);
		DS_LOG_WARNING_0 ("Failed to send DiagnosticsIPC response");
	}

	ds_ipc_stream_free (stream);
	return result;
}

DiagnosticsSetEnvironmentVariablePayload *
ds_set_environment_variable_payload_alloc (void)
{
	return ep_rt_object_alloc (DiagnosticsSetEnvironmentVariablePayload);
}

void
ds_set_environment_variable_payload_free (DiagnosticsSetEnvironmentVariablePayload *payload)
{
	ep_return_void_if_nok (payload != NULL);
	ep_rt_byte_array_free (payload->incoming_buffer);
	ep_rt_object_free (payload);
}

static
uint8_t *
set_environment_variable_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len)
{
	EP_ASSERT (buffer != NULL);

	uint8_t * buffer_cursor = buffer;
	uint32_t buffer_cursor_len = buffer_len;

	DiagnosticsSetEnvironmentVariablePayload *instance = ds_set_environment_variable_payload_alloc ();
	ep_raise_error_if_nok (instance != NULL);

	instance->incoming_buffer = buffer;

	if (!ds_ipc_message_try_parse_string_utf16_t (&buffer_cursor, &buffer_cursor_len, &instance->name) ||
		!ds_ipc_message_try_parse_string_utf16_t (&buffer_cursor, &buffer_cursor_len, &instance->value))
		ep_raise_error ();

ep_on_exit:
	return (uint8_t *)instance;

ep_on_error:
	ds_set_environment_variable_payload_free (instance);
	instance = NULL;
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_set_environment_variable (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

	bool result = false;
	DiagnosticsSetEnvironmentVariablePayload *payload = (DiagnosticsSetEnvironmentVariablePayload *)ds_ipc_message_try_parse_payload (message, set_environment_variable_command_try_parse_payload);
	if (!payload) {
		ds_ipc_message_send_error (stream, DS_IPC_E_BAD_ENCODING);
		ep_raise_error ();
	}

	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_set_environment_variable (payload->name, payload->value);
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_set_environment_variable_payload_free (payload);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

DiagnosticsEnablePerfmapPayload *
ds_enable_perfmap_payload_alloc (void)
{
	return ep_rt_object_alloc (DiagnosticsEnablePerfmapPayload);
}

void
ds_enable_perfmap_payload_free (DiagnosticsEnablePerfmapPayload *payload)
{
	ep_return_void_if_nok (payload != NULL);
	ep_rt_byte_array_free (payload->incoming_buffer);
	ep_rt_object_free (payload);
}

static
uint8_t *
enable_perfmap_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len)
{
	EP_ASSERT (buffer != NULL);

	uint8_t * buffer_cursor = buffer;
	uint32_t buffer_cursor_len = buffer_len;

	DiagnosticsEnablePerfmapPayload *instance = ds_enable_perfmap_payload_alloc ();
	ep_raise_error_if_nok (instance != NULL);

	instance->incoming_buffer = buffer;

	if (!ds_ipc_message_try_parse_uint32_t (&buffer_cursor, &buffer_cursor_len, &instance->perfMapType))
		ep_raise_error ();

ep_on_exit:
	return (uint8_t *)instance;

ep_on_error:
	ds_enable_perfmap_payload_free (instance);
	instance = NULL;
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_enable_perfmap (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

	bool result = false;
	DiagnosticsEnablePerfmapPayload *payload = (DiagnosticsEnablePerfmapPayload *)ds_ipc_message_try_parse_payload (message, enable_perfmap_command_try_parse_payload);
	if (!payload) {
		ds_ipc_message_send_error (stream, DS_IPC_E_BAD_ENCODING);
		ep_raise_error ();
	}

	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_enable_perfmap (payload->perfMapType);
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_enable_perfmap_payload_free (payload);
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_disable_perfmap (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

	bool result = false;
	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_disable_perfmap ();
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

DiagnosticsApplyStartupHookPayload *
ds_apply_startup_hook_payload_alloc (void)
{
	return ep_rt_object_alloc (DiagnosticsApplyStartupHookPayload);
}

void
ds_apply_startup_hook_payload_free (DiagnosticsApplyStartupHookPayload *payload)
{
	ep_return_void_if_nok (payload != NULL);
	ep_rt_byte_array_free (payload->incoming_buffer);
	ep_rt_object_free (payload);
}

static
uint8_t *
apply_startup_hook_command_try_parse_payload (
	uint8_t *buffer,
	uint16_t buffer_len)
{
	EP_ASSERT (buffer != NULL);

	uint8_t * buffer_cursor = buffer;
	uint32_t buffer_cursor_len = buffer_len;

	DiagnosticsApplyStartupHookPayload *instance = ds_apply_startup_hook_payload_alloc ();
	ep_raise_error_if_nok (instance != NULL);

	instance->incoming_buffer = buffer;

	if (!ds_ipc_message_try_parse_string_utf16_t (&buffer_cursor, &buffer_cursor_len, &instance->startup_hook_path))
		ep_raise_error ();

ep_on_exit:
	return (uint8_t *)instance;

ep_on_error:
	ds_apply_startup_hook_payload_free (instance);
	instance = NULL;
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_apply_startup_hook (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	if (!stream)
		return false;

	bool result = false;
	DiagnosticsApplyStartupHookPayload *payload = (DiagnosticsApplyStartupHookPayload *)ds_ipc_message_try_parse_payload (message, apply_startup_hook_command_try_parse_payload);
	if (!payload) {
		ds_ipc_message_send_error (stream, DS_IPC_E_BAD_ENCODING);
		ep_raise_error ();
	}

	ds_ipc_result_t ipc_result;
	ipc_result = ds_rt_apply_startup_hook (payload->startup_hook_path);
	if (ipc_result != DS_IPC_S_OK) {
		ds_ipc_message_send_error (stream, ipc_result);
		ep_raise_error ();
	} else {
		ds_ipc_message_send_success (stream, ipc_result);
	}

	result = true;

ep_on_exit:
	ds_ipc_stream_free (stream);
	return result;

ep_on_error:
	EP_ASSERT (!result);
	ep_exit_error_handler ();
}

static
bool
process_protocol_helper_unknown_command (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	DS_LOG_WARNING_1 ("Received unknown request type (%d)", ds_ipc_header_get_commandset (ds_ipc_message_get_header_ref (message)));
	ds_ipc_message_send_error (stream, DS_IPC_E_UNKNOWN_COMMAND);
	ds_ipc_stream_free (stream);
	return true;
}

bool
ds_process_protocol_helper_handle_ipc_message (
	DiagnosticsIpcMessage *message,
	DiagnosticsIpcStream *stream)
{
	EP_ASSERT (message != NULL);
	EP_ASSERT (stream != NULL);

	bool result = false;

	switch ((DiagnosticsProcessCommandId)ds_ipc_header_get_commandid (ds_ipc_message_get_header_ref (message))) {
	case DS_PROCESS_COMMANDID_GET_PROCESS_INFO:
		result = process_protocol_helper_get_process_info (message, stream);
		break;
	case DS_PROCESS_COMMANDID_RESUME_RUNTIME:
		result = process_protocol_helper_resume_runtime_startup (message, stream);
		break;
	case DS_PROCESS_COMMANDID_GET_PROCESS_ENV:
		result = process_protocol_helper_get_process_env (message, stream);
		break;
	case DS_PROCESS_COMMANDID_SET_ENV_VAR:
		result = process_protocol_helper_set_environment_variable (message, stream);
		break;
	case DS_PROCESS_COMMANDID_GET_PROCESS_INFO_2:
		result = process_protocol_helper_get_process_info_2 (message, stream);
		break;
	case DS_PROCESS_COMMANDID_ENABLE_PERFMAP:
		result = process_protocol_helper_enable_perfmap (message, stream);
		break;
	case DS_PROCESS_COMMANDID_DISABLE_PERFMAP:
		result = process_protocol_helper_disable_perfmap (message, stream);
		break;
	case DS_PROCESS_COMMANDID_APPLY_STARTUP_HOOK:
		result = process_protocol_helper_apply_startup_hook (message, stream);
		break;
	case DS_PROCESS_COMMANDID_GET_PROCESS_INFO_3:
		result = process_protocol_helper_get_process_info_3 (message, stream);
		break;
	default:
		result = process_protocol_helper_unknown_command (message, stream);
		break;
	}

	return result;
}

#endif /* !defined(DS_INCLUDE_SOURCE_FILES) || defined(DS_FORCE_INCLUDE_SOURCE_FILES) */
#endif /* ENABLE_PERFTRACING */

#if !defined(ENABLE_PERFTRACING) || (defined(DS_INCLUDE_SOURCE_FILES) && !defined(DS_FORCE_INCLUDE_SOURCE_FILES))
extern const char quiet_linker_empty_file_warning_diagnostics_process_protocol;
const char quiet_linker_empty_file_warning_diagnostics_process_protocol = 0;
#endif
