/*
 * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#include <lttng/session-descriptor-internal.h>
#include <common/macros.h>
#include <common/uri.h>
#include <common/defaults.h>
#include <common/error.h>
#include <time.h>
#include <assert.h>
#include <stdio.h>

struct lttng_session_descriptor_network_location {
	struct lttng_uri *control;
	struct lttng_uri *data;
};

struct lttng_session_descriptor {
	enum lttng_session_descriptor_type type;
	/*
	 * If an output type that is not OUTPUT_TYPE_NONE is specified,
	 * it means that an output of that type must be generated at
	 * session-creation time.
	 */
	enum lttng_session_descriptor_output_type output_type;
	char *name;
	union {
		struct lttng_session_descriptor_network_location network;
		struct lttng_uri *local;
	} output;
};

struct lttng_session_descriptor_snapshot {
	struct lttng_session_descriptor base;
	/*
	 * Assumes at-most one snapshot output is supported. Uses
	 * the output field of the base class.
	 */
};

struct lttng_session_descriptor_live {
	struct lttng_session_descriptor base;
	unsigned long long live_timer_us;
};

struct lttng_session_descriptor_comm {
	/* enum lttng_session_descriptor_type */
	uint8_t type;
	/* enum lttng_session_descriptor_output_type */
	uint8_t output_type;
	/* Includes trailing null. */
	uint32_t name_len;
	/* Name follows, followed by URIs */
	uint8_t uri_count;
} LTTNG_PACKED;

struct lttng_session_descriptor_live_comm {
	struct lttng_session_descriptor_comm base;
	/* Live-specific parameters. */
	uint64_t live_timer_us;
} LTTNG_PACKED;

static
struct lttng_uri *uri_copy(const struct lttng_uri *uri)
{
	struct lttng_uri *new_uri = NULL;

	if (!uri) {
		goto end;
	}

	new_uri = zmalloc(sizeof(*new_uri));
	if (!new_uri) {
		goto end;
	}
	memcpy(new_uri, uri, sizeof(*new_uri));
end:
	return new_uri;
}

static
struct lttng_uri *uri_from_path(const char *path)
{
	struct lttng_uri *uris = NULL;
	ssize_t uri_count;
	char local_protocol_string[LTTNG_PATH_MAX + sizeof("file://")] =
			"file://";

	if (strlen(path) >= LTTNG_PATH_MAX) {
		goto end;
	}

	if (path[0] != '/') {
		/* Not an absolute path. */
		goto end;
	}

	strncat(local_protocol_string, path, LTTNG_PATH_MAX);
	uri_count = uri_parse(local_protocol_string, &uris);
	if (uri_count != 1) {
		goto error;
	}
	if (uris[0].dtype != LTTNG_DST_PATH) {
		goto error;
	}

end:
	return uris;
error:
	free(uris);
	return NULL;
}

static
void network_location_fini(
		struct lttng_session_descriptor_network_location *location)
{
	free(location->control);
	free(location->data);
}

/* Assumes ownership of control and data. */
static
int network_location_set_from_lttng_uris(
		struct lttng_session_descriptor_network_location *location,
		struct lttng_uri *control, struct lttng_uri *data)
{
	int ret = 0;

	if (!control && !data) {
		goto end;
	}

	if (!(control && data)) {
		/* None or both must be set. */
		ret = -1;
		goto end;
	}

	if (control->stype != LTTNG_STREAM_CONTROL ||
			data->stype != LTTNG_STREAM_DATA) {
		ret = -1;
		goto end;
	}

	free(location->control);
	free(location->data);
	location->control = control;
	location->data = data;
	control = NULL;
	data = NULL;
end:
	free(control);
	free(data);
	return ret;
}

static
int network_location_set_from_uri_strings(
		struct lttng_session_descriptor_network_location *location,
		const char *control, const char *data)
{
	int ret = 0;
	ssize_t uri_count;
	struct lttng_uri *parsed_uris = NULL;
	struct lttng_uri *control_uri = NULL;
	struct lttng_uri *data_uri = NULL;

	uri_count = uri_parse_str_urls(control, data, &parsed_uris);
	if (uri_count != 2 && uri_count != 0) {
		ret = -1;
		goto end;
	}

	/*
	 * uri_parse_str_urls returns a contiguous array of lttng_uris whereas
	 * session descriptors expect individually allocated lttng_uris.
	 */
	if (uri_count == 2) {
		control_uri = zmalloc(sizeof(*control_uri));
		data_uri = zmalloc(sizeof(*data_uri));
		if (!control_uri || !data_uri) {
			ret = -1;
			goto end;
		}
		memcpy(control_uri, &parsed_uris[0], sizeof(*control_uri));
		memcpy(data_uri, &parsed_uris[1], sizeof(*data_uri));
	}

	/* Ownership of control and data uris is transferred. */
	ret = network_location_set_from_lttng_uris(
			location,
			control_uri,
			data_uri);
	control_uri = NULL;
	data_uri = NULL;
end:
	free(parsed_uris);
	free(control_uri);
	free(data_uri);
	return ret;
}

struct lttng_session_descriptor *
lttng_session_descriptor_create(const char *name)
{
	struct lttng_session_descriptor *descriptor;

	descriptor = zmalloc(sizeof(*descriptor));
	if (!descriptor) {
		goto error;
	}

	descriptor->type = LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR;
	descriptor->output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE;
	if (lttng_session_descriptor_set_session_name(descriptor, name)) {
		goto error;
	}
	return descriptor;
error:
	lttng_session_descriptor_destroy(descriptor);
	return NULL;
}

/* Ownership of uri is transferred. */
static
struct lttng_session_descriptor *
_lttng_session_descriptor_local_create(const char *name,
		struct lttng_uri *uri)
{
	struct lttng_session_descriptor *descriptor;

	descriptor = lttng_session_descriptor_create(name);
	if (!descriptor) {
		goto error;
	}
	descriptor->type = LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR;
	descriptor->output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL;
	if (uri) {
		if (uri->dtype != LTTNG_DST_PATH) {
			goto error;
		}
		descriptor->output.local = uri;
		uri = NULL;
	}
	return descriptor;
error:
	free(uri);
	lttng_session_descriptor_destroy(descriptor);
	return NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_local_create(const char *name, const char *path)
{
	struct lttng_uri *uri = NULL;
	struct lttng_session_descriptor *descriptor;

	if (path) {
		uri = uri_from_path(path);
		if (!uri) {
			goto error;
		}
	}
	descriptor = _lttng_session_descriptor_local_create(name, uri);
	return descriptor;
error:
	return NULL;
}

/* Assumes the ownership of both uris. */
static
struct lttng_session_descriptor *
_lttng_session_descriptor_network_create(const char *name,
		struct lttng_uri *control, struct lttng_uri *data)
{
	int ret;
	struct lttng_session_descriptor *descriptor;

	descriptor = lttng_session_descriptor_create(name);
	if (!descriptor) {
		goto error;
	}

	descriptor->type = LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR;
	descriptor->output_type = LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK;
	/* Assumes the ownership of both uris. */
	ret = network_location_set_from_lttng_uris(&descriptor->output.network,
			control, data);
	control = NULL;
	data = NULL;
	if (ret) {
		goto error;
	}
	return descriptor;
error:
	lttng_session_descriptor_destroy(descriptor);
	free(control);
	free(data);
	return NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_network_create(const char *name,
		const char *control_url, const char *data_url)
{
	int ret;
	struct lttng_session_descriptor *descriptor;

	descriptor = _lttng_session_descriptor_network_create(name,
			NULL, NULL);
	if (!descriptor) {
		goto error;
	}

	ret = network_location_set_from_uri_strings(&descriptor->output.network,
			control_url, data_url);
	if (ret) {
		goto error;
	}
	return descriptor;
error:
	lttng_session_descriptor_destroy(descriptor);
	return NULL;
}

static
struct lttng_session_descriptor_snapshot *
_lttng_session_descriptor_snapshot_create(const char *name)
{
	struct lttng_session_descriptor_snapshot *descriptor;

	descriptor = zmalloc(sizeof(*descriptor));
	if (!descriptor) {
		goto error;
	}

	descriptor->base.type = LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT;
	descriptor->base.output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE;
	if (lttng_session_descriptor_set_session_name(&descriptor->base,
			name)) {
		goto error;
	}
	return descriptor;
error:
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

/* Ownership of control and data is transferred. */
static
struct lttng_session_descriptor_snapshot *
_lttng_session_descriptor_snapshot_network_create(const char *name,
		struct lttng_uri *control, struct lttng_uri *data)
{
	int ret;
	struct lttng_session_descriptor_snapshot *descriptor;

	descriptor = _lttng_session_descriptor_snapshot_create(name);
	if (!descriptor) {
		goto error;
	}

	descriptor->base.output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK;
	/* Ownership of control and data is transferred. */
	ret = network_location_set_from_lttng_uris(
			&descriptor->base.output.network,
			control, data);
	control = NULL;
	data = NULL;
	if (ret) {
		goto error;
	}
	return descriptor;
error:
	free(control);
	free(data);
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_snapshot_create(const char *name)
{
	struct lttng_session_descriptor_snapshot *descriptor;

	descriptor = _lttng_session_descriptor_snapshot_create(name);
	return descriptor ? &descriptor->base : NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_snapshot_network_create(const char *name,
		const char *control_url, const char *data_url)
{
	int ret;
	struct lttng_session_descriptor_snapshot *descriptor;

	descriptor = _lttng_session_descriptor_snapshot_network_create(name,
			NULL, NULL);
	if (!descriptor) {
		goto error;
	}

	ret = network_location_set_from_uri_strings(
			&descriptor->base.output.network,
			control_url, data_url);
	if (ret) {
		goto error;
	}
	return &descriptor->base;
error:
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

/* Ownership of uri is transferred. */
static
struct lttng_session_descriptor_snapshot *
_lttng_session_descriptor_snapshot_local_create(const char *name,
		struct lttng_uri *uri)
{
	struct lttng_session_descriptor_snapshot *descriptor;

	descriptor = _lttng_session_descriptor_snapshot_create(name);
	if (!descriptor) {
		goto error;
	}
	descriptor->base.output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL;
	if (uri) {
		if (uri->dtype != LTTNG_DST_PATH) {
			goto error;
		}
		descriptor->base.output.local = uri;
		uri = NULL;
	}
	return descriptor;
error:
	free(uri);
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_snapshot_local_create(const char *name,
		const char *path)
{
	struct lttng_uri *path_uri = NULL;
	struct lttng_session_descriptor_snapshot *descriptor;

	if (path) {
		path_uri = uri_from_path(path);
		if (!path_uri) {
			goto error;
		}
	}
	descriptor = _lttng_session_descriptor_snapshot_local_create(name,
			path_uri);
	return descriptor ? &descriptor->base : NULL;
error:
	return NULL;
}

static
struct lttng_session_descriptor_live *
_lttng_session_descriptor_live_create(const char *name,
		unsigned long long live_timer_interval_us)
{
	struct lttng_session_descriptor_live *descriptor = NULL;

	if (live_timer_interval_us == 0) {
		goto error;
	}
	descriptor = zmalloc(sizeof(*descriptor));
	if (!descriptor) {
		goto error;
	}

	descriptor->base.type = LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE;
	descriptor->base.output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE;
	descriptor->live_timer_us = live_timer_interval_us;
	if (lttng_session_descriptor_set_session_name(&descriptor->base,
			name)) {
		goto error;
	}

	return descriptor;
error:
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

/* Ownership of control and data is transferred. */
static
struct lttng_session_descriptor_live *
_lttng_session_descriptor_live_network_create(
		const char *name,
		struct lttng_uri *control, struct lttng_uri *data,
		unsigned long long live_timer_interval_us)
{
	int ret;
	struct lttng_session_descriptor_live *descriptor;

	descriptor = _lttng_session_descriptor_live_create(name,
			live_timer_interval_us);
	if (!descriptor) {
		goto error;
	}

	descriptor->base.output_type =
			LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK;

	/* Ownerwhip of control and data is transferred. */
	ret = network_location_set_from_lttng_uris(
			&descriptor->base.output.network,
			control, data);
	control = NULL;
	data = NULL;
	if (ret) {
		goto error;
	}
	return descriptor;
error:
	free(control);
	free(data);
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_live_create(
		const char *name,
		unsigned long long live_timer_us)
{
	struct lttng_session_descriptor_live *descriptor;

	descriptor = _lttng_session_descriptor_live_create(name, live_timer_us);

	return descriptor ? &descriptor->base : NULL;
}

struct lttng_session_descriptor *
lttng_session_descriptor_live_network_create(
		const char *name,
		const char *control_url, const char *data_url,
		unsigned long long live_timer_us)
{
	int ret;
	struct lttng_session_descriptor_live *descriptor;

	descriptor = _lttng_session_descriptor_live_network_create(name,
			NULL, NULL, live_timer_us);
	if (!descriptor) {
		goto error;
	}

	ret = network_location_set_from_uri_strings(
			&descriptor->base.output.network,
			control_url, data_url);
	if (ret) {
		goto error;
	}
	return &descriptor->base;
error:
	lttng_session_descriptor_destroy(descriptor ? &descriptor->base : NULL);
	return NULL;
}

void lttng_session_descriptor_destroy(
		struct lttng_session_descriptor *descriptor)
{
	if (!descriptor) {
		return;
	}

	switch (descriptor->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		free(descriptor->output.local);
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
		network_location_fini(&descriptor->output.network);
		break;
	default:
		abort();
	}

	free(descriptor->name);
	free(descriptor);
}

LTTNG_HIDDEN
ssize_t lttng_session_descriptor_create_from_buffer(
		const struct lttng_buffer_view *payload,
		struct lttng_session_descriptor **descriptor)
{
	int i;
	ssize_t offset = 0, ret;
	struct lttng_buffer_view current_view;
	const char *name = NULL;
	const struct lttng_session_descriptor_comm *base_header;
	size_t max_expected_uri_count;
	uint64_t live_timer_us = 0;
	struct lttng_uri *uris[2] = {};
	enum lttng_session_descriptor_type type;
	enum lttng_session_descriptor_output_type output_type;

	current_view = lttng_buffer_view_from_view(payload, offset,
			sizeof(*base_header));
	if (!lttng_buffer_view_is_valid(&current_view)) {
		ret = -1;
		goto end;
	}

	base_header = (typeof(base_header)) current_view.data;
	switch (base_header->type) {
	case LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR:
	case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT:
		break;
	case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE:
	{
		const struct lttng_session_descriptor_live_comm *live_header;

		current_view = lttng_buffer_view_from_view(payload, offset,
				sizeof(*live_header));
		if (!lttng_buffer_view_is_valid(&current_view)) {
			ret = -1;
			goto end;
		}

		live_header = (typeof(live_header)) current_view.data;
		live_timer_us = live_header->live_timer_us;
		break;
	}
	default:
		ret = -1;
		goto end;
	}
	/* type has been validated. */
	type = base_header->type;

	switch (base_header->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		max_expected_uri_count = 0;
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		max_expected_uri_count = 1;
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
		max_expected_uri_count = 2;
		break;
	default:
		ret = -1;
		goto end;
	}
	/* output_type has been validated. */
	output_type = base_header->output_type;

	/* Skip after header. */
	offset += current_view.size;
	if (!base_header->name_len) {
		goto skip_name;
	}

	/* Map the name. */
	current_view = lttng_buffer_view_from_view(payload, offset,
			base_header->name_len);
	if (!lttng_buffer_view_is_valid(&current_view)) {
		ret = -1;
		goto end;
	}

	name = current_view.data;
	if (base_header->name_len == 1 ||
			name[base_header->name_len - 1] ||
			strlen(name) != base_header->name_len - 1) {
		/*
		 * Check that the name is not NULL, is NULL-terminated, and
		 * does not contain a NULL before the last byte.
		 */
		ret = -1;
		goto end;
	}

	/* Skip after the name. */
	offset += base_header->name_len;
skip_name:
	if (base_header->uri_count > max_expected_uri_count) {
		ret = -1;
		goto end;
	}

	for (i = 0; i < base_header->uri_count; i++) {
		struct lttng_uri *uri;

		/* Map a URI. */
		current_view = lttng_buffer_view_from_view(payload,
				offset, sizeof(*uri));
		if (!lttng_buffer_view_is_valid(&current_view)) {
			ret = -1;
			goto end;
		}

		uri = (typeof(uri)) current_view.data;
		uris[i] = zmalloc(sizeof(*uri));
		if (!uris[i]) {
			ret = -1;
			goto end;
		}
		memcpy(uris[i], uri, sizeof(*uri));
		offset += sizeof(*uri);
	}

	switch (type) {
	case LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR:
		switch (output_type) {
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
			*descriptor = lttng_session_descriptor_create(name);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
			*descriptor = _lttng_session_descriptor_local_create(
					name, uris[0]);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
			*descriptor = _lttng_session_descriptor_network_create(
					name, uris[0], uris[1]);
			break;
		default:
			/* Already checked. */
			abort();
		}
		break;
	case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT:
	{
		struct lttng_session_descriptor_snapshot *snapshot;
		switch (output_type) {
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
			snapshot = _lttng_session_descriptor_snapshot_create(
					name);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
			snapshot = _lttng_session_descriptor_snapshot_local_create(
					name, uris[0]);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
			snapshot = _lttng_session_descriptor_snapshot_network_create(
					name, uris[0], uris[1]);
			break;
		default:
			/* Already checked. */
			abort();
		}
		*descriptor = snapshot ? &snapshot->base : NULL;
		break;
	}
	case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE:
	{
		struct lttng_session_descriptor_live *live;

		switch (output_type) {
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
			live = _lttng_session_descriptor_live_create(
					name, live_timer_us);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
			live = _lttng_session_descriptor_live_network_create(
					name, uris[0], uris[1],
					live_timer_us);
			break;
		case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
			ret = -1;
			goto end;
		default:
			/* Already checked. */
			abort();
		}
		*descriptor = live ? &live->base : NULL;
		break;
	}
	default:
		/* Already checked. */
		abort();
	}
	memset(uris, 0, sizeof(uris));
	if (!*descriptor) {
		ret = -1;
		goto end;
	}

	ret = offset;
end:
	free(uris[0]);
	free(uris[1]);
	return ret;
}

LTTNG_HIDDEN
int lttng_session_descriptor_serialize(
		const struct lttng_session_descriptor *descriptor,
		struct lttng_dynamic_buffer *buffer)
{
	int ret, i;
	/* There are, at most, two URIs to serialize. */
	struct lttng_uri *uris[2] = {};
	size_t uri_count = 0;
	/* The live header is a superset of all headers. */
	struct lttng_session_descriptor_live_comm header = {
		.base.type = (uint8_t) descriptor->type,
		.base.output_type = (uint8_t) descriptor->output_type,
		.base.name_len = descriptor->name ?
				strlen(descriptor->name) + 1 : 0,
	};
	const void *header_ptr = NULL;
	size_t header_size;

	switch (descriptor->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		uris[0] = descriptor->output.local;
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
		uris[0] = descriptor->output.network.control;
		uris[1] = descriptor->output.network.data;
		break;
	default:
		ret = -1;
		goto end;
	}
	uri_count += !!uris[0];
	uri_count += !!uris[1];

	header.base.uri_count = uri_count;
	if (descriptor->type == LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE) {
		const struct lttng_session_descriptor_live *live =
				container_of(descriptor, typeof(*live),
				base);

		header.live_timer_us = live->live_timer_us;
		header_ptr = &header;
		header_size = sizeof(header);
	} else {
		header_ptr = &header.base;
		header_size = sizeof(header.base);
	}

	ret = lttng_dynamic_buffer_append(buffer, header_ptr, header_size);
	if (ret) {
		goto end;
	}
	if (header.base.name_len) {
		ret = lttng_dynamic_buffer_append(buffer, descriptor->name,
				header.base.name_len);
		if (ret) {
			goto end;
		}
	}

	for (i = 0; i < uri_count; i++) {
		ret = lttng_dynamic_buffer_append(buffer, uris[i],
				sizeof(struct lttng_uri));
		if (ret) {
			goto end;
		}
	}
end:
	return ret;
}

LTTNG_HIDDEN
enum lttng_session_descriptor_type
lttng_session_descriptor_get_type(
		const struct lttng_session_descriptor *descriptor)
{
	return descriptor->type;
}

LTTNG_HIDDEN
enum lttng_session_descriptor_output_type
lttng_session_descriptor_get_output_type(
		const struct lttng_session_descriptor *descriptor)
{
	return descriptor->output_type;
}

LTTNG_HIDDEN
void lttng_session_descriptor_get_local_output_uri(
		const struct lttng_session_descriptor *descriptor,
		struct lttng_uri *local_uri)
{
	memcpy(local_uri, descriptor->output.local, sizeof(*local_uri));
}

LTTNG_HIDDEN
void lttng_session_descriptor_get_network_output_uris(
		const struct lttng_session_descriptor *descriptor,
		struct lttng_uri *control,
		struct lttng_uri *data)
{
	memcpy(control, descriptor->output.network.control, sizeof(*control));
	memcpy(data, descriptor->output.network.data, sizeof(*data));
}

LTTNG_HIDDEN
unsigned long long
lttng_session_descriptor_live_get_timer_interval(
		const struct lttng_session_descriptor *descriptor)
{
	struct lttng_session_descriptor_live *live;

	live = container_of(descriptor, typeof(*live), base);
	return live->live_timer_us;
}

enum lttng_session_descriptor_status
lttng_session_descriptor_get_session_name(
		const struct lttng_session_descriptor *descriptor,
		const char **session_name)
{
	enum lttng_session_descriptor_status status;

	if (!descriptor || !session_name) {
		status = LTTNG_SESSION_DESCRIPTOR_STATUS_INVALID;
		goto end;
	}

	*session_name = descriptor->name;
	status = descriptor->name ?
			LTTNG_SESSION_DESCRIPTOR_STATUS_OK :
			LTTNG_SESSION_DESCRIPTOR_STATUS_UNSET;
end:
	return status;
}

LTTNG_HIDDEN
int lttng_session_descriptor_set_session_name(
		struct lttng_session_descriptor *descriptor,
		const char *name)
{
	int ret = 0;
	char *new_name;

	if (!name) {
		goto end;
	}
	if (strlen(name) >= LTTNG_NAME_MAX) {
		ret = -1;
		goto end;
	}
	new_name = strdup(name);
	if (!new_name) {
		ret = -1;
		goto end;
	}
	free(descriptor->name);
	descriptor->name = new_name;
end:
	return ret;
}

LTTNG_HIDDEN
bool lttng_session_descriptor_is_output_destination_initialized(
		const struct lttng_session_descriptor *descriptor)
{
	switch (descriptor->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		return true;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		return descriptor->output.local;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
		return descriptor->output.network.control;
	default:
		abort();
	}
}

LTTNG_HIDDEN
bool lttng_session_descriptor_has_output_directory(
		const struct lttng_session_descriptor *descriptor)
{
	switch (descriptor->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		if (descriptor->output.local) {
			return *descriptor->output.local->dst.path;
		}
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
		if (descriptor->output.network.control) {
			return *descriptor->output.network.control->subdir;
		}
		break;
	default:
		abort();
	}
	return false;
}

LTTNG_HIDDEN
enum lttng_error_code lttng_session_descriptor_set_default_output(
		struct lttng_session_descriptor *descriptor,
		time_t *session_creation_time,
		const char *absolute_home_path)
{
	enum lttng_error_code ret_code = LTTNG_OK;
	struct lttng_uri *uris = NULL;

	switch (descriptor->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		goto end;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
	{
		int ret;
		ssize_t uri_ret;
		char local_uri[LTTNG_PATH_MAX];
		char creation_datetime_suffix[17] = {};

		if (session_creation_time) {
			size_t strftime_ret;
			struct tm *timeinfo;

			timeinfo = localtime(session_creation_time);
			if (!timeinfo) {
				ret_code = LTTNG_ERR_FATAL;
				goto end;
			}
			strftime_ret = strftime(creation_datetime_suffix,
					sizeof(creation_datetime_suffix),
				 	"-%Y%m%d-%H%M%S", timeinfo);
			if (strftime_ret == 0) {
				ERR("Failed to format session creation timestamp while setting default local output destination");
				ret_code = LTTNG_ERR_FATAL;
				goto end;
			}
		}
		assert(descriptor->name);
		ret = snprintf(local_uri, sizeof(local_uri),
				"file://%s/%s/%s%s",
				absolute_home_path,
				DEFAULT_TRACE_DIR_NAME, descriptor->name,
				creation_datetime_suffix);
		if (ret >= sizeof(local_uri)) {
			ERR("Truncation occurred while setting default local output destination");
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		} else if (ret < 0) {
			PERROR("Failed to format default local output URI");
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		}

		uri_ret = uri_parse(local_uri, &uris);
		if (uri_ret != 1) {
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		}
		free(descriptor->output.local);
		descriptor->output.local = &uris[0];
		uris = NULL;
		break;
	}
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
	{
		int ret;
		ssize_t uri_ret;
		struct lttng_uri *control = NULL, *data = NULL;

		uri_ret = uri_parse_str_urls("net://127.0.0.1", NULL, &uris);
		if (uri_ret != 2) {
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		}

		control = uri_copy(&uris[0]);
		data = uri_copy(&uris[1]);
		if (!control || !data) {
			free(control);
			free(data);
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		}

		/* Ownership of uris is transferred. */
		ret = network_location_set_from_lttng_uris(
				&descriptor->output.network,
				control, data);
		if (ret) {
			abort();
			ret_code = LTTNG_ERR_SET_URL;
			goto end;
		}
		break;
	}
	default:
		abort();
	}
end:
	free(uris);
	return ret_code;
}

/*
 * Note that only properties that can be populated by the session daemon
 * (output destination and name) are assigned.
 */
LTTNG_HIDDEN
int lttng_session_descriptor_assign(
		struct lttng_session_descriptor *dst,
		const struct lttng_session_descriptor *src)
{
	int ret = 0;

	if (dst->type != src->type) {
		ret = -1;
		goto end;
	}
	if (dst->output_type != src->output_type) {
		ret = -1;
		goto end;
	}
	ret = lttng_session_descriptor_set_session_name(dst, src->name);
	if (ret) {
		goto end;
	}
	switch (dst->output_type) {
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL:
		free(dst->output.local);
		dst->output.local = uri_copy(src->output.local);
		if (!dst->output.local) {
			ret = -1;
			goto end;
		}
		break;
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK:
	{
		struct lttng_uri *control_copy = NULL, *data_copy = NULL;

		control_copy = uri_copy(dst->output.network.control);
		if (!control_copy && dst->output.network.control) {
			ret = -1;
			goto end;
		}
		data_copy = uri_copy(dst->output.network.data);
		if (!data_copy && dst->output.network.data) {
			free(control_copy);
			ret = -1;
			goto end;
		}
		ret = network_location_set_from_lttng_uris(&dst->output.network,
				control_copy, data_copy);
		break;
	}
	case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE:
		goto end;
	}
end:
	return ret;
}
