/*
 * Copyright (C) 2014-2025 Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "pevent.h"


int pev_time_to_tsc(uint64_t *tsc, uint64_t time,
		    const struct pev_config *config)
{
	uint64_t quot, rem, time_zero;
	uint16_t time_shift;
	uint32_t time_mult;

	if (!tsc || !config)
		return -pte_internal;

	/* "time 0" is for synthesized events, and is not a real time. Using
	 * the normal conversion for a synthesized time causes problems, so
	 * just translate it as TSC time 0 ("beginning of time").
	 */
	if (!time) {
		*tsc = 0;
		return 0;
	}

	if (!pev_config_has(config, time_zero))
		return -pte_bad_config;

	time_shift = config->time_shift;
	time_mult = config->time_mult;
	time_zero = config->time_zero;

	if (!time_mult)
		return -pte_bad_config;

	time -= time_zero;

	quot = time / time_mult;
	rem = time % time_mult;

	quot <<= time_shift;
	rem <<= time_shift;
	rem /= time_mult;

	*tsc = quot + rem;

	return 0;
}

int pev_time_from_tsc(uint64_t *time, uint64_t tsc,
		      const struct pev_config *config)
{
	uint64_t quot, rem, time_zero;
	uint16_t time_shift;
	uint32_t time_mult;

	if (!time || !config)
		return -pte_internal;

	if (!pev_config_has(config, time_zero))
		return -pte_bad_config;

	time_shift = config->time_shift;
	time_mult = config->time_mult;
	time_zero = config->time_zero;

	if (!time_mult)
		return -pte_bad_config;

	quot = tsc >> time_shift;
	rem = tsc & ((1ull << time_shift) - 1);

	quot *= time_mult;
	rem *= time_mult;
	rem >>= time_shift;

	*time = time_zero + quot + rem;

	return 0;
}

static int pev_strlen(const char *begin, const void *end_arg)
{
	const char *pos, *end;

	if (!begin || !end_arg)
		return -pte_internal;

	end = (const char *) end_arg;
	if (end < begin)
		return -pte_internal;

	for (pos = begin; pos < end; ++pos) {
		if (!pos[0])
			return (int) (pos - begin) + 1;
	}

	return -pte_bad_packet;
}

static int pev_sample_type(uint64_t *psample_type, const uint64_t *pidentifier,
			   const struct pev_config *config)
{
	if (!psample_type || !config)
		return -pte_internal;

	if (!pev_config_has(config, sample_type))
		return -pte_bad_config;

#if (LIBIPT_SB_VERSION >= 0x201)
	if (pev_config_has(config, sample_config)) {
		const struct pev_sample_config *sconfig;
		uint64_t identifier;
		int sti, nstypes;

		sconfig = config->sample_config;
		if (!sconfig || !pidentifier) {
			*psample_type = config->sample_type;
			return 0;
		}

		identifier = *pidentifier;
		nstypes = sconfig->nstypes;
		for (sti = 0; sti < nstypes; ++sti) {
			const struct pev_sample_type *stype;
			uint64_t sample_type;

			stype = &sconfig->stypes[sti];
			if (stype->identifier != identifier)
				continue;

			sample_type = stype->sample_type;
			if (!(sample_type & PERF_SAMPLE_IDENTIFIER))
				return -pte_bad_config;

			*psample_type = sample_type;
			return 0;
		}
	}
#endif

	*psample_type = config->sample_type;
	return 0;
}

static int pev_read_samples(struct pev_event *event, const uint8_t *begin,
			    const uint8_t *end,
			    const struct pev_config *config)
{
	const uint64_t *pidentifier;
	const uint8_t *pos;
	uint64_t sample_type;
	int errcode;

	if (!event || !begin || !end || !config)
		return -pte_internal;

	pidentifier = NULL;
	pos = (end - sizeof(*pidentifier));
	if (begin <= pos)
		pidentifier = (const uint64_t *) pos;

	errcode = pev_sample_type(&sample_type, pidentifier, config);
	if (errcode < 0)
		return errcode;

	pos = begin;

	if (sample_type & PERF_SAMPLE_TID) {
		event->sample.pid = (const uint32_t *) &pos[0];
		event->sample.tid = (const uint32_t *) &pos[4];
		pos += 8;
	}

	if (sample_type & PERF_SAMPLE_TIME) {
		event->sample.time = (const uint64_t *) pos;
		pos += 8;

		/* We're reading the time.  Let's make sure the pointer lies
		 * inside the buffer.
		 */
		if (end < pos)
			return -pte_nosync;

		errcode = pev_time_to_tsc(&event->sample.tsc,
					  *event->sample.time, config);
		if (errcode < 0)
			return errcode;
	}

	if (sample_type & PERF_SAMPLE_ID) {
		event->sample.id = (const uint64_t *) pos;
		pos += 8;
	}

	if (sample_type & PERF_SAMPLE_STREAM_ID) {
		event->sample.stream_id = (const uint64_t *) pos;
		pos += 8;
	}

	if (sample_type & PERF_SAMPLE_CPU) {
		event->sample.cpu = (const uint32_t *) pos;
		pos += 8;
	}

	if (sample_type & PERF_SAMPLE_IDENTIFIER) {
		event->sample.identifier = (const uint64_t *) pos;
		pos += 8;
	}

	return (int) (pos - begin);
}

int pev_read(struct pev_event *event, const uint8_t *begin, const uint8_t *end,
	     const struct pev_config *config)
{
	const struct perf_event_header *header;
	const uint8_t *pos;
	int size;

	if (!event || !begin || end < begin)
		return -pte_internal;

	pos = begin;
	if (end < (pos + sizeof(*header)))
		return -pte_eos;

	header = (const struct perf_event_header *) pos;
	pos += sizeof(*header);

	if (header->size < sizeof(*header))
		return -pte_nosync;

	if (!header->type || (end < (begin + header->size)))
		return -pte_eos;

	/* Stay within the packet. */
	end = begin + header->size;

	memset(event, 0, sizeof(*event));

	event->type = header->type;
	event->misc = header->misc;

	switch (event->type) {
	default:
		/* We don't provide samples.
		 *
		 * It would be possible since we know the event's total size
		 * as well as the sample size.  But why?
		 */
		return (int) header->size;

	case PERF_RECORD_MMAP: {
		int slen;

		event->record.mmap = (const struct pev_record_mmap *) pos;

		slen = pev_strlen(event->record.mmap->filename, end);
		if (slen < 0)
			return slen;

		slen = (slen + 7) & ~7;

		pos += sizeof(*event->record.mmap);
		pos += slen;
	}
		break;

	case PERF_RECORD_LOST:
		event->record.lost = (const struct pev_record_lost *) pos;
		pos += sizeof(*event->record.lost);
		break;

	case PERF_RECORD_COMM: {
		int slen;

		event->record.comm = (const struct pev_record_comm *) pos;

		slen = pev_strlen(event->record.comm->comm, end);
		if (slen < 0)
			return slen;

		slen = (slen + 7) & ~7;

		pos += sizeof(*event->record.comm);
		pos += slen;
	}
		break;

	case PERF_RECORD_EXIT:
		event->record.exit = (const struct pev_record_exit *) pos;
		pos += sizeof(*event->record.exit);
		break;

	case PERF_RECORD_THROTTLE:
	case PERF_RECORD_UNTHROTTLE:
		event->record.throttle =
			(const struct pev_record_throttle *) pos;
		pos += sizeof(*event->record.throttle);
		break;

	case PERF_RECORD_FORK:
		event->record.fork = (const struct pev_record_fork *) pos;
		pos += sizeof(*event->record.fork);
		break;

	case PERF_RECORD_MMAP2: {
		int slen;

		event->record.mmap2 = (const struct pev_record_mmap2 *) pos;

		slen = pev_strlen(event->record.mmap2->filename, end);
		if (slen < 0)
			return slen;

		slen = (slen + 7) & ~7;

		pos += sizeof(*event->record.mmap2);
		pos += slen;
	}
		break;

	case PERF_RECORD_AUX:
		event->record.aux = (const struct pev_record_aux *) pos;
		pos += sizeof(*event->record.aux);
		break;

	case PERF_RECORD_ITRACE_START:
		event->record.itrace_start =
			(const struct pev_record_itrace_start *) pos;
		pos += sizeof(*event->record.itrace_start);
		break;

	case PERF_RECORD_LOST_SAMPLES:
		event->record.lost_samples =
			(const struct pev_record_lost_samples *) pos;
		pos += sizeof(*event->record.lost_samples);
		break;

	case PERF_RECORD_SWITCH:
		break;

	case PERF_RECORD_SWITCH_CPU_WIDE:
		event->record.switch_cpu_wide =
			(const struct pev_record_switch_cpu_wide *) pos;
		pos += sizeof(*event->record.switch_cpu_wide);
		break;
	}

	size = pev_read_samples(event, pos, end, config);
	if (size < 0)
		return size;

	pos += size;
	if (pos < begin)
		return -pte_internal;

	size = (int) (pos - begin);
	if ((uint16_t) size != header->size)
		return -pte_nosync;

	return size;
}

static int pev_sample_config(size_t *psample_size, uint64_t *psample_type,
			     const struct pev_event *event)
{
	uint64_t sample_type;
	size_t sample_size;

	if (!psample_size || !psample_type || !event)
		return -pte_internal;

	sample_type = 0;
	sample_size = 0;

	if (event->sample.tid) {
		sample_type |= PERF_SAMPLE_TID;
		sample_size += sizeof(*event->sample.pid);
		sample_size += sizeof(*event->sample.tid);
	}

	if (event->sample.time) {
		sample_type |= PERF_SAMPLE_TIME;
		sample_size += sizeof(*event->sample.time);
	}

	if (event->sample.id) {
		sample_type |= PERF_SAMPLE_ID;
		sample_size += sizeof(*event->sample.id);
	}

	if (event->sample.stream_id) {
		sample_type |= PERF_SAMPLE_STREAM_ID;
		sample_size += sizeof(*event->sample.stream_id);
	}

	if (event->sample.cpu) {
		sample_type |= PERF_SAMPLE_CPU;
		sample_size += sizeof(*event->sample.cpu);
		sample_size += sizeof(uint32_t);
	}

	if (event->sample.identifier) {
		sample_type |= PERF_SAMPLE_IDENTIFIER;
		sample_size += sizeof(*event->sample.identifier);
	}

	*psample_type = sample_type;
	*psample_size = sample_size;

	return 0;
}

static void write(uint8_t **stream, const void *object, size_t size)
{
	memcpy(*stream, object, size);
	*stream += size;
}

static void clear(uint8_t **stream, size_t size)
{
	memset(*stream, 0, size);
	*stream += size;
}

static int write_samples(uint8_t **stream, const struct pev_event *event,
			 const struct pev_config *config, uint64_t sample_type)
{
	uint64_t cstype;
	int errcode;

	if (!event || !config)
		return -pte_internal;

	errcode = pev_sample_type(&cstype, event->sample.identifier, config);
	if (errcode < 0)
		return errcode;

	if (cstype != sample_type)
		return -pte_bad_config;

	if (sample_type & PERF_SAMPLE_TID) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_TID;

		if (!event->sample.pid || !event->sample.tid)
			return -pte_bad_packet;

		write(stream, event->sample.pid, sizeof(*event->sample.pid));
		write(stream, event->sample.tid, sizeof(*event->sample.tid));
	}

	if (sample_type & PERF_SAMPLE_TIME) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_TIME;

		if (!event->sample.time)
			return -pte_bad_packet;

		write(stream, event->sample.time, sizeof(*event->sample.time));
	}

	if (sample_type & PERF_SAMPLE_ID) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_ID;

		if (!event->sample.id)
			return -pte_bad_packet;

		write(stream, event->sample.id, sizeof(*event->sample.id));
	}

	if (sample_type & PERF_SAMPLE_STREAM_ID) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_STREAM_ID;

		if (!event->sample.stream_id)
			return -pte_bad_packet;

		write(stream, event->sample.stream_id,
		      sizeof(*event->sample.stream_id));
	}

	if (sample_type & PERF_SAMPLE_CPU) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_CPU;

		if (!event->sample.cpu)
			return -pte_bad_packet;

		write(stream, event->sample.cpu, sizeof(*event->sample.cpu));
		*stream += sizeof(uint32_t);
	}

	if (sample_type & PERF_SAMPLE_IDENTIFIER) {
		sample_type &= ~(uint64_t) PERF_SAMPLE_IDENTIFIER;

		if (!event->sample.identifier)
			return -pte_bad_packet;

		write(stream, event->sample.identifier,
		      sizeof(*event->sample.identifier));
	}

	if (sample_type)
		return -pte_bad_packet;

	return 0;
}

int pev_write(const struct pev_event *event, uint8_t *begin, uint8_t *end,
	      const struct pev_config *config)
{
	struct perf_event_header header;
	uint64_t sample_type;
	uint8_t *pos;
	size_t sample_size, size;
	int errcode;

	if (!event || !begin || end < begin)
		return -pte_internal;

	errcode = pev_sample_config(&sample_size, &sample_type, event);
	if (errcode < 0)
		return errcode;

	pos = begin;
	size = sizeof(header) + sample_size;
	if (UINT16_MAX < size)
		return -pte_internal;

	header.type = event->type;
	header.misc = event->misc;

	switch (header.type) {
	default:
		return -pte_bad_opc;

	case PERF_RECORD_MMAP: {
		size_t slen, gap;

		slen = strnlen(event->record.mmap->filename, UINT16_MAX);
		if (UINT16_MAX <= slen)
			return -pte_eos;

		slen += 1;
		gap = ((slen + 7u) & ~7u) - slen;

		size += sizeof(*event->record.mmap) + slen + gap;
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.mmap, sizeof(*event->record.mmap));
		write(&pos, event->record.mmap->filename, slen);
		clear(&pos, gap);
	}
		break;

	case PERF_RECORD_LOST:
		size += sizeof(*event->record.lost);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.lost, sizeof(*event->record.lost));
		break;

	case PERF_RECORD_COMM: {
		size_t slen, gap;

		slen = strnlen(event->record.comm->comm, UINT16_MAX);
		if (UINT16_MAX <= slen)
			return -pte_eos;

		slen += 1;
		gap = ((slen + 7u) & ~7u) - slen;

		size += sizeof(*event->record.comm) + slen + gap;
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.comm, sizeof(*event->record.comm));
		write(&pos, event->record.comm->comm, slen);
		clear(&pos, gap);
	}
		break;

	case PERF_RECORD_EXIT:
		size += sizeof(*event->record.exit);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.exit, sizeof(*event->record.exit));
		break;

	case PERF_RECORD_THROTTLE:
	case PERF_RECORD_UNTHROTTLE:
		size += sizeof(*event->record.throttle);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.throttle,
		      sizeof(*event->record.throttle));
		break;

	case PERF_RECORD_FORK:
		size += sizeof(*event->record.fork);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.fork, sizeof(*event->record.fork));
		break;

	case PERF_RECORD_MMAP2: {
		size_t slen, gap;

		slen = strnlen(event->record.mmap2->filename, UINT16_MAX);
		if (UINT16_MAX <= slen)
			return -pte_eos;

		slen += 1;
		gap = ((slen + 7u) & ~7u) - slen;

		size += sizeof(*event->record.mmap2) + slen + gap;
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.mmap2, sizeof(*event->record.mmap2));
		write(&pos, event->record.mmap2->filename, slen);
		clear(&pos, gap);
	}
		break;

	case PERF_RECORD_AUX:
		size += sizeof(*event->record.aux);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.aux, sizeof(*event->record.aux));
		break;

	case PERF_RECORD_ITRACE_START:
		size += sizeof(*event->record.itrace_start);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.itrace_start,
		      sizeof(*event->record.itrace_start));
		break;

	case PERF_RECORD_LOST_SAMPLES:
		size += sizeof(*event->record.lost_samples);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.lost_samples,
		      sizeof(*event->record.lost_samples));
		break;

	case PERF_RECORD_SWITCH:
		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		break;

	case PERF_RECORD_SWITCH_CPU_WIDE:
		size += sizeof(*event->record.switch_cpu_wide);
		if (UINT16_MAX < size)
			return -pte_bad_packet;

		header.size = (uint16_t) size;
		if (end < pos + header.size)
			return -pte_eos;

		write(&pos, &header, sizeof(header));
		write(&pos, event->record.switch_cpu_wide,
		      sizeof(*event->record.switch_cpu_wide));
		break;
	}

	errcode = write_samples(&pos, event, config, sample_type);
	if (errcode < 0)
		return errcode;

	return (int) (pos - begin);
}
