/*
 * section and descriptor parser
 *
 * Copyright (C) 2005 Andrew de Quincey (adq_dvb@lidskialf.net)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include "transport_packet.h"

#define CONTINUITY_VALID 0x80
#define CONTINUITY_DUPESEEN 0x40

int transport_packet_values_extract(struct transport_packet *pkt,
				    struct transport_values *out,
 				    enum transport_value extract)
{
	uint8_t *end = (uint8_t*) pkt + TRANSPORT_PACKET_LENGTH;
	uint8_t *adapend;
	uint8_t *pos = (uint8_t*) pkt + sizeof(struct transport_packet);
	enum transport_value extracted = 0;
	enum transport_adaptation_flags adapflags = 0;
	enum transport_adaptation_extension_flags adapextflags = 0;
	int adaplength = 0;
	int adapextlength = 0;

	/* does the packet contain an adaptation field ? */
	if ((pkt->adaptation_field_control & 2) == 0)
		goto extract_payload;

	/* get the adaptation field length and skip the byte */
	adaplength = *pos++;

	/* do we actually have any adaptation data? */
	if (adaplength == 0)
		goto extract_payload;

	/* sanity check */
	adapend = pos + adaplength;
	if (adapend > end)
		return -1;

	/* extract the adaptation flags (we must have at least 1 byte to be here) */
	adapflags = *pos++;

	/* do we actually want anything else? */
	if ((extract & 0xffff) == 0)
		goto extract_payload;

	/* PCR? */
	if (adapflags & transport_adaptation_flag_pcr) {
		if ((pos+6) > adapend)
			return -1;

		if (extract & transport_value_pcr) {
			uint64_t base = ((uint64_t) pos[0] << 25) |
					((uint64_t) pos[1] << 17) |
					((uint64_t) pos[2] << 9) |
					((uint64_t) pos[3] << 1) |
					((uint64_t) pos[4] >> 7);
			uint64_t ext = (((uint64_t) pos[4] & 1) << 8) |
					(uint64_t) pos[5];
			out->pcr= base * 300ULL + ext;
			extracted |= transport_value_pcr;
		}
		pos += 6;
	}

	/* OPCR? */
	if (adapflags & transport_adaptation_flag_opcr) {
		if ((pos+6) > adapend)
			return -1;

		if (extract & transport_value_opcr) {
			uint64_t base = ((uint64_t) pos[0] << 25) |
					((uint64_t) pos[1] << 17) |
					((uint64_t) pos[2] << 9) |
					((uint64_t) pos[3] << 1) |
					((uint64_t) pos[4] >> 7);
			uint64_t ext = (((uint64_t) pos[4] & 1) << 8) |
					(uint64_t) pos[5];
			out->opcr= base * 300ULL + ext;
			extracted |= transport_value_opcr;
		}
		pos += 6;
	}

	/* splice countdown? */
	if (adapflags & transport_adaptation_flag_splicing_point) {
		if ((pos+1) > adapend)
			return -1;

		if (extract & transport_value_splice_countdown) {
			out->splice_countdown = *pos;
			extracted |= transport_value_splice_countdown;
		}
		pos++;
	}

	/* private data? */
	if (adapflags & transport_adaptation_flag_private_data) {
		if ((pos+1) > adapend)
			return -1;
		if ((pos+1+*pos) > adapend)
			return -1;

		if (extract & transport_value_private_data) {
			out->private_data_length = *pos;
			out->private_data = pos + 1;
			extracted |= transport_value_private_data;
		}
		pos += 1 + *pos;
	}

	/* is there an adaptation extension? */
	if (!(adapflags & transport_adaptation_flag_extension))
		goto extract_payload;

	/* get/check the length */
	if (pos >= adapend)
		return -1;
	adapextlength = *pos++;
	if ((pos + adapextlength) > adapend)
		return -1;

	/* do we want/have anything in the adaptation extension? */
	if (((extract & 0xff00) == 0) || (adapextlength == 0))
		goto extract_payload;

	/* extract the adaptation extension flags (we must have at least 1 byte
	 * to be here) */
	adapextflags = *pos++;

	/* LTW? */
	if (adapextflags & transport_adaptation_extension_flag_ltw) {
		if ((pos+2) > adapend)
			return -1;

		if (extract & transport_value_ltw) {
			if (*pos & 0x80) {
				out->ltw_offset = ((pos[0] & 0x7f) << 8) |
						  (pos[1]);
				extracted |= transport_value_ltw;
			}
		}
		pos += 2;
	}

	/* piecewise_rate? */
	if (adapextflags & transport_adaptation_extension_flag_piecewise_rate) {
		if ((pos+3) > adapend)
			return -1;

		if (extract & transport_value_piecewise_rate) {
			out->piecewise_rate = ((pos[0] & 0x3f) << 16) |
					      (pos[1] << 8) |
					      pos[2];
			extracted |= transport_value_piecewise_rate;
		}
		pos += 3;
	}

	/* seamless_splice? */
	if (adapextflags & transport_adaptation_extension_flag_seamless_splice) {
		if ((pos+5) > adapend)
			return -1;

		if (extract & transport_value_piecewise_rate) {
			out->splice_type = pos[0] >> 4;
			out->dts_next_au = ((pos[0] & 0x0e) << 29) |
					   (pos[1] << 22) |
					   ((pos[2] & 0xfe) << 14) |
					   (pos[3] << 7) |
					   ((pos[4] & 0xfe) >> 1);
			extracted |= transport_value_seamless_splice;
		}
		pos += 5;
	}



extract_payload:
	/* does the packet contain a payload? */
	if (pkt->adaptation_field_control & 1) {
		int off = sizeof(struct transport_packet);
		if (pkt->adaptation_field_control & 2)
			off++;
		off += adaplength;

		out->payload = (uint8_t*) pkt + off;
		out->payload_length = TRANSPORT_PACKET_LENGTH - off;
	} else {
		out->payload = NULL;
		out->payload_length = 0;
	}

	out->flags = adapflags;
	return extracted;
}

int transport_packet_continuity_check(struct transport_packet *pkt,
				      int discontinuity_indicator, unsigned char *cstate)
{
	unsigned char pktcontinuity = pkt->continuity_counter;
	unsigned char prevcontinuity = *cstate & 0x0f;
	unsigned char nextcontinuity;

	/* NULL packets have undefined continuity */
	if (transport_packet_pid(pkt) == TRANSPORT_NULL_PID)
		return 0;

	/* is the state valid? */
	if (!(*cstate & CONTINUITY_VALID)) {
		*cstate = pktcontinuity | CONTINUITY_VALID;
		return 0;
	}

	/* check for discontinuity_indicator */
	if (discontinuity_indicator) {
		*cstate = pktcontinuity | CONTINUITY_VALID;
		return 0;
	}

	/* only packets with a payload should increment the counter */
	if (pkt->adaptation_field_control & 1)
		nextcontinuity = (prevcontinuity + 1) & 0xf;
	else
		nextcontinuity = prevcontinuity;

	/* check for a normal continuity progression */
	if (nextcontinuity == pktcontinuity) {
		*cstate = pktcontinuity | CONTINUITY_VALID;
		return 0;
	}

	/* one dupe is allowed */
	if ((prevcontinuity == pktcontinuity) && (!(*cstate & CONTINUITY_DUPESEEN))) {
		*cstate = pktcontinuity | (CONTINUITY_VALID|CONTINUITY_DUPESEEN);
		return 0;
	}

	/* continuity error */
	return -1;
}
